単一責任原則を使用する場合、「責任」とは何ですか?


198

「単一責任の原則」が「1つのことだけを行う」ことを意味するものではないことは明らかです。それがメソッドの目的です。

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

ボブ・マーティンは、「クラスには変更する理由が1つだけあるべきだ」と言っています。しかし、SOLIDを初めて使用するプログラマーの場合、これを考えるのは困難です。

私は別の質問への回答を書きまし。そこでは、職責は役職のようなものであると提案し、レストランのメタファーを使って私の論点を説明することで主題について踊りました。しかし、それでも、クラスの責任を定義するために誰かが使用できる一連の原則は明確にされていません。

それでどうやってやるの?各クラスが持つべき責任をどのように決定し、SRPのコンテキストで責任をどのように定義しますか?


28
ポストコードレビューと:-Dを引き裂かれ
イェルクWミッターク

8
@JörgWMittagねえ、人々を怖がらせないでください:)
フランビーノ

118
退役軍人からのこのような質問は、私たちが守ろうとしている規則と原則が決して単純でも単純でもないことを示しています。彼らは自己矛盾して[の並べ替え]している神秘的なルールのいずれかの良いセットのように... なければならないこと。そして、私はこのような質問を賢明に謙虚に信じ、絶望的に愚かであると感じている人々に希望を与えたいです。ありがとう、ロバート!
svidgen

41
この質問は、noobによって投稿された場​​合、すぐにダウン投票され、重複とマークされていただろうか?)
Andrejs

9
@rmunn:または他の言葉で-誰もがstackexchangeに人間の基本的偏見をキャンセルしていないので、大きな担当者は、さらに多くの担当者を魅了
Andrejs

回答:


117

これを回避する1つの方法は、将来のプロジェクトで潜在的な要件の変更を想像し、それを実現するために何をする必要があるかを自問することです。

例えば:

新しいビジネス要件:カリフォルニアにいるユーザーには特別割引が適用されます。

「良い」変更の例:割引を計算するクラスのコードを変更する必要があります。

悪い変更の例:Userクラスのコードを変更する必要があります。その変更は、Userクラスを使用する他のクラス(登録、列挙、管理などの割引とは関係のないクラスを含む)にカスケード効果をもたらします。

または:

新しい非機能要件:SQL Serverの代わりにOracleの使用を開始します

適切な変更の例:DTOでデータを永続化する方法を決定するデータアクセスレイヤーの単一のクラスを変更するだけです。

悪い変更:SQL Server固有のロジックが含まれているため、ビジネスレイヤークラスをすべて変更する必要があります。

将来の潜在的な変更のフットプリントを最小限に抑え、変更領域ごとにコードの変更を1つのコード領域に制限するという考え方です。

最低限、クラスは論理的な懸念を物理的な懸念から分離する必要があります。例の偉大なセットがで見つけることができますSystem.IO名前空間:そこに我々は物理的なストリーム(例えば、各種見つけることができFileStreamMemoryStreamまたはNetworkStream)、様々なリーダーやライター(BinaryWriterTextWriter論理レベルで動作します)。代わりに、必要とする:それらをこのように分離することにより、我々はcombinatoric爆発を避けるためFileStreamTextWriterFileStreamBinaryWriterNetworkStreamTextWriterNetworkStreamBinaryWriterMemoryStreamTextWriter、そしてMemoryStreamBinaryWriterあなただけの作家やストリームをフックアップし、あなたが望むものを持つことができ、。その後XmlWriter、メモリ、ファイル、およびネットワーク用に個別に再実装する必要なく、たとえばを追加できます。


34
私は先を考えることに同意しますが、YAGNIのような原則があり、TDDのような方法論は逆を示唆しています。
ロバートハーヴェイ

87
YAGNIは、今日必要のないものを作らないようにと言っています。拡張可能な方法でものを構築しないように指示しません。また、「ソフトウェアエンティティ(クラス、モジュール、関数など)は拡張のために開かれ、修正のために閉じられる必要がある」というオープン/クローズド原則も参照してください。
ジョン・ウー

18
@JohnW:YAGNIコメントだけで+1。YAGNIは、変化に反応できない硬直した柔軟性のないシステムを構築する口実ではないことを人々に説明する必要があることを信じられません-皮肉なことに、SRPとOpen / Closedプリンシパルが目指しているものの正反対です
グレッグブルクハート

36
@JohnWu:同意しない、YAGNIは今日必要のないものを作らないように言っている。たとえば、読みやすさとテストは、プログラムが常に「今日」必要とするものなので、YAGNIは構造と注入ポイントを追加しない言い訳にはなりません。しかし、「拡張性」が「今日」の利点が明らかでない大きなコストを追加するとすぐに、YAGNIはこの種の拡張性を避けることを意味します。後者はオーバーエンジニアリングにつながるからです。
Doc Brown

9
@JohnWu SQL 2008から2012に切り替えました。変更が必要なクエリは合計2つありました。SQL Authから信頼できるものまで?なぜそれがコード変更になるのでしょうか。構成ファイルのconnectionStringを変更するだけで十分です。再び、ヤグニ。YAGNIとSRPは競合する懸念事項である場合があり、どちらがより優れた費用/利益を持っているかを判断する必要があります。
アンディ

76

実際には、責任は変化する可能性のあるものによって制限されます。したがって、残念ながら責任を構成するものに到達するための科学的または定式的な方法はありません。それは判断の呼び出しです。

それは、あなたの経験上、何が変わる可能性があるかについてです。

私たちは、原則の言語を、双曲的で、文字通り、熱心な怒りで適用する傾向があります。クラス変更される可能性があるため、または単に問題を解決するのに役立つように、クラスを分割する傾向があります。(後者の理由は本質的に悪いことではありません。)しかし、SRPはそれ自体のために存在しません。保守可能なソフトウェアの作成に役立っています。

繰り返しになりますが、部門がありそうな変更によって動かされていない場合、YAGNIがより適切であれば、それらは本当に SRP 1に役立っていません。どちらも同じ究極の目標を果たします。そして、どちらも判断の問題です。うまくいけば、経験豊かな判断です。

ボブおじさんがこれについて書いているとき、彼は、「誰が変更を求めているのか」という観点から「責任」を考えることを提案します。つまり、パーティBが変更を求めたため、パーティAが職を失うことは望ましくありません。

ソフトウェアモジュールを作成するとき、変更が要求されたときに、それらの変更は単一の人、または厳密に定義された単一のビジネス機能を表す単一の密結合グループからのみ発生することを確認する必要があります。組織全体の複雑さからモジュールを分離し、各モジュールがその1つのビジネス機能のニーズに対応する(応答する)ようにシステムを設計します。(ボブおじさん-単一責任の原則

優れた経験豊富な開発者には、変更が行われる可能性があるという感覚があります。そして、そのメンタルリストは、業界や組織によって多少異なります。

特定の組織での特定のアプリケーションの責任を構成するものは、最終的にベテランの判断の問題です。それは何が変わる可能性があるかについてです。そして、ある意味で、それはモジュールの内部ロジックの所有者に関するものです


1.明確にするために、それは彼らが悪い分裂であることを意味しません。それらは、コードの可読性を劇的に向上させる素晴らしい部門になる可能性があります。それは、彼らがSRPによって動かされていないことを意味します。


11
最良の答えであり、実際にはボブおじさんの考えを引用しています。変更される可能性のあるものについては、誰もがI / Oに関して「データベースを変更したらどうなるでしょうか?」または「XMLからJSONに切り替えるとどうなりますか?」これは通常、見当違いだと思います。本当の質問は、「このintをfloatに変更し、フィールドを追加し、この文字列を文字列のリストに変更する必要がある場合はどうでしょうか?」
user949300

2
これは不正行為です。単独の責任自体は、「変更の分離」の提案された方法にすぎません。責任を「単一」に保つために変更を分離する必要があることを説明することは、これを行う方法を示唆するものではなく、要件の起源を説明するだけです。
バシレフ

6
@Basilevsボブおじさんの答えは言うまでもなく、この答えであなたが見ている欠陥に目を向けようとしています!しかし、おそらく、SRPは「変更」が1つのクラスのみに影響を与えることを保証するものではないことを明確にする必要があります。各クラスが「1つの変更」にのみ応答するようにすることです。...それは、各クラスから単一の所有者に矢を引くことです。各所有者から単一のクラスへではありません。
svidgen

2
実用的な応答を提供していただきありがとうございます!ボブおじさんでさえ、アジャイルアーキテクチャの固い原則への熱心な順守に対して警告しています。引用符は手元にありませんが、基本的に、責任を分割するとコードの抽象化のレベルが本質的に増加し、すべての抽象化にはコストがかかるため、SRP(または他の原則)に従うメリットコストを上回ることを確認します抽象化を追加します。(次のコメントに続く)
マイケルL.

4
そのため、製品をできるだけ早く、合理的な範囲で顧客の前に配置する必要があるため、設計の変更が強制され、その製品でどの領域が変更される可能性あるかを確認できます。さらに、彼はあらゆる変化から身を守ることはできないと警告しています。以下のために任意のアプリケーション、変更の特定の種類は、作成が困難になります。これらの変更が発生する可能性が最も低いことを確認する必要があります。
マイケルL.

29

「クラスには、変更する理由が1つしかありません」に従う。

私にとって、これは、私の製品所有者が思いつくかもしれない厳しいスキームを考えることを意味します(「モバイルをサポートする必要がある!」、「クラウドに行く必要がある!」、「中国語をサポートする必要がある!」)。優れた設計は、これらのスキームの影響をより小さな領域に制限し、比較的簡単に達成できるようにします。悪い設計とは、多くのコードを作成し、多くの危険な変更を加えることを意味します。

これらのクレイジーなスキームの可能性を適切に評価できるのは経験だけです(1つを簡単にすると他の2つが難しくなる可能性があるため)。経験豊富なプログラマーは、コードを変更するために何をする必要があるか、ロバに噛み付くために横になっていること、物事を簡単にするトリックを想像できます。経験豊富なプログラマーは、製品の所有者がクレイジーなものを求めたとき、どれほど自分がねじ込まれているのかを直感的に感じます。

実際には、ここで単体テストが役立つことがわかります。コードに柔軟性がない場合、テストするのは難しくなります。モックやその他のテストデータを挿入できない場合、おそらくそのSupportChineseコードを挿入することはできないでしょう。

別の大まかな指標は、エレベーターのピッチです。従来のエレベーターピッチは「投資家と一緒にエレベーターに乗っていた場合、アイデアで彼を売ることができますか?」です。スタートアップは、彼らが何をしているのか、彼らの焦点が何であるのかについて、簡単で短い説明が必要です。同様に、クラス(および関数)には、それらが行うことの簡単な説明が必要です。「このクラスは、これらの特定のシナリオで使用できるようにfubarを実装しています」ではありません。別の開発者に伝えることができるもの:「このクラスはユーザーを作成します」。あなたが他の開発者にそれを通信できない場合、あなたはしているつもりはバグを取得します。


面倒な変更だと思っていたものを実装する場合がありますが、それは単純なことがわかります。または、小さなリファクタリングで簡単になり、同時に有用な機能が追加されます。しかし、はい、通常、あなたは来るトラブルを見ることができます。

16
私は「エレベーターピッチ」のアイデアを強く支持しています。クラスが何をしているのかを1つまたは2つの文で説明するのが難しい場合は、危険な領域にいます。
イヴァン

1
重要な点に触れます。これらのクレイジーなスキームの可能性は、プロジェクトの所有者によって劇的に異なります。一般的な経験だけでなく、プロジェクトの所有者をどれだけ知っているかに依存する必要があります。私はスプリントを1週間に短縮したい人のために働いてきましたが、それでもスプリントの途中で方向を変えることを避けられませんでした。
ケビンクルムウィーデ

1
明らかな利点に加えて、「エレベーターピッチ」を使用してコードを文書化することは、複数の責任を明らかにするのに役立つ自然言語を使用してコードが何をしているのかを考えるのにも役立ちます。
アレクサンダー

1
@KevinKrumwiedeそれが、「頭を切り落とした鶏が走り回る」と「野生のガチョウの追跡」の方法論の目的です!

26

誰も知らない。または、少なくとも、1つの定義に同意することはできません。それがSPR(およびその他のSOLID原則)を非常に物議を醸しているものです。

私は、ソフトウェア開発者が彼のキャリアの過程で学ばなければならないスキルの1つは、責任の有無を把握できることだと主張します。より多くのコードを作成してレビューすればするほど、何かが単一の責任なのか複数の責任なのかを判断するための経験が増えます。または、単一の責任がコードの別々の部分で分断されている場合。

私は、SRPの主な目的は厳格な規則ではないと主張します。それは、コードの結束性に留意し、どのコードが結束性であり、そうでないかを常に意識的に努力することを思い出させることです。


20
新しいプログラマーは、SOLIDを一連の法律であるかのように扱う傾向がありますが、そうではありません。これは、人々がクラスのデザインをより良くするための良いアイデアを集めたものです。残念ながら、人々はこれらの原則をあまりにも真剣に受けとめる傾向があります。私は最近、仕事の要件の1つとしてSOLIDを引用した求人を見つけました。
ロバートハーヴェイ

9
最後の段落では+42。@RobertHarveyが言うように、SPR、SOLID、YAGNIのようなものは「絶対的なルール」としてではなく、「良いアドバイス」の一般的な原則として受け取られるべきです。それら(および他の)の間でアドバイスは矛盾する場合がありますが、そのアドバイスのバランスをとることは(厳格な一連のルールに従うのではなく)(経験が大きくなるにつれて)より良いソフトウェアを作成するためのガイドになります。ソフトウェア開発には、「絶対ルール」は1つしかありません。「絶対ルールはありません」。
-TripeHound

これは、SRPの1つの側面に関する非常に優れた説明です。しかし、SOLIDの原則が厳密な規則ではない場合でも、その意味を誰も理解していない場合、それはそれほど価値がありません。...理解しにくいことは理にかなっています。任意のスキルと同様に、あります何かから良いを区別するあまり良いです!しかし...「だれも知らない」ことは、それをより厄介な儀式にします。(そして、それがソリッドの意図だとは思わない!)
svidgen

3
「誰も知らない」とは、@ Euphoric がすべてのユースケースで機能する正確な定義がないことを意味することを願っています。ある程度の判断が必要なものです。あなたの責任がどこにあるのかを判断する最良の方法の1つは、迅速に反復し、コードベースに伝えることだと思います。コードを簡単に保守できない「臭い」を探してください。たとえば、単一のビジネスルールへの変更が、一見無関係なクラスを通じて連鎖的な影響を与え始めると、おそらくSRPに違反していることになります。
マイケルL.

1
@TripeHoundと、これらすべての「ルール」が開発の真の宗教を定義するために存在するのではなく、保守可能なソフトウェアを開発する可能性を高めるために存在することを指摘した他の人たちに心から2番目です。あなたはそれが、保守性ソフトウェアを推進し、品質を向上させたり、開発の効率を向上させる方法を説明することはできません場合は、「ベストプラクティス」を、以下の非常に警戒する...
マイケル・L.

5

「責任」という用語は、ソフトウェアを使用して、ソフトウェアがどの程度適切に編成されているかを調査できるため、比phorとして役立つと思います。特に、2つの原則に焦点を当てます。

  • 責任は権限と釣り合っています。
  • 2つのエンティティが同じことに対して責任を負うべきではありません。

これらの2つの原則により、相互にプレーするため、責任を有意義に果たすことができます。コードの一部に何かをする権限を与えている場合、それが何をするのかについて責任を持つ必要があります。これにより、クラスが成長しなければならないという責任が生じ、「変更する1つの理由」がより広い範囲に拡大されます。ただし、物事を広くすると、当然、複数のエンティティが同じことを担当する状況に陥り始めます。これには現実の責任の問題がたくさんあるので、コーディングの問題でもあります。その結果、責任を重複していない区画に細分するため、この原則により範囲が狭くなります。

これら2つに加えて、3番目の原則は合理的なようです。

  • 責任を委任することができます

新しく作成されたプログラムを考えてみてください...白紙の状態。最初は、プログラム全体であるエンティティは1つだけです。それは...すべてに責任があります。当然、ある時点で、関数またはクラスに責任を委任し始めます。この時点で、最初の2つのルールが作用し、その責任のバランスをとることを強制します。マネージャーがチームの生産性を担当するのと同じように、トップレベルのプログラムは依然として全体的な出力を担当しますが、各サブエンティティには責任が委任されており、それによってその責任を遂行する権限があります。

追加のボーナスとして、これにより、SOLIDは必要な企業ソフトウェア開発と特に互換性があります。地球上のすべての企業は、責任を委任する方法についていくつかの概念を持っていますが、すべてが同意するわけではありません。自社の委任を思い起こさせる方法でソフトウェア内の責任を委任すると、将来の開発者がこの会社でのやり方に追いつくのがはるかに簡単になります。


これで完全に説明できるかどうかは100%わかりません。しかし、「権限」に関して「責任」を説明することは、それを表現する洞察に満ちた方法だと思います!(+1)
svidgen

Pirsig氏は、「問題をマシンに組み込む傾向がある」と言いました。

@nocomprendeまた、マシンにあなたの強みを組み込む傾向があります。あなたの長所と短所が同じものであるとき、それが面白くなるときだと私は主張します。
コートアンモン

5

、この会議イェール大学、アンクルボブはこの与え面白い例を:

ここに画像の説明を入力してください

彼は、Employee変更する3つの理由、変更要件の3つのソースがあり、このユーモラスで皮肉な説明をしますが、それでも実例となる説明を与えます:

  • CalcPay()メソッドにエラーがあり、会社に数百万米ドルの費用がかかる場合、CFOが解雇します

  • ReportHours()メソッドにエラーがあり、会社に数百万米ドルの費用がかかる場合、COOが解雇します

  • 場合WriteEmmployee()メソッドは、大量のデータの消去を引き起こし、US $の会社の何百万もの費用がかかる誤りがあり、CTOはあなたを発射します

したがって、同じクラスで3つの異なるCレベルのexecを実行すると、コストのかかるエラーが発生する可能性があるため、クラスの責任が多すぎます。

彼は、SRPの違反を解決するこのソリューションを提供しますが、ビデオには示されていないDIPの違反を解決する必要があります。

ここに画像の説明を入力してください


この例は、間違った責務を持つクラスのように見えます。
ロバートハーヴェイ

4
@RobertHarveyクラスの責任が多すぎる場合、余分な責任が間違った責任であることを意味します。
Tulainsコルドバ

5
あなたの言っていることは聞きますが、説得力がありません。あまりにも多くの責任を持つクラスと、まったくビジネスをしていない何かをしているクラスとの間には違いがあります。同じように聞こえるかもしれませんが、そうではありません。ピーナッツを数えることは、クルミと呼ぶのと同じではありません。それはボブおじさんの原則とボブおじさんの例ですが、もしそれが十分に説明的であれば、この質問はまったく必要ありません。
ロバートハーヴェイ

@RobertHarvey、違いは何ですか?これらの状況は私にとって同形のようです。
ポールドレーパー

3

「変更する理由」よりも物事を細分化する方法は、2つ(またはそれ以上)のアクションを実行する必要のあるコードが個別のオブジェクト参照を保持する必要があることを意味するかどうかを考えることから始めることだと思います各アクション、および1つのアクションは実行できるが他のアクションは実行できないパブリックオブジェクトがあると便利かどうか。

両方の質問に対する答えが「はい」の場合、アクションは別々のクラスで実行する必要があることを示唆します。両方の質問に対する答えが「いいえ」である場合、それは一般的な見地から1つのクラスがあるべきであることを示唆します。そのためのコードが扱いにくい場合、内部的にプライベートクラスに細分化される可能性があります。最初の質問への答えはノーであるが、2番目がyesの場合、各アクションの別のクラスがあるはずプラス他のインスタンスへの参照を含む複合クラス。

レジのキーパッド、ビープ音、数値読み取り、レシートプリンター、キャッシュドロワーに個別のクラスがあり、完全なレジの複合クラスがない場合、トランザクションを処理することになっているコードが誤って呼び出される可能性があります1台のマシンのキーパッドから入力を受け取り、2台目のマシンのビープ音からノイズを生成し、3台目のマシンのディスプレイに数字を表示し、4台目のマシンのプリンターにレシートを印刷し、5台目のマシンのキャッシュドロワーをポップする方法。これらのサブ関数はそれぞれ別のクラスで処理すると便利ですが、それらを結合する複合クラスも必要です。複合クラスは、可能な限り多くのロジックを構成クラスに委任する必要があります。

各クラスの「責任」は、実際のロジックを組み込むか、そうする他の複数のクラスに共通の接続ポイントを提供することのいずれかであると言えますが、重要なのは、クライアントコードがクラスを表示する方法に何よりも重点を置くことです。クライアントコードが何かを単一のオブジェクトと見なすことが理にかなっている場合、クライアントコードはそれを単一のオブジェクトと見なす必要があります。


これは適切なアドバイスです。SRPだけでなく、より多くの基準に従って責任を分割することを指摘する価値があるかもしれません。
ヨルゲンフォグ

1
車のアナロジー:他の誰かのタンクにどれだけのガスが入っているかを知る必要も、他の誰かのワイパーをオンにする必要もありません。(しかし、それはインターネットの定義です)(

1
@nocomprende-「他の誰かのタンクにどれだけのガスがあるかを知る必要はない」-あなたが家族の車のどれを次の旅行のために「借りる」かを決定しようとしているティーンエイジャーでなければ...;)
alephzero

3

SRPを正しく取得するのは困難です。ほとんどの場合、コードに「ジョブ」を割り当て、各部分に明確な責任があることを確認します。実際の生活のように、場合によっては作業を人に分割することは非常に自然なこともありますが、他の場合、特にそれら(または作業)を知らない場合は、本当に注意が必要です。

最初動作する単純なコードを作成してから、少しリファクタリングすることを常にお勧めします。しばらくすると、コードがどのように自然にクラスター化するかがわかるようになります。コード(または人々)と実行する作業を知る前に責任を強制するのは間違いだと思います。

あなたが気づく1つのことは、モジュールがあまりにも多くのことを開始し、デバッグ/メンテナンスが難しいときです。これがリファクタリングの瞬間です。コアジョブはどうあるべきか、別のモジュールにどのタスクを与えることができるのか?たとえば、セキュリティチェックとその他の作業を処理する必要がありますか、それとも最初に他の場所でセキュリティチェックを行う必要がありますか、またはこれによりコードがより複雑になりますか?

あまりにも多くのインダイレクションを使用すると、再び混乱になります...他の原則については、これはKISS、YAGNIなどのような他の原則と対立します。すべてがバランスの問題です。


SRPはコンスタンティンの凝集力だけではありませんか?
ニックケイリー

あなたは十分な長さコーディングする場合は、当然それらのパターンを見つけるだろうが、あなたはそれに名前を付けることにより、学習をスピードアップすることができ、それは...コミュニケーションに役立ちます
クリストフRoussy

@NickKeighley私はそれが結束であると思います、それほど大きく書かれていませんが、別の観点から見ました。
-sdenham

3

「単一の責任の原則」は、おそらく紛らわしい名前です。「変更する唯一の理由」は原則のより良い説明ですが、それでも誤解しやすいです。実行時にオブジェクトの状態が変化する原因については述べていません。開発者が将来コードを変更しなければならない原因について考えています。

バグを修正していない限り、変更はビジネス要件の新規または変更によるものです。コード自体の外側を考え、要件が独立して変化する原因となる外部要因を想像する必要があります。いう:

  • 税率は、政治的決定により変化します。
  • マーケティングは、すべての製品の名前を変更することにしました
  • UIはアクセス可能に再設計する必要があります
  • データベースが混雑しているため、いくつかの最適化を行う必要があります
  • モバイルアプリに対応する必要があります
  • 等々...

理想的には、独立した要因が異なるクラスに影響を与えるようにします。たとえば、税率は製品名とは無関係に変わるため、同じクラスに影響を与えるべきではありません。そうしないと、税制変更のリスクが発生し、製品の命名にエラーが発生します。これは、モジュラーシステムで回避したい種類の密結合です。

だから変更することができるものの焦点はありません- 何を考え、将来的に変更される可能性があります。独立して変化する可能性のあるものに焦点を当てます。通常、変更は異なるアクターによって引き起こされる場合、独立しています。

役職の例は正しい軌道に乗っていますが、より文字通りそれを取る必要があります!マーケティングがコードの変更を引き起こし、財務が他の変更を引き起こす可能性がある場合、これらの変更は同じコードに影響を与えるべきではありません。

この用語を発明したボブおじさんを引用するには

ソフトウェアモジュールを作成するとき、変更が要求されたときに、それらの変更は単一の人、または厳密に定義された単一のビジネス機能を表す単一の密結合グループからのみ発生することを確認する必要があります。組織全体の複雑さからモジュールを分離し、各モジュールがその1つのビジネス機能のニーズに対応する(応答する)ようにシステムを設計します。

要約すると、「責任」は単一のビジネス機能に対応することです。複数のアクターがクラスを変更する必要がある場合、クラスはおそらくこの原則を破っています。


彼の本「Clean Architecture」によると、これはまさに正しい。ビジネスルールは1つのソースから取得し、1つのソースからのみ取得する必要があります。これは、人事、運用、ITのすべてが協力して「単一の責任」で要件を策定する必要があることを意味します。そして、それが原則です。+1
ベニースコグバーグ

2

SOLIDプログラミングの原則を説明し、この原則に従うかどうかにかかわらずコードの例を提供する優れた記事は、https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-oriented-です。デザイン

SRPに関する例では、いくつかの形状クラス(円と正方形)と、複数の形状の合計面積を計算するために設計されたクラスの例を示しています。

彼の最初の例では、面積計算クラスを作成し、HTMLとして出力を返します。後で彼は代わりにJSONとして表示したいと決め、面積計算クラスを変更する必要があります。

この例の問題は、彼の面積計算クラスが形状の面積を計算し、その面積を表示することです。次に、エリアを表示するために特別に設計された別のクラスを使用して、これを行うためのより良い方法を説明します。

これは単純な例(およびコードスニペットがあるため記事を読む方が簡単に理解できます)ですが、SRPのコアアイデアを示しています。


0

まず第一に、あなたが持っているのは、実際には2つの別々の問題です。クラスにどのメソッドを入れるかという問題と、インターフェースの膨張の問題です。

インターフェース

このインターフェースがあります:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

おそらく、CustomerCRUDインターフェイスに準拠する複数のクラス(インターフェイスが不要な場合)とdo_crud(customer: CustomerCRUD)、準拠するオブジェクトを取り込む関数がいくつかあります。しかし、すでにSRPを破っています。これらの4つの異なる操作を結び付けています。

後で、データベースビューを操作するとします。データベースビューには、使用可能なメソッドのみReadあります。ただしdo_query_stuff(customer: ???)、本格的なテーブルまたはビューのいずれかを透過的に操作する関数を作成する必要があります。Read結局、メソッドのみを使用します。

インターフェースを作成する

public Interface CustomerReader {public Customer Read(customerID:int)}

CustomerCrudインターフェースを次のように考慮します。

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

しかし、終わりは見えません。作成できるが更新はできないオブジェクトがあるかもしれません。このウサギの穴は深すぎます。単一の責任原則を順守する唯一の正しい方法は、すべてのインターフェイスにメソッドを1つだけ含めることです。Goは実際に、私が見たものからこの方法論に従っており、インターフェイスの大部分は単一の機能を含んでいます。2つの関数を含むインターフェイスを指定する場合、2つを組み合わせた新しいインターフェイスを作成する必要があります。インターフェースの組み合わせが急増します。

この混乱から抜け出す方法は、インターフェイス(名目上のサブタイプの形式)ではなく、構造サブタイプ(OCamlなどで実装)を使用することです。インターフェイスを定義しません。代わりに、単に関数を書くことができます

let do_customer_stuff customer = customer.read ... customer.update ...

好きなメソッドを呼び出します。OCamlは型推論を使用して、これらのメソッドを実装するオブジェクトを渡すことができるかどうかを判断します。この例でcustomerは、type がであると判断されます<read: int -> unit, update: int -> unit, ...>

クラス

これにより、インターフェイスの混乱が解決されます。ただし、複数のメソッドを含むクラスを実装する必要があります。たとえば、2つの異なるクラスを作成する必要がCustomerReaderありCustomerWriterますか?テーブルの読み取り方法を変更したい場合(たとえば、データを取得する前に応答をredisにキャッシュする)が、現在はどのように書き込まれるのでしょうか。論理的結論までこの推論の連鎖をたどると、関数型プログラミングに密接につながります:)


4
「無意味」は少し強いです。「神秘的」または「禅」の背後に立つことができました。しかし、完全に無意味
svidgen

構造サブタイプが解決策である理由をもう少し説明できますか?
ロバートハーヴェイ

@RobertHarvey再編大幅に私の答え
gardenhead

4
実装するクラスが1つしかない場合でも、インターフェイスを使用します。どうして?単体テストでのモック。
Eternal21

0

私の考えでは、頭に浮かぶSRPに最も近いのは使用フローです。特定のクラスの使用フローが明確でない場合、おそらくクラスにデザインの匂いがあります。

使用フローは、指定されたメソッド呼び出しの継承であり、期待される(したがってテスト可能な)結果が得られます。基本的には、IMHOで得たユースケースを使用してクラスを定義します。そのため、すべてのプログラムの方法論は、実装よりもインターフェイスに焦点を当てています。


0

それは、複数の要件の変更を達成することであり、コンポーネントを変更する必要はありません

しかし、幸いなことに、最初にSOLIDについて聞いたとき、それが一目でわかります。


SRPYAGNI互いに矛盾する可能性があるというコメントがたくさんありますが、TDD (GOOS、ロンドンスクール)によって強制されたYAGNは、クライアントの観点からコンポーネントを考え、設計することを教えてくれました。私は、クライアントが何をしたいのか、それが何をするべきではないかによって、インターフェースの設計を始めました。そして、TDDの知識がなくても、この演習を行うことができます。

私は、ボブおじさんが説明したテクニックが好きです(悲しいことに、どこから思い出せないのですか)。

このクラスは何をするのでしょうか?

回答にAndまたはOrのいずれかが含まれていましたか

もしそうなら、答えのその部分を抽出し、それはそれ自身の責任です

この手法は絶対的なものであり、@ svidgenが言ったように、SRPは判断を促すものですが、新しいことを学ぶときは絶対的なものが最良であり、常に何かをする方が簡単です。分離しない理由を確認してください。経験に基づいた見積もりであり、方法がわからないからではありません。これは芸術であり、経験が必要です。


SRPについて話すとき、多くの答えがデカップリングの議論をしているように思えます。

SRPは、変更が依存関係グラフに伝播しないことを確認するものではありません。

理論的には、SRPがなければ、依存関係はありません...

1つの変更によってアプリケーションの多くの場所が変更されることはありませんが、そのための他の原則があります。ただし、SRPOpen Closed Principleを改善します。この原則は抽象化に関するものですより小さい抽象化は再実装が容易です。

したがって、SOLID全体を教えるときは、SRPを使用すると、要件の変更時に変更できるコード少なくなり、実際には新しいコードの作成が少なくなることに注意してください。


3
When learning something new, absolutes are the best, it is easier to just always do something.-私の経験では、新しいプログラマーは独断的すぎます。絶対主義は、思考力のない開発者と貨物カルトのプログラミングにつながります。「これをやるだけ」と言っても構いませんが、あなたが話している相手が後で教えたことを学ばなければならないことを理解している限りです。
ロバートハーヴェイ

@RobertHarvey、それは独断的な振る舞いを生み出すことは完全に真実であり、経験を積むにつれて学習直さなければなりません。これが私のポイントです。新しいプログラマーが判断を下す方法なしに判断呼び出しを行おうとすると、境界線が役に立たないように見えます。人々に無理をさせることにより、資格のない推測をする代わりに例外を探すように教えます。絶対主義についてのあなたの言ったことはすべて正しいです。だから、それは出発点にすぎないはずです。
クリスウォーラート

@RobertHarvey、簡単な実生活の例:子供に常に正直であることを教えるかもしれませんが、子供が成長するにつれて、人々はおそらく最も正直な考えを聞きたくないといういくつかの例外を認識するでしょう。5歳の人が正直であることについて正しい判断を下すと期待することは、せいぜい楽観的です。:)
クリスウォーラート

0

それに対する明確な答えはありません。質問は狭いですが、説明はそうではありません。

私にとっては、オッカムのかみそりのようなものです。現在のコードを測定しようとするのに理想的です。簡潔で単純な言葉で特定するのは困難です。もう1つのメタファーは、「1つのトピック」であり、「単一の責任」と同じくらい抽象的、つまり把握が困難です。3番目の説明は、「1レベルの抽象化を扱う」ことです。

それは実際にはどういう意味ですか?

最近、ほとんど2つのフェーズで構成されるコーディングスタイルを使用しています。

フェーズIは、創造的なカオスとして最もよく説明されます。このフェーズでは、考えが流れるようにコードを書きます。つまり、生でrawいです。

フェーズIIは完全に反対です。それはハリケーンの後に掃除するようなものです。これには最も多くの作業と規律が必要です。そして、デザイナーの観点からコードを見ていきます。

現在、主にPythonで作業しています。これにより、後でオブジェクトやクラスについて考えることができます。最初のフェーズI-関数のみを記述し、それらを異なるモジュールにほぼランダムに広げます。では、フェーズII、私は行く事を得た後、私は解決策のどの部分でどのようなモジュール取引をよく見ています。そして、モジュールをざっと見ていくうちに、トピックが浮かび上がってきます。一部の機能はテーマ的に関連しています。これらはクラスの良い候補です。そして、関数をクラスに変換した後-ほぼインデントとselfpythonのパラメーターリストへの追加で完了します;)- SRPOccamのRazorのように他のモジュールとクラスの機能を削除します。

現在の例は、先日小さなエクスポート機能を書くことです。

zip内にcsvexcelおよび結合されたexcelシートが必要でした。

単純な機能はそれぞれ3つのビュー(=機能)で行われました。各関数は、フィルターを決定するための一般的な方法と、データを取得するための2番目の方法を使用しました。次に、各機能でエクスポートの準備が行われ、サーバーからの応答として配信されました。

抽象化のレベルが多すぎます:

I)着信/発信のリクエスト/レスポンスの処理

II)フィルターの決定

III)データの取得

IV)データの変換

簡単なステップは、1つの抽象化(exporter)を使用して、最初のステップでレイヤーIIからIVを処理することでした。

残っているのは、リクエスト/レスポンスを扱うトピックのみです。同じレベルの抽象化では、要求パラメーター抽出しますが、これは問題ありません。ですから、私はこの見方のために「責任」を持ちました。

次に、エクスポーターを分割する必要がありました。これは、少なくとも3つの抽象レイヤーで構成されていました。

フィルター条件と実際の検索の決定は、ほぼ同じレベルの抽象化に基づいています(フィルターは、データの正しいサブセットを取得するために必要です)。これらのレベルは、データアクセスレイヤーのようなものに配置されました。

次のステップで、実際のエクスポートメカニズムを分解しました。一時ファイルへの書き込みが必要な場合、それを2つの「責任」に分割しました。

クラスとモジュールの形成に伴い、物事はより明確になり、どこに属していたのかが明らかになりました。そして、クラスがやり過ぎかどうか、常に潜在的な質問です。

各クラスが持つべき責任をどのように決定し、SRPのコンテキストで責任をどのように定義しますか?

従うべきレシピを与えるのは難しい。もちろん、私は不可解な»1レベルの抽象化«-それが助ければルールを繰り返すことができます。

私にとっては、それは現在のデザインにつながる一種の「芸術的直観」です。アーティストが粘土を彫ったり、絵を描いたりするようなコードをモデル化します。

コーディングボブロスとして私を想像してください;)


0

SRPに続くコードを記述するために私がやろうとすること:

  • 解決する必要がある特定の問題を選択してください。
  • それを解決するコードを書き、すべてを1つのメソッド(例:main)で書きます。
  • コードを慎重に分析し、ビジネスに基づいて、実行されているすべての操作で目に見える責任を定義しようとします(これは、ビジネス/プロジェクト/顧客にも依存する主観的な部分です)。
  • すべての機能が既に実装されていることに注意してください。次はコードの編成のみです(このアプローチでは今後、追加の機能やメカニズムは実装されません)。
  • 前のステップ(ビジネスと「変更する1つの理由」のアイデアに基づいて定義されている)で定義した責任に基づいて、それぞれに個別のクラスまたはメソッドを抽出します。
  • このアプローチはSPRのみを対象とすることに注意してください。理想的には、他の原則を順守しようとする追加のステップがあるはずです。

例:

問題:ユーザーから2つの数値を取得し、それらの合計を計算し、結果をユーザーに出力します。

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

次に、実行する必要があるタスクに基づいて責任を定義してみます。これから、適切なクラスを抽出します。

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

次に、リファクタリングされたプログラムは次のようになります。

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

注:この非常に単純な例では、SRPの原則のみを考慮しています。他の原則の使用(例:「L」-コードは、具体化ではなく抽象化に依存する必要があります)は、コードにより多くの利点をもたらし、ビジネスの変更に対してより保守しやすくなります。


1
あなたの例は単純すぎて、SRPを十分に説明できません。現実の世界では誰もこれをしません。
ロバートハーベイ

はい、実際のプロジェクトでは、私の例のように正確なコードを書くのではなく、いくつかの擬似コードを書きます。擬似コードの後、例で行ったように責任を分割しようとします。とにかく、それは私がそれをする方法です。
エマーソンカルドーソ

0

Robert C. Martinsの著書Clean Architecture:A Craftsman's Guide to Software Structure and Design(2017年9月10日発行)から、Robertは62ページに次のように書いています。

これまで、SRPは次のよ​​うに説明されてきました。

モジュールには、変更する理由が1つだけ必要です。

ソフトウェアシステムは、ユーザーと利害関係者を満足させるために変更されます。それらのユーザーと利害関係者「変化する理由」です。原則が話していること。実際、これを言うために原則を言い換えることができます。

モジュールは、1人のユーザーまたは利害関係者に対してのみ責任を負う必要があります

残念ながら、「ユーザー」と「利害関係者」という言葉は、ここで使用するのに適切な言葉ではありません。システムの健全な変更を望むユーザーまたは利害関係者が複数いる可能性があります。代わりに、私たちは本当にグループを指している-その変更を必要とする一人以上の人々。そのグループをアクターと呼びます。

したがって、SRPの最終バージョンは次のとおりです。

モジュールは、1つのアクターのみに責任を持つ必要があります。

したがって、これはコードに関するものではありません。SRPは、要件とビジネスニーズの流れを制御するものであり、1つのソースからのみ発生します。


「コードに関するものではない」とあなたが区別している理由はわかりません。もちろん、それはコードに関するものです。これはソフトウェア開発です。
ロバートハーヴェイ

@RobertHarvey私のポイントは、要件のフローが1つのソース、つまりアクターから来るということです。ユーザーと利害関係者はコードではなく、要件として私たちにやってくるビジネスルールです。したがって、SRPはこれらの要件を制御するプロセスであり、私にとってはコードではありません。ソフトウェア開発(!)ですが、コードではありません。
ベニースコグバーグ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.