Swift 函数提早返回

简评:函数提早返回主要的好处是:将每一个错误处理进行分离,审查代码时不须要考虑多种复杂异常,咱们能够吧注意力集中在也业务逻辑中,调试代码时能够直接在异常中打断点。

提早返回

首先来看一下须要改进的代码示例,咱们构建一个笔记应用使用 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

  • 将方法提早返回,让咱们函数尽量的快的返回。
  • 使用 guard 替代 if,以免嵌套。
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 取决于:测试

  • 是否已经登陆。
  • 用户是否已经完成入职流程(onboarding flow)。

咱们对这些条件的的实现多是一系列的 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
相关文章
相关标签/搜索