web-dev-qa-db-ja.com

適切な方法で前提条件をチェックする

私には約1300行のクラスがあり、パラメーターを確認する必要があるCRUDに似たメソッドがたくさんあります。それらのいくつかは、いくつかのルール以上のものです。

わかりやすくするために、クラスとメソッドには総称名を使用します。

最近、すべての前提条件チェックの実装を完了し、メソッドと同様の手順の最初にifブロックを使用してカシカルな方法でそれを行っていましたが、結局、チャンクが繰り返されていました(これを抽出してみましたプライベートメソッドにそれは完全に助けにはならなかった)そして大丈夫ですが私が本当に再構築したいので整然としていて明確なクラス.

だから、これは私のクラスの非常に簡潔なバージョンなので、次のようになります。

public class Event {
    public void setAttribute1(List<Attribute1> attribute1) {
        if (attribute1 == null)
            throw new IllegalArgumentException("Attribute1 cannot be null");

        if (attribute1.isEmpty())
            throw new IllegalArgumentException("Attribute1 cannot be empty");

        // check for repeated elements

        // check for more things

        this.attribute1 = attribute1;
    }

    public void addAttribute1(Attribute1 attr) {
        // null check

        // check not existing

        // check for more things

        attribute1.add(attr);
    }

    public void removeAttribute1(Attribute1 attr) {
        // null check

        // check not existing

        attribute1.remove(attr);
    }

    public void setMyMap(Map<K, V> myMap) {
        // null check

        // keys check

        // values check

        // more and more

        this.myMap = myMap;
    }

    // a long etc...
}

結局、部分的に繰り返される巨大な前提条件チェックのチャンクができてしまいます。これは乱雑で混乱を招きます。特に、同じ属性に対して多くの類似したメソッドがあり、それらが同じでないことを除いて、同じチェックがある場合は特にそうです。上記の例が私の意味を理解するのに役立たない場合、これは私のクラスで何度も繰り返されるケースになります:

public class Event {
    private List<K> kDomain;
    private List<V> vDomain;
    private int kConfig;

    public void setMap(Map<K, Set<V>> map) {
        // null check

        // all k in K should be in kDomain

        // the length of the map should match an arithmetical operation based on the value of kConfig
        // (and similar checks)

        // a null Set<V> associated to a K should be illegal

        // for each k, each v in V should be in vDomain
    }

    public void addVtoK(K k, V v) {
        // null check k and v

        // k must be in kDomain

        // v must be in vDomain

        // if k has already a set of Vs, v must not be in that set already (already existing object check)
    }

    public void addVsToK(K k, Set<V> vs) {
        // null check k and vs

        // k must be in kDomain

        // all v in vs should be in vDomain

        // no v in vs can already be associated to k
    }
}

ご覧のとおり、非常に似ている長いチェックがありますが、同じではありません。

私はクラス全体を再構成するための最良の方法を探しています。これは、最終的には書き直しになると確信しています。

私は Guava Preconditions を調べましたが、見た目はすっきりしていますが、コードの拡張になるとは言え、処理が少し複雑で、より深い設計が必要な特定の前提条件を確認する必要があります考え。

適切なアプローチは何でしょうか?または私は私の問題を考えすぎていますか?

6
dabadaba

あなたの前提条件をチェックすることは立派ですが、私は不思議に思います:

  1. これらのすべてをチェックする必要があるようなクラスの使用法ですか?例えばより一般的な使用のために公開されている一連のコンポーネントの前提条件をチェックしますが、コンポーネントの使用方法がわかっているより限定的な使用では、あまり多くのチェックを実行しません。これに対する反論は、より広範囲に使用するためにコンポーネントを抽出する場合(ライブラリなど)、より多くの前提条件を適用する必要があるということですが、それは実用的なアプローチです
  2. これらすべてのチェックは本当に必要ですか?例えば追加する要素の空のセットを指定した場合、何も追加されないことを本当に気にかけますか?このエンティティに追加するときに、クライアントコードが空のSetチェックで散らばりたくありません。エンティティに対処させる(つまり、何もしない)

上記にもかかわらず、前提条件に繰り返しコードが大量にある場合は、おそらく Javaの動的プロキシ をチェックしてください。これらにより、オブジェクトのすべてのメソッド呼び出しをラップするラッパーメソッドを記述できます。次に、呼び出すメソッドがsetXYZ()であるかどうか、および属性のセットまたは1つだけを追加するかどうかを確認し、実際の実装にさらに委任する前に一般的に前提条件を呼び出すことができます。 。

4
Brian Agnew

自身の入力を検証するのはメソッドの責任です。ライブラリを構築しているかどうかに関係なく、完全かつ厳密に行う必要があります。 (imo)が適切な入力検証をスキップするのに十分な理由はめったにありません。意味のある結果を返す場合、エッジのケースを自然な入力のセットの一部として処理することは許容されます(たとえば、空のセットにループ全体で0回反復させる)。

各メソッドのパラメーターを個別に検証する場合、異なるメソッドで同様の検証が行われると、繰り返しが発生する可能性があります。あなたはその繰り返しで大丈夫です:

DRYは過大評価されています-ほとんどの場合、プロセス間の結合がより緊密になり、この状況では、(想定されている場合は)独立したメソッド間で間接的に結合を効果的に導入することもできます。パラメーターを一元的に検証する方法でこれをリファクタリングする場合は、複数のメソッドをバリデーターメソッドに依存させることになります。私は常にそのアプローチを推奨するわけではありません。特に、後でバリデーターメソッドが他のメソッドに依存していることが判明したり、後で他の多くのメソッドを呼び出したりする場合など、簡単な組み合わせになります。コード(バリデーター)は呼び出し階層の奥深くに埋め込まれており、スパゲッティのようなアーキテクチャーにリファクタリングするのが非常に難しくなる可能性があります。

DRYで問題ない可能性のある部分は、かなり単純な「リーフ」メソッドのままである可​​能性が高い、単一の責任、再利用された、重要ではない(ただし巨大ではない)ロジックの一部)時間の経過に伴う呼び出しツリーの変更。ただし、DRYを適用して、たとえばnullでないかどうかを確認するなどの単純なチェックを行わないでください。これは、コードの単純さと保守性のバランスと、緊密な結合を作りすぎないことです。それは将来的に柔軟性に欠ける可能性があります。

1
Brad Thomas