web-dev-qa-db-ja.com

単一責任の原則-コードの断片化を回避するにはどうすればよいですか?

私は、チームリーダーがSOLID開発原則の猛烈な擁護者であるチームに取り組んでいます。しかし、彼は複雑なソフトウェアを戸外に出した経験が多くありません。

私たちは、彼がすでに非常に複雑なコードベースであったものにSRPを適用した状況にあります。これは現在、非常に断片化されており、理解とデバッグが困難になっています。

プライベートまたは保護されている可能性のあるクラス内のメソッドが「変更する理由」を表すと判断され、パブリックまたは内部クラスおよびインターフェースに抽出されたため、コードの断片化だけでなくカプセル化にも問題が発生しました。アプリケーションのカプセル化の目標を満たしていません。

20を超えるインターフェイスパラメーターを取るクラスコンストラクターがいくつかあるので、IoCの登録と解決はそれ自体がモンスターになっています。

これらの問題のいくつかを修正するために使用できる「SRPからのリファクタリング」アプローチがあるかどうかを知りたいです。 SOLIDに違反していないことを確認しました。多数の密接に関連するクラスを「ラップ」して多数の空の粗粒度クラスを作成し、それらの機能の合計(つまり、あまりSRPされていないクラス実装を模倣している)。

それとは別に、みんなを幸せにしながら、実際に開発作業を続行できるソリューションは考えられません。

助言がありますか ?

59
Dean Chalk

クラスのコンストラクターに20個のパラメーターがある場合、チームがSRPが何であるかを十分に理解しているようには思えません。 1つのことだけを行うクラスがある場合、どのようにして20の依存関係がありますか?それは釣り旅行に行って、釣り竿、タックルボックス、キルティング用品、ボウリングボール、ヌンチャク、火炎放射器などを持って行くようなものです。釣りに行くために必要なものがすべてあれば、釣りに行くだけではありません。

そうは言っても、そこにあるほとんどの原則と同様に、SRPは過剰に適用される可能性があります。整数をインクリメントするための新しいクラスを作成する場合、そうです、それは単一の責任であるかもしれませんが、実行されます。それはばかげています。 SOLID原則は目的のために存在することを忘れがちです。SOLIDは目的そのものではなく目的のための手段です。目的はmaintainabilityです。単一責任原則でこれを詳細に取得する場合は、SOLIDへの熱意がチームをSOLIDの目標に向けて盲目にしていることを示しています。

だから、私が言っていることは... SRPはあなたの問題ではありません。これは、SRPの誤解か、SRPの非常にきめ細かいアプリケーションです。チームが主要なものを主要なものに保つようにしてください。そして主なものは保守性です。

編集

使いやすさを促進する方法でモジュールを設計してもらいます。各クラスをミニAPIと考えてください。最初に「このクラスをどのように使用したいのか」を考えてから、実装してください。 「このクラスは何をする必要があるのか​​」と考えてはいけません。 SRPは、クラスを使いにくくする大きな傾向があります。if使いやすさをあまり考慮していません。

編集2

リファクタリングのヒントを探しているなら、あなたは提案したことを始めることができます-より粗いクラスを作成して他のいくつかをラップします。 より粗いクラスがまだSRPに準拠しているが、より高いレベルにあることを確認してください。次に、2つの選択肢があります。

  1. より細かいクラスがシステムの他の場所で使用されなくなった場合は、徐々にその実装をより粗いクラスにプルして削除できます。
  2. きめの細かいクラスはそのままにしておきます。おそらく、それらはうまく設計されていて、使いやすくするためのラッパーが必要だったのでしょう。これはあなたのプロジェクトの多くに当てはまるのではないかと思います。

リファクタリングが完了したら(ただし、リポジトリにコミットする前に)、作業を確認し、リファクタリングが実際に保守性と使いやすさの改善であったかどうかを自問してください。

87
Phil

Martin Fowlerのリファクタリングで、SRPのカウンタールールを読んで、行き過ぎている場所を定義したと思います。 「すべてのクラスに変更する理由が1つしかないのか」と同じくらい重要な2番目の質問があります。それは「すべての変更が1つのクラスにのみ影響するか」ということです。

最初の質問への回答がすべての場合に「はい」であるが、2番目の質問が「あまり近くない」場合、SRPの実装方法をもう一度確認する必要があります。

たとえば、テーブルにフィールドを1つ追加すると、DTOとバリデータークラス、永続性クラスとビューモデルオブジェクトなどを変更する必要がある場合、問題が発生します。たぶん、SRPの実装方法を再考する必要があります。

おそらく、フィールドの追加がCustomerオブジェクトを変更する理由であると言いましたが、永続性レイヤーの変更(たとえば、XMLファイルからデータベースへ)は、Customerオブジェクトを変更するもう1つの理由です。したがって、CustomerPersistenceオブジェクトも作成することにしました。しかし、フィールドSTILLを追加するためにCustomerPersisitenceオブジェクトの変更が必要になるようにそれを行う場合、何がポイントでしたか?変更する理由が2つあるオブジェクトがまだあります-それはもはや顧客ではありません。

ただし、ORMを導入すると、DTOにフィールドを追加すると、そのデータの読み取りに使用されるSQLが自動的に変更されるように、クラスを機能させることができる可能性があります。次に、2つの懸念を分離する十分な理由があります。

要約すると、ここに私がする傾向があります:「いいえ、このオブジェクトを変更する理由は複数あります」と私が言う回数の間に大まかなバランスがある場合、「いいえ、この変更は複数のオブジェクトに影響を与える」とすると、SRPとフラグメント化のバランスが取れていると思います。しかし、どちらもまだ高い場合は、懸念を分離する別の方法があるかどうか疑問に思い始めます。

33
pdr

システムが complexだからといって、システムを複雑にする必要があるわけではありません 。次のような依存関係(またはコラボレーター)が多すぎるクラスがある場合:

_public class MyAwesomeClass {
    public class MyAwesomeClass(IDependency1 _d1, IDependency2 _d2, ... , IDependency20 _d20) {
      // Assign it all
    }
}
_

...それでは複雑すぎて、実際に [〜#〜] srp [〜#〜] をフォローしていませんか? MyAwesomeClassCRCカードに書かれていることを書き留めておいてください インデックスカードに適合しないか、非常に小さな判読できない文字で書かなければなりません。

あなたがここに持っているのは、あなたの連中が代わりに Interface Segregation Principle だけに従っていて、それを極端にしたかもしれないということですが、それはまったく別の話です。依存関係はドメインオブジェクト(発生する)であると主張することもできますが、同時に20のドメインオブジェクトを処理するクラスがあると、少し拡張しすぎます。

TDDは、クラスの実行量を示す適切な指標を提供します。ぼんやりと;テストメソッドに(テストをリファクタリングした場合でも)作成に永久にかかるセットアップコードがある場合、MyAwesomeClassはおそらく実行することが多すぎます。

それでは、この難問をどのように解決しますか? 責任を他のクラスに移します。この問題があるクラスで実行できるいくつかの手順があります。

  1. クラスが依存関係に対して行うすべてのアクション(または責任)を特定します。
  2. 密接に関連する依存関係に従ってアクションをグループ化します。
  3. Redelegate!つまり識別された各アクションを、新しいクラスまたは(さらに重要なことには)他のクラスにリファクタリングします。

リファクタリングの責任に関する抽象的な例

Cを、使用量を減らすためにリファクタリングする必要のある_D1_、_D2_、_D3_、_D4_のいくつかの依存関係を持つクラスとします。 Cが依存関係を呼び出すメソッドを特定したら、その単純なリストを作成できます。

  • _D1_-performA(D2)performB()
  • _D2_-performD(D1)
  • _D3_-performE()
  • _D4_-performF(D3)

リストを見ると、_D1_と_D2_は、クラスが何らかの形で一緒に必要としているため、互いに関連していることがわかります。 _D4_には_D3_が必要であることもわかります。したがって、2つのグループがあります。

  • _Group 1_-_D1_ <-> _D2_
  • _Group 2_-_D4_-> _D3_

グループ化は、クラスに2つの責任があることを示しています。

  1. _Group 1_-相互に必要な2つのオブジェクトの呼び出しを処理するためのもの。クラスCに両方の依存関係を処理する必要性をなくさせ、代わりにそれらの1つをそれらの呼び出しを処理させることができるかもしれません。このグループ化では、_D1_が_D2_への参照を持つことができるのは明らかです。
  2. _Group 2_-他の責任では、あるオブジェクトが別のオブジェクトを呼び出す必要があります。クラスの代わりに_D4_が_D3_を処理できないのですか?次に、代わりに_D3_に呼び出しを行わせることにより、クラスCから_D4_を削除できます。

例は非常に抽象的なものであり、多くの仮定を行っているため、私の答えを堅固に解釈しないでください。これをリファクタリングする方法は他にもあると私は確信していますが、少なくともこの手順は、クラスを分割するのではなく、ある種のプロセスが責任を移動するのに役立つ場合があります。


編集:

コメントの中で @ Emmad Karem さんのコメント:

「クラスにコンストラクター内に20個のパラメーターがある場合、チームがSRPとは何かを十分に理解しているようには思えません。1つのことだけを行うクラスがある場合、どのようにして20個の依存関係がありますか?」- Customerクラスがある場合、コンストラクターに20のパラメーターがあることは不思議ではありません。

DAOオブジェクトには多くのパラメーターがあり、コンストラクターで設定する必要があることは事実です。パラメーターは通常、文字列などの単純な型です。ただし、Customerクラスの例では、他のクラス内でそのプロパティをグループ化して、物事を簡単にすることができます。たとえば、ストリートを含むAddressクラスや、郵便番号を含み、データ検証などのビジネスロジックも処理するZipcodeクラスがあるとします。

_public class Address {
    private String street1;
    //...

    private Zipcode zipcode;

    // easy to extend
    public bool isValid() {
        return zipcode.isValid();
    }
}

public class Zipcode {
    private string zipcode;
    public bool isValid() {
        // return regex match that zipcode contains numbers
    }
}
_

このことは、ブログ投稿 でさらに説明されています。「決して、決して、決してJava(または少なくとも頻繁に)で文字列を使用しないでください」 " 。コンストラクターまたは静的メソッドを使用してサブオブジェクトを作成しやすくするには、 fluid builder pattern を使用できます。

24
Spoike

答えは、何よりも保守性とコードの明確さです。私にとってそれはコードが少ないを書くことを意味し、それ以上ではありません。抽象化、インターフェース、オプション、パラメーターが少なくなります。

コードの再構築を評価するとき、または新しい機能を追加するときはいつでも、実際のロジックと比較してどれだけのボイラープレートが必要になるかについて考えます。答えが50%を超えている場合、おそらく私はそれを考えすぎていることを意味します。

SRPに加えて、他にも多くの開発スタイルがあります。あなたの場合、YAGNIのようなサウンドは間違いなく欠けています。

3
cmcginty

ここでの答えの多くは本当に良いですが、この問題の技術的な側面に焦点を当てています。開発者が実際にSRPに違反しているようにSRPサウンドを追跡しようとしているように聞こえることを簡単に付け加えます。

この状況についてボブのブログ here を見ることができますが、責任が複数のクラスにまたがっていると、それらのクラスが並行して変更されるため、責任SRPに違反していると彼は主張します。私はあなたの開発者がボブのブログの一番上にあるデザインを本当に望んでいると思います、そしてそれがバラバラになっているのを見るには少しがっかりするかもしれません。特に、「共通の閉鎖原則」に違反しているため、一緒に変化するものは一緒にとどまります。

SRPは「変更の理由」を指し、「1つのことを行う」のではなく、実際に変更が発生するまで変更の理由を気にする必要がないことに注意してください。 2番目の男は抽象化の費用を負担します。

ここで、2番目の問題があります。「SOLID developmentの激しい擁護者です。」コードベースの問題は困惑しています。関係を修復して、問題について実際に話し合う必要があります。私がお勧めするのはビールです。

真剣に-あなたが飲まない場合は、コーヒーショップに向かいます。オフィスから出て、どこかリラックスして、このことについて非公式に話すことができます。あなたがそうしないであろう会議で議論を勝ち取ろうとするのではなく、どこか楽しい議論をします。あなたを狂わせているこの開発者が、ソフトウェアを「戸外」に出そうとし、がらくたを出荷したくない、実際に機能している人間であることを認識してください。その共通点を共有している可能性が高いので、SRPに準拠しながらデザインを改善する方法について話し始めることができます。

SRPが良いことであり、アスペクトの解釈が異なることを両方とも認識できる場合は、生産的な会話を始めることができます。

3
Eric Smith

私はSRPに関するすべての回答と、SRPがあまりにも遠くにある可能性があることに同意します。あなたの投稿では、SRPに準拠するための「オーバーリファクタリング」により、カプセル化が壊れている、または変更されていることがわかりました。私にとってうまくいったことの1つは、常に基本に忠実であり、目的を達成するために必要なことを正確に実行することです。

レガシーシステムを使用する場合、すべてを修正して改善するための「熱意」は、通常、チームリーダー、特にその役割にとって初めてのチームリーダーではかなり高くなります。 SOLID、SRPがないだけ-それはSです。SOLIDをフォローしている場合は、OLIDも忘れないようにしてください。

私は今、レガシーシステムに取り組んでおり、最初から同様の道を歩み始めました。私たちのために働いたのは集団の決定両方の世界を最大限に活用することでした= SOLIDとKISS(Keep It Simple Stupid)。コードへの主要な変更をまとめて議論しましたさまざまな開発原則を適用する際の構造と適用された常識。「S/W開発の法則)ではなく、ガイドラインとして優れています。チームはチームリードだけではありません。すべての開発者についてです。私にとって常にうまくいったことは、全員を部屋に入れ、チーム全体が従うことに同意したガイドラインの共有セットを考え出すことです。

現在の状況を修正する方法については、VCSを使用していて、アプリケーションにあまり多くの新機能を追加していない場合は、チーム全体が理解可能、読み取り可能、保守可能であると考えるコードのバージョンにいつでも戻ることができます。はい!仕事を捨ててゼロから始めるようお願いします。これは、壊れたものを「修正」して、すでに存在しているものに戻そうとするよりも優れています。

3
Sharath Satish