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
相關文章
相關標籤/搜索