web-dev-qa-db-ja.com

クリーンなコードとハイブリッドオブジェクトと機能の羨望

そのため、最近、コードにいくつかの主要なリファクタリングを行いました。私がしようとした主なことの1つは、クラスをデータオブジェクトとワーカーオブジェクトに分割することでした。これは、とりわけ クリーンコード のこのセクションに触発されました。

ハイブリッド

この混乱は、半分のオブジェクトと半分のデータ構造である不幸なハイブリッドデータ構造につながる場合があります。重要なことを行う関数があり、パブリック変数またはパブリックアクセサーとミューテーターのいずれかがあり、すべての目的と目的のために、プライベート変数をパブリックにして、他の外部関数がそれらの変数を手続き型プログラムが使用する方法で使用するように誘いますデータ構造。

そのようなハイブリッドは、新しい関数を追加することを困難にしますが、新しいデータ構造を追加することも困難にします。彼らは両方の世界で最悪です。それらを作成しないでください。それらは、作者が関数や型からの保護を必要とするかどうか、またはさらに悪いことに無知である混乱した設計を示しています。

最近、私はワーカーオブジェクトの1つ(たまたま Visitor Pattern を実装する)のコードを見て、これを確認しました。

@Override
public void visit(MarketTrade trade) {
    this.data.handleTrade(trade);
    updateRun(trade);
}

private void updateRun(MarketTrade newTrade) {
    if(this.data.getLastAggressor() != newTrade.getAggressor()) {
        this.data.setRunLength(0);
        this.data.setLastAggressor(newTrade.getAggressor());
    }
    this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}

私はすぐに「機能が羨ましいです。このロジックはDataクラス、特にhandleTradeメソッドにあるはずです。handleTradeupdateRun常に一緒に起こります "。しかし、「データクラスは単なるpublicデータ構造です。それを始めれば、ハイブリッドオブジェクトになるでしょう!」

何が良いのか、そしてその理由は?どちらを行うかをどのように決定しますか?

10
durron597

あなたが引用したテキストには良いアドバイスがありますが、構造体のようなものを意味すると仮定して、「データ構造」を「レコード」に置き換えます。レコードは、データの単なる集約です。それらは変更可能である可能性があります(したがって、関数型プログラミングの考え方ではステートフルです)が、内部状態はなく、保護する必要のある不変条件はありません。使用を容易にする操作をレコードに追加することは完全に有効です。

たとえば、3Dベクトルはダムレコードであると主張できます。ただし、これによってaddのようなメソッドを追加でき、ベクターの追加が簡単になります。動作を追加しても、(それほど馬鹿ではない)レコードはハイブリッドになりません。

オブジェクトのパブリックインターフェイスでカプセル化を解除できるようになると、この行が交差します。直接アクセスできる内部がいくつかあり、オブジェクトが無効な状態になります。 Dataには状態があり、無効な状態になる可能性があるようです。

  • トレードを処理した後、最後のアグレッサーは更新されない場合があります。
  • 最後の攻撃者は、新しい取引が発生していなくても更新できます。
  • アグレッサーが更新された場合でも、ランレングスは古い値を保持する場合があります。
  • 等.

データに対して有効な状態があれば、コードはすべて正常であり、続行できます。それ以外の場合:Dataクラスは、独自のデータ整合性を担当します。取引の処理に常にアグレッサーの更新が含まれる場合、この動作はDataクラスの一部である必要があります。アグレッサーの変更で実行長をゼロに設定する必要がある場合、この動作はDataクラスの一部である必要があります。 Dataは、決してばかげた記録ではありませんでした。パブリックセッターを追加することで、すでにハイブリッドになっています。

これらの厳格な責任を緩和する検討できるシナリオが1つあります:Dataがプロジェクトにプライベートであり、したがって、パブリックインターフェースがあっても、クラスを適切に使用できます。ただし、これにより、Dataの一貫性をコード全体で維持する必要があり、中央の場所で収集するのではありません。

私は最近 カプセル化についての回答 を書きました。

9
amon

handleTrade()updateRun()は常に一緒に発生する(そして、2番目のメソッドは実際にはビジター上にあり、データオブジェクトで他のいくつかのメソッドを呼び出す)という匂い 時間結合 。これは、特定の順序でメソッドを呼び出す必要があることを意味します。順序外でメソッドを呼び出すと、最悪の場合、何かが壊れるか、提供できなくなると思いますせいぜい意味のある結果。良くない。

通常、その依存関係をリファクタリングする正しい方法は、各メソッドが結果を返すことです。この結果は、次のメソッドにフィードするか、直接操作することができます。

古いコード:

MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();

新しいコード:

MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();

これにはいくつかの利点があります。

  • 個別の関心事を個別のオブジェクトに移動します( [〜#〜] srp [〜#〜] )。
  • これにより、時間的な結合がなくなります。メソッドを順不同で呼び出すことは不可能であり、メソッドシグネチャは、メソッドの呼び出し方法に関する暗黙のドキュメントを提供します。ドキュメンテーションを見て、必要なオブジェクトを見て、逆に作業したことがありますか?オブジェクトZが必要ですが、Zを取得するにはYが必要です。Yを取得するには、Xが必要です。私はXを取得するために必要なWを持っています。これをすべてチェーンすると、Wを使用してZを取得できます。
  • このようにオブジェクトを分割すると、オブジェクトが不変になる可能性が高くなります。これには、この質問の範囲を超える多くの利点があります。簡単に言うと、不変オブジェクトはより安全なコードにつながる傾向があるということです。
5
user22815

私の観点からは、 クラス には「状態の値(メンバー変数)と動作の実装(メンバー関数、メソッド)」が含まれている必要があります。

「不幸なハイブリッドデータ構造」は、パブリックではないクラス状態のメンバー変数(またはそのゲッター/セッター)をパブリックにすると発生します。

そのため、データデータオブジェクトとワーカーオブジェクトに別々のクラスを用意する必要はありません。

状態メンバー変数を非公開に保つことができるはずです(データベース層は非公開メンバー変数を処理できる必要があります)

フィーチャー羨望は、別のクラスのメソッドを過度に使用するクラスです。 Code_smell を参照してください。メソッドと状態を持つクラスがあれば、これはなくなります。

0
k3b