Swift関数を繰り上げて返す

5075 ワード

簡単な評価:関数が早期に戻る主なメリットは、各エラー処理を分離し、コードを審査する際に多くの複雑な異常を考慮する必要がなく、ビジネスロジックに集中し、コードをデバッグする際に直接異常の中でブレークポイントを打つことができることです.

リードバック


まず、改善すべきコードの例を見てみましょう.ノートの内容が変化した場合、ノートのリストの変更を通知するノートアプリケーションを構築します.コードは以下の通りです.
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()
            }
        }
    }
}


上のコードはよく機能しますが、可読性が少し悪いです.このコードには複数のインデントとタイプ変換が含まれているからです.このコードを改善しようとします.
  • メソッドを事前に返し、できるだけ速く返しましょう.
  • ifの代わりにguardを使用して、ネストを回避します.
  • 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()
        }
    }
    
    

    関数を早期に返すことで、機能が失敗した場合をより明確に処理できます.これは、可読性(インデントが少なく、ネストが少ない)を向上させるだけでなく、ユニットテストにも役立ちます.
    さらにコードを改良し、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にブレークポイントを直接追加してすべての失敗をキャプチャすることができます.

    じょうけんこうぞう


    オブジェクトインスタンスを構築する場合、どのオブジェクトを構築する必要があるかは、一連の条件に依存します.
    たとえば、アプリケーションを起動するときに表示される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を返す.次のようになります.
    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法は純粋な関数である(外部状態に影響を及ぼさず、固定入力で固定出力が得られる)ため、実際に外部状態に影響を及ぼすのは1箇所のみである.viewControllers=[viewController],(日常開発において状態が良好に制御されていないとバグが発生しやすいため,より少ない状態を用いることと状態への修正を減らすことでバグが発生する確率をある程度低減できる).

    じょうけんせいぎょ


    最後に,関数が複雑な条件論理をどのように簡略化するかを見てみよう.私たちはview controllerを構築してソーシャルアプリケーションのコメント機能を表示し、3つの条件を満たすとユーザーがコメントを編集することを実行します.コードは次のとおりです.
    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おすすめ読書:JavaScript正規表現ユーティリティガイド