it-swarm-ja.com

オプションのバインディングを含む条件の場合、複雑な方法を分解するにはどうすればよいですか?

私は最近Clean Codeを読んでいて、Javaに、コメントを使用するよりも複雑なifステートメントの条件を関数に分解するほうがよい場合)の非常に良い例がありました。

_// Check to see if the employee is eligible for full benefits
if ((employee.flags && HOURLY_FLAG) && (employee.age > 65))
_

_if (employee.isEligibleForFullBenefits() )
_

これをSwiftで利用できるようにしたいのですが、オプションのバインディングの性質により、これは困難です。

_if let employeeFlags = employee.flags,
    let hourlyFlag = self.hourlyFlag,
    employee.age > 65 {
        //Do stuff with unwrapped optionals
    }
_

アンラップしたオプションはスコープ内にないため、強制的にアンラップしないと、条件を適切な名前の関数にバンドルできません。これは読みやすいですが、誰かがisEligibleForFullBenefitsを変更した場合、nil値を強制的にアンラップするとクラッシュする可能性があります。

_if employee.isEligibleForFullBenefits {
    //Do something with:
    employee.flags!
    hourlyFlags!
}
_

私の別の試みは、条件とifステートメントのコードブロックの両方の関数を作成することです。

tryToDoSomethingWithEmpolyeeBenefits()またはdoSomethingWithEmployeeBenefitsIfElligible()

強制アンラップを回避しますが、関数が条件をチェックし、その条件に基づいてアクションを実行することを考えると、おそらく単一責任の原則を破っていますか?

これらのうちの1つは良い解決策ですか、より良い解決策がありますか、またはこれはSwiftを使用するときに単に良い考えではありませんか?

5
Declan McKenna

サンプルコードを簡略化するために使用できるアプローチはいくつかあります。

それらをオプションにしない(別名nullオブジェクトとデフォルト値)

オプションの型をアンラップすることを心配しない最も直接的な方法は、オプションの型を持たないことです。適切なデフォルトがある場合、オプションではなく常に値を返すアクセサーを作成できます。デフォルト値は、オプションが存在せず、値を使用しようとするすべての場所で同じデフォルト値が理にかなっている場合、コードがすでにデフォルトを想定している(booleans、intなどの)単純型に適しています。 Nullオブジェクトは、より複雑な型に対して同様の役割を果たします。 nullオブジェクトは、実際のオブジェクトと同じインターフェイスを実装しますが、「何もしない」です。アクセサは正しいデフォルトを返すことができ、コマンドメソッドは何もしない実装を持つことができます。コレクションのタイプ(配列、セット、辞書など)を使用している場合、空のコレクションは有効なnullオブジェクトです。

適切なデフォルト値がない場合、この方法は使用できません。大きくて複雑なnullオブジェクトを表す必要がある場合、そのような大きな「何もしない」クラスを維持するのは面倒です。

ラムダ関数パラメーター

ブール値を返す_employee.isEligibleForFullBenefits_メソッドの代わりに、ラムダ関数を取る_employee.whenEligibleForFullBenefits_メソッドを使用できます。ラムダ関数は必要なラップされていない値を取ります。これらの値が存在する場合にのみ呼び出されます。したがって、コードは次のようになります。

_class Employee {
    func whenEligibleForFullBenefits(
            hourlyFlags optionalHourlyFlags: HourlyFlagsType?,
            thenDo: (EmployeeFlagsType, HourlyFlagsType) -> void) {
        if let employeeFlags = self.flags,
            let hourlyFlags = optionalHourlyFlags,
            self.age > 65 {
            thenDo(employeeFlags, hourlyFlags)
        }
    }
}
_

これは、これが一般的な状態である場合に意味がありますが、その状態に基づいて行うことは大きく異なります。条件がカプセル化され、動作が挿入されます。

代わりに、これが多くの中で1回限りの条件である場合、このような方法が急速に増殖し、利益が少なくなります。これは、条件が同じでも、入力パラメーターまたは必要な値が異なる場合でも当てはまります。または、この条件が同じ動作に一般的にリンクされている場合、そのロジックをどこにでも複製するため、この条件を渡したくないでしょう。

聞かないで

あなたの例は、従業員の内部のさまざまな価値観にひどく興味を持っているようです。さまざまな情報を従業員に問い合わせる代わりに、従業員に何をしたいかを伝えます。これは、彼らがクリーンコードで作成していた元のポイントに戻ります。これは、コードをdoSomethingWithEmployeeBenefitsIfEligible()メソッドにプルすることによって実行しようとしていることでもあります。

正確なコードを確認しないと、これが理想的なソリューションであるかどうかを判断するのは困難です。実行できるさまざまなsomethingsがたくさんある場合、Employeeに多くのメソッドが作成され、メンテナンスが難しくなります。

不変にする

isEligibleForFullBenefits()メソッドを変更するとnil値がラップ解除されるのではないかと心配しています。 trueを返すisEligibleForFullBenefits()メソッドは、値がそのコントラクトの一部としてnilでないことを暗黙的に保証することを宣言できます。誰かがメソッドを変更するとき、彼らは不変条件がまだ成り立つことを確認する必要があります。これを自動的に確認するには、完全な自動テストを記述して、状態のさまざまな組み合わせと、不変条件が正​​しく保持されていることを確認します。誰かが不変条件に違反する変更を加えた場合、テストは失敗します。

不変条件を "宣言"することは、コンパイラではチェックできないことがよくありますが、代わりに、テストまたはアサーションを通じて、実行時にチェックできます。これは、誰かが誤った変更を行った場合、フィードバックが多少遅れることを意味します。また、自動テストでチェックされない変更を加えて、不変条件を意図せずに壊してしまう可能性もあります。一部の不変条件は、コードではまったくチェックできませんが、コメントでのみ指定できます。その場合、開発者の勤勉さが唯一の安全策です。

2
cbojar