简评:函数提早返回主要的好处是:将每一个错误处理进行分离,审查代码时不须要考虑多种复杂异常,咱们能够吧注意力集中在也业务逻辑中,调试代码时能够直接在异常中打断点。
首先来看一下须要改进的代码示例,咱们构建一个笔记应用使用 NotificationCenter API,当笔记内容有变化时 Notification 来通知笔记列表变动,代码以下:web
class NoteListViewController: UIViewController { @objc func handleChangeNotification(_ notification: Notification) { let noteInfo = notification.userInfo?["note"] as? [String : Any] if let id = noteInfo?["id"] as? Int { if let note = database.loadNote(withID: id) { notes[id] = note tableView.reloadData() } } } }
上面的代码能够很好的工做,可是可读性差了点。由于这段代码包含多重缩进和类型转换。咱们来尝试改进这段代码。swift
class NoteListViewController: UIViewController { @objc func handleChangeNotification(_ notification: Notification) { let noteInfo = notification.userInfo?["note"] as? [String : Any] guard let id = noteInfo?["id"] as? Int else { return } guard let note = database.loadNote(withID: id) else { return } notes[id] = note tableView.reloadData() } }
将函数提早返回可以将功能失败的状况处理得更加清晰,这不只提升了可读性(更少的缩进,更少的嵌套),同时也有利于单元测试。ide
咱们能够进一步改进代码,将获取 noteID 和类型转换的代码放在 Notification Extension 中,这样就将 handleChangeNotification 业务逻辑和具体细节分离开来。修改后代码以下所示:函数
private extension Notification { var noteID: Int? { let info = userInfo?["note"] as? [String : Any] return info?["id"] as? Int } } class NoteListViewController: UIViewController { @objc func handleChangeNotification(_ notification: Notification) { guard let id = notification.noteID else { return } guard let note = database.loadNote(withID: id) else { return } notes[id] = note tableView.reloadData() } }
这种结构还大大简化了调试的难度,咱们能够直接在每一个 guard 中 return 中添加断点来截获全部失败状况,而不须要单步执行全部逻辑。post
当构造一个对象实例,很是广泛的需求是须要构建哪类对象取决于一系列的条件。单元测试
例如,启动应用程序时显示哪一个 view controller 取决于:测试
咱们对这些条件的的实现多是一系列的 if 和 else 语句,以下所示:优化
func showInitialViewController() { if loginManager.isUserLoggedIn { if tutorialManager.isOnboardingCompleted { navigationController.viewControllers = [HomeViewController()] } else { navigationController.viewControllers = [OnboardingViewController()] } } else { navigationController.viewControllers = [LoginViewController()] } }
一样的提早返回和 guard 语句能够提高代码可读性,可是如今这种状况不是处理失败状况,而是在不一样条件下构建不一样 view controller。调试
如今来改进这段代码,使用轻量级的工程模式,将构造初始界面移动到专门的函数中,该函数返回匹配条件的view controller。以下所示:code
func makeInitialViewController() -> UIViewController { guard loginManager.isUserLoggedIn else { return LoginViewController() } guard tutorialManager.isOnboardingCompleted else { return OnboardingViewController() } return HomeViewController() } func showInitialViewController() { let viewController = makeInitialViewController() navigationController.viewControllers = [viewController] }
因为 makeInitialViewController 方法是个纯函数(不影响外部状态,固定输入可以获得固定输出),实际上影响外部状态的只有一个地方 navigationController.viewControllers = [viewController] ,(在平常开发中状态若是没有获得很好的控制很容易引发 bug,因此使用更少状态和减小对状态的修改能够必定程度上减小 bug 出现的概率)。
最后咱们来看看,函数如何简化复杂的条件逻辑。咱们来构建一个 view controller 来显示社交应用的评论功能,若是知足三个条件则运行用户对评论进行编辑。代码以下:
class CommentViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() if comment.authorID == user.id { if comment.replies.isEmpty { if !comment.edited { let editButton = UIButton() ... view.addSubview(editButton) } } } ... } }
这里使用了 3 个 if 嵌套逻辑,每次从新审查代码都会比较困扰,更具以前的经验咱们能够对代码进行优化,添加 Comment extension:
extension Comment { func canBeEdited(by user: User) -> Bool { guard authorID == user.id else { return false } guard comment.replies.isEmpty else { return false } return !edited } } class CommentViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() if comment.canBeEdited(by: user) { let editButton = UIButton() ... view.addSubview(editButton) } ... } }
原文: Early returning functions in Swift