簡評:函數提早返回主要的好處是:將每一個錯誤處理進行分離,審查代碼時不須要考慮多種複雜異常,咱們能夠吧注意力集中在也業務邏輯中,調試代碼時能夠直接在異常中打斷點。
首先來看一下須要改進的代碼示例,咱們構建一個筆記應用使用 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