各クラスの責任が1つだけであることを確認してください、なぜですか?


37

Microsoftのドキュメント、WikipediaのSOLIDプリンシペ記事、またはほとんどのITアーキテクトによると、各クラスに1つの責任のみを持たせる必要があります。誰もがこの規則に同意するように見える場合、誰もこの規則の理由について同意するように思われないので、私はその理由を知りたいです。

メンテナンスの改善を挙げている人もいれば、簡単なテストを提供したり、クラスをより堅牢にしたり、セキュリティを強化したりする人もいます。何が正しいのか、実際にはどういう意味ですか?なぜメンテナンスが改善され、テストが簡単になり、コードがより堅牢になるのですか?




3
単一の責任のすべての理由を正しくすることはできませんか?メンテナンスが簡単になります。テストが簡単になります。(一般的に)クラスをより堅牢にします。
マーティンヨーク14

2
同じ理由で、クラスがすべてあります。
DavorŽdralo

2
私はいつもこの声明に問題がありました。「単一責任」とは何かを定義するのは非常に困難です。単一の責任は、「1 + 1 = 2を検証する」から「企業の銀行口座に出入りするすべてのお金の正確なログを維持する」までの範囲になります。
デイブネイ14

回答:


57

モジュール性。適切な言語であれば、コードをつなぎ合わせる手段を提供しますが、プログラマーがソースで手術を行わずに大きなコードをつなぎとめる一般的な方法はありません。多くのタスクを1つのコード構造に詰め込むことで、自分自身と他の人がその部分を他の方法で結合する機会を奪い、1つの部分の変更が他の部分に影響を与える不必要な依存関係を導入します。

SRPはクラスと同じように関数にも適用できますが、主流のOOP言語は関数を結合するのが比較的苦手です。


12
抽象化を作成するより安全な方法として関数型プログラミングに言及するための+1。
logc 14

>しかし、主流のOOP言語は、関数を一緒に接着するのが比較的苦手です。
ジェフハバード14

@JeffHubbardあなたはそれが彼らがC / C ++の後をとるからだと思うと思う 関数と相互に排他的にするオブジェクトについては何もありません。地獄、オブジェクト/インターフェイスは単なる関数の記録/構造です。関数が重要ではないふりをすることは、理論と実践の両方で保証されません。とにかく回避することはできません-「Runnables」、「Factories」、「Providers」、および「Actions」を回避するか、いわゆる「Strategy and Template Method」の「パターン」を使用しなければなりません。同じ仕事を成し遂げるための不必要なボイラープレートの束。
ドーバル14

@Dovalいいえ、OOPはメッセージに関係しているということです。それは、関数を一緒に接着することにおいて、与えられた言語が関数型プログラミング言語よりも優れている、または劣っていると言うことではありません- それは言語の主要な関心事ではないというだけです
ジェフハバード14

基本的に、あなたの言語がOOPをより簡単/より良い/より速く/より強くすることである場合、関数がうまく結びつくかどうかはあなたが焦点を合わせているものではありません。
ジェフハバード14

30

メンテナンスの改善、テストの容易化、バグ修正の迅速化は、SRPを適用した結果として得られるものです。主な理由(ロバートC.マティンが言うように)は:

クラスには、変更する理由が1つだけ必要です。

言い換えれば、SRPは変更局所性を発生させます

SRPはDRYコードも促進します。責任が1つだけのクラスがある限り、好きな場所で使用することを選択できます。2つの責務を持つクラスがあり、そのうちの1つだけが必要であり、2番目が干渉している場合、2つの選択肢があります。

  1. クラスを別のクラスにコピーして貼り付け、多分別の多責任ミュータントを作成することもできます(技術的な負債を少し増やします)。
  2. クラスを分割して、そもそも本来あるべきようにします。これは、元のクラスを広範囲に使用するために費用がかかる場合があります。

1
+1 SRPをDRYにリンクします。
マフディ14

21

特定の問題を修正するコードを簡単に作成できます。その問題を修正するコードを作成することはより複雑であり、後の変更を安全に行うことができます。SOLIDは、コードを改善する一連のプラクティスを提供します。

どちらが正しいか:3つすべて。これらはすべて、単一の責任を使用する利点と、それを使用する必要がある理由です。

それらが意味することに関して:

  • メンテナンスの改善とは、変更が容易であり、頻繁に変更されないことを意味します。コードが少なく、そのコードは特定の何かに焦点を合わせているため、クラスに関連しないものを変更する必要がある場合、クラスを変更する必要はありません。さらに、クラスを変更する必要がある場合、パブリックインターフェイスを変更する必要がない限り、そのクラスのみを心配する必要があります。
  • 簡単なテストとは、テストとセットアップが少ないことを意味します。クラスには可動部品がそれほど多くないため、クラスで発生する可能性のある障害の数は少ないため、テストする必要があるケースは少なくなります。設定するプライベートフィールド/メンバーは少なくなります。
  • 上記の2つにより、変化が少なく失敗の少ないクラスが得られるため、より堅牢になります。

原則に従ってしばらくの間コードを作成し、そのコードを後で再確認して変更を加えてみてください。あなたはそれが提供する大きな利点を見るでしょう。

各クラスに対して同じことを行い、最終的にすべてのクラスがSRPに準拠するようになります。接続の観点から構造がより複雑になりますが、各クラスの単純さが正当化されます。


ここで指摘されたポイントが正当化されるとは思わない。特に、クラスを単純化すると他のクラスがより複雑になり、コードベース全体に実質的な影響を与えないゼロサムゲームをどのように回避できますか?@Dovalの答えがこの問題に対処していると思います。

2
すべてのクラスに同じことを行います。すべてのクラスがSRPに準拠して終了します。接続の観点から構造がより複雑になります。しかし、各クラスのシンプルさが正当化されます。しかし、@ Dovalの答えが好きです。
宮本明14

それをあなたの答えに加えるべきだと思います。

4

私の考えでは、単一責任の原則は良い習慣であるという主張を支持する議論はここにあります。さらに詳細な推論を読むことができるさらなる文献へのリンクも提供します-私のものより雄弁です:

  • メンテナンスの改善:理想的には、システムの機能を変更する必要があるときはいつでも、変更する必要があるクラスは1つだけになります。クラスと責任の間の明確なマッピングは、プロジェクトに関与する開発者がこれがどのクラスであるかを識別できることを意味します。(@MaciejChałapukが指摘したように、Robert C. Martinの「Clean Code」を参照してください。)

  • より簡単なテスト:理想的には、クラスはできるだけ最小限のパブリックインターフェイスを備え、テストはこのパブリックインターフェイスのみに対応する必要があります。クラスの多くの部分がプライベートであるために明確にテストできない場合、これはクラスの責任が多すぎること、およびそれをより小さなサブクラスに分割する必要があることの明確な兆候です。これは、「パブリック」または「プライベート」クラスのメンバーがいない言語にも適用されることに注意してください。小さなパブリックインターフェイスがあるということは、クラスのどの部分を使用することになっているのか、クライアントコードに対して非常に明確であることを意味します。(詳細については、Kent Beckの「テスト駆動開発」を参照してください。)

  • 堅牢なコード:コードは適切に記述されているため、多かれ少なかれ失敗することはありません。しかし、すべてのコードと同様に、最終的な目標はマシンと通信することではなく、仲間の開発者と通信することです(Kent Beck、 "Implementation Patterns"、Chapter 1を参照してください)。 、バグを発見してから修正するまでの時間が短くなります。


ビジネス上の理由で何かを変更する必要がある場合は、SRPで私を信じて、複数のクラスを変更する必要があります。クラスが変更された場合、それが唯一の理由であると言うことは、変更があった場合、それは1つのクラスにのみ影響するということとは異なります。
バスティアンヴァンダム14

B413 @:私はことを意味するものではありませんでした任意の変更は、単一のクラスへの単一の変更を意味します。システムの多くの部分では、単一のビジネスの変更に対応するために変更が必要になる場合があります。しかし、多分あなたにはポイントがあり、「システムの機能を変更しなければならないときはいつでも」書くべきだった。
logc 14

3

いくつかの理由がありますが、私が気に入っているのは、初期のUNIXプログラムの多くで使用されているアプローチです。一つのことでそれを行うのは十分に困難であり、あなたがやろうとすることは難しくなります。

別の理由は、副作用を制限および制御することです。私はコンビネーションコーヒーメーカーのドアオープナーが大好きでした。残念ながら、訪問者がいたとき、コーヒーは通常あふれていました。先日コーヒーを飲んで、誰かがそれを盗んだ後、私はドアを閉めるのを忘れました。

心理的な観点からは、一度にいくつかのことしか追跡できません。一般的な推定値は7プラスまたはマイナス2です。クラスが複数のことを行う場合、それらすべてを一度に追跡する必要があります。これにより、実行していることを追跡する機能が低下します。クラスが3つのことを行い、そのうちの1つだけが必要な場合、クラスで実際に何かをする前に、物事を追跡する能力を使い果たす可能性があります。

複数のことを行うと、コードが複雑になります。最も単純なコードを超えて、複雑さが増すとバグが発生する可能性が高くなります。この観点から、クラスをできるだけシンプルにする必要があります。

1つのことを行うクラスのテストは、はるかに簡単です。クラスが行う2番目のことは、すべてのテストで行われたか、行われなかったかを確認する必要はありません。また、これらのテストの1つが失敗した場合、壊れた状態を修正して再テストする必要もありません。


2

ソフトウェアはオーガニックだからです。要件は常に変化するため、できるだけ頭痛の少ないコンポーネントを操作する必要があります。固い原則に従わない場合、具体的に設定されたコードベースになる可能性があります。

耐荷重コンクリート壁のある家を想像してください。サポートなしでこの壁を取り出したらどうなりますか?家はおそらく崩壊するでしょう。ソフトウェアではこれを望まないため、多くの損傷を与えることなくコンポーネントを簡単に移動/交換/修正できるようにアプリケーションを構築します。


1

私は考えに従います:1 Class = 1 Job

生理学的アナロジーの使用:運動(神経系)、呼吸(肺)、消化器(胃)、嗅覚(観察)など。これらのそれぞれにコントローラーのサブセットがありますが、管理するかどうかに関係なく、それぞれ1つの責任しかありません。それぞれのサブシステムが機能する方法、またはエンドポイントサブシステムであり、指を持ち上げる、毛包を成長させるなどの1つのタスクのみを実行するかどうか。

ワーカーではなくマネージャーとして機能している可能性があるという事実を混同しないでください。一部のワーカーは、1つのプロセスだけでは処理できないほど複雑になったときに、最終的にマネージャーに昇格します。

私が経験したことの中で最も複雑な部分は、クラスをオペレーター、スーパーバイザー、またはマネージャープロセスとして指定するタイミングを知ることです。とにかく、1つの責任(オペレーター、スーパーバイザー、またはマネージャー)でその機能を観察し、示す必要があります。

クラス/オブジェクトがこれらのタイプの役割を複数実行すると、プロセス全体でパフォーマンスの問題が発生したり、ボトルネックが発生したりすることがわかります。


大気中の酸素をヘモグロビンに処理する実装をLungクラスとして処理する必要がある場合PersonでもBreathe、のインスタンスはまだであると仮定します。したがって、Personクラスには、少なくとも、そのメソッドに関連する責任を委任するのに十分なロジックが含まれている必要があります。さらに、共通の所有者からのみアクセスできる相互接続されたエンティティのフォレストは、複数の独立したアクセスポイントを持つフォレストよりも推論が容易な場合が多いことをさらにお勧めします。
supercat

はい、その場合、肺はマネージャーですが、Villiはスーパーバイザーになり、呼吸のプロセスは、空気粒子を血流に転送するWorkerクラスになります。
GoldBishop

@GoldBishop:おそらく正しい見方は、Personクラスがその仕事として、さまざまな部分の関連付けを含む、非常に複雑な「実世界の人間」エンティティに関連付けられたすべての機能の委任を持っていると言うことでしょう。エンティティに500のことをさせるのはいですが、それがモデル化されている現実のシステムの動作方法である場合、1つのIDを保持しながら500の機能を委任するクラスを持つことは、ばらばらの部分ですべてを行うよりも良いかもしれません。
supercat

@supercatまたは、単純に全体をコンパートメント化し、サブプロセスで必要なスーパーバイザーのクラスを1つ持つことができます。そうすれば、(理論的には)各プロセスを基本クラスから分離させることができますがPerson、成功/失敗の連鎖を報告することはできますが、必ずしも他に影響を与えるわけではありません。500関数(例として設定されていますが、オーバーボードであり、サポートされません)私は、そのタイプの機能を派生し、モジュール化したままにします。
GoldBishop

@GoldBishop:「一時的な」型を宣言する方法があればいいのに、外部のコードがその上でメンバーを呼び出したり、型として独自のメソッドにパラメーターとして渡すことができますが、それ以外は何もしません。コードの組織の観点から、細分化したクラスは、理にかなっている、とさえ1つのレベルダウンは(例えばメソッド呼び出し外のコード持つFred.vision.lookAt(George)意味をなすのが、言うにコードを許可するsomeEyes = Fred.vision; someTubes = Fred.digestionことが関係不明瞭ので、不快なようだsomeEyessomeTubes
supercat

1

特に単一責任などの重要な原則については、人々がこの原則を採用する多くの理由があると個人的に期待しています。

これらの理由のいくつかは次のとおりです。

  • メンテナンス-SRPは、1つのクラスの責任の変更が他の責任に影響を与えないようにし、メンテナンスを簡素化します。これは、各クラスに1つの責任しかない場合、1つの責任に加えられた変更が他の責任から分離されるためです。
  • テスト-クラスに1つの責任がある場合、この責任をテストする方法を見つけやすくなります。クラスに複数の責任がある場合、正しいものをテストしていることと、クラスが持つ他の責任の影響を受けないことを確認する必要があります。

また、SRPにはSOLID原則がバンドルされていることに注意してください。SRPを順守し、残りを無視することは、そもそもSRPを行わないことと同じくらい悪いことです。したがって、SRPを単独で評価するのではなく、すべてのSOLID原則のコンテキストで評価する必要があります。


... test is not affected by other responsibilities the class has、これについて詳しく説明していただけますか?
マフディ14

1

これらの原則の重要性を理解する最良の方法は、必要性を持つことです。

私が初心者プログラマーだったとき、私はデザインについてあまり考えませんでした。実際、デザインパターンが存在することすら知りませんでした。私のプログラムが成長するにつれて、一つのことを変えることは他の多くのことを変えることを意味しました。バグを追跡するのは難しく、コードは巨大で反復的でした。オブジェクト階層はあまりなく、物事はいたるところにありました。 新しいものを追加したり、古いものを削除すると、プログラムの他の部分でエラーが発生します。図を移動します。

小さなプロジェクトでは問題になりませんが、大きなプロジェクトでは非常に悪夢のようなものになります。後にデザインパターンの概念に出くわしたとき、私は「ああ、そうすれば物事がずっと簡単になります」と自分に言いました。

必要が生じるまで、設計パターンの重要性を本当に理解することはできません。経験から、コードのメンテナンスが容易でコードが堅牢になると言えるので、パターンを尊重します。

しかし、あなたと同じように、私はまだ「簡単なテスト」については不確かです。なぜなら、私はまだ単体テストに入る必要がなかったからです。


パターンは何らかの形でSRPに接続されていますが、デザインパターンを適用する場合、SRPは必要ありません。パターンを使用して、SRPを完全に無視することができます。非機能要件に対処するためにパターンを使用しますが、どのプログラムでもパターンを使用する必要はありません。開発の痛みを軽減するためには原則が不可欠であり、(IMO)を習慣にする必要があります。
マチェイチャワプク14

私はあなたに完全に同意します。SRPは、ある程度、エンフォーサがオブジェクトの設計と階層をきれいにします。開発者がオブジェクトやそのあり方について考え始めることができるので、開発者にとっては良いメンタリティです。個人的には、私の開発にも非常に良い影響を与えました。
harsimranb 14

1

答えは、他の人が指摘したように、それらはすべて正しいものであり、それらはすべて相互にフィードし、テストが簡単でメンテナンスが簡単になり、コードがより堅牢になり、メンテナンスが簡単になります...

これらすべては、主要なプリンシパルに要約されます。コードは小さく、ジョブを完了するために必要なだけ実行する必要があります。これは、関数だけでなく、アプリケーション、ファイル、またはクラスにも適用されます。コードが実行する処理が多いほど、理解、保守、拡張、またはテストが難しくなります。

私はそれを一言で要約できると思う:スコープ。アーティファクトの範囲に細心の注意を払ってください。アプリケーション内の特定のポイントで範囲内の物が少ないほど良いです。

スコープの拡大=複雑さ=物事がうまくいかない方法が増えます。


1

クラスが大きすぎると、維持、テスト、理解が難しくなります。他の答えがこの意志をカバーしています。

クラスが問題なく複数の責任を持つことは可能ですが、すぐに複雑すぎるクラスで問題が発生します。

ただし、「1つの責任のみ」という単純なルールがあると、新しいクラスがいつ必要になるかがわかりやすくなります

しかし、「責任」を定義することは難しく、「アプリケーション仕様に記載されていることをすべて実行する」ことを意味するものではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.