単一責任原則を新しいコードに適用できますか/適用すべきですか?


20

原則は変更する1つの理由があるモジュールとして定義されます。私の質問は、確かにこれらの変更理由は、コードが実際に変更を開始するまでわからないということですか?ほとんどすべてのコードには、変更される可能性がある多くの理由ありますが、これらすべてを確実に予測し、これを念頭に置いてコードを設計しようとすると、非常に貧弱なコードになります。コードを変更する要求が入り始めたときにのみ、SRPの適用を実際に開始することをお勧めしますか?より具体的には、1つのコードが複数の理由で複数回変更された場合に、変更する理由が複数あることを証明します。変更の理由を推測しようとすると、非常に反アジャイルに聞こえます。

例は、ドキュメントを印刷するコードです。それをPDFに印刷するように変更する要求が来てから、ドキュメントにいくつかの異なるフォーマットを適用するように変更するために2番目の要求が行われます。この時点で、変更する理由(およびSRP違反)が複数あることを示す証拠があり、適切なリファクタリングを行う必要があります。


6
@フランク-それは実際にそのように一般的に定義されています-たとえばen.wikipedia.org/wiki/Single_responsibility_principleを
ジョリス・

1
言い方は、私がSRPの定義を理解する方法ではありません。
ピーターB

2
コードの各行には、(少なくとも)変更される2つの理由があります。それはバグの原因となるか、新しい要件に干渉します。
バートヴァンインゲンシェナウ

1
@BartvanIngenSchenau:LOL ;-)このように表示される場合、SRPはどこにも適用できません。
ドックブラウン

1
@DocBrown:SRPをソースコードの変更と組み合わせなければ、可能です。たとえば、もしあなたは言葉使わずにクラス/関数は一つの文章に何をするかの完全なアカウントを与えることができるものとしてSRPを解釈し、(およびその制限を回避するために無イタチ文言を)。
バートヴァンインゲンシェナウ

回答:


27

もちろん、YAGNIの原則では、本当に必要になる前にSRPを適用するように指示されます。しかし、あなた自身に問うべき質問は、最初にSRPを適用する必要があり、実際にコードを変更する必要があるときだけですか?

私の経験では、SRP を適用すると、コードの特定の変更をどこどのように適用するを知る必要がある場合に、はるかに早い段階でメリットあります。このタスクでは、既存の関数とクラスを読んで理解する必要があります。すべての関数とクラスに特定の責任がある場合、これは非常に簡単になります。したがって、コードを読みやすくするとき、関数を小さくして自己記述的にするときは常に、SHOを適用する必要があります。したがって、答えはyesです。新しいコードに対してもSRPを適用するのは理にかなっています。

たとえば、あなたの印刷コードは、原稿を読み取り、文書をフォーマットする場合、特定のデバイスに結果を出力し、これらは3明確な分離可能な責任があります。したがって、それらから少なくとも3つの関数を作成し、名前に応じて名前を付けます。例えば:

 void RunPrintWorkflow()
 {
     var document = ReadDocument();
     var formattedDocument = FormatDocument(document);
     PrintDocumentToScreen(formattedDocument);
 }

あなたは、変更文書フォーマットやPDFに印刷するための別のものに新しい要件を取得するときに今、あなたは知って正確に変更を適用する必要があり、さらに重要なのは、どこでないコードでこれらの機能または場所のれます。

あなたが関数に来るたび機能が「あまりにも」ありませんので、だから、あなたは理解していない、と変更を適用する場所や、場合はわからない、その後独立し、小さな関数に機能をリファクタリングすることを検討してください。何かを変更する必要があるまで待たないでください。コードは変更されるよりも10倍頻繁に読み取られ、小さな関数ははるかに読みやすくなります。私の経験では、機能が特定の複雑さを持っている場合、将来どの変更が行われるかを知ることなく、機能をいつでも異なる責任に分割することができます。ボブ・マーティンは通常、さらに一歩進んでいます。下のコメントで私が与えたリンクを見てください。

編集:あなたのコメントへ:上記の例の外部機能の主な責任は、特定のデバイスに印刷したり、ドキュメントをフォーマットすることではありません-それは印刷ワークフロー統合することです。したがって、外部関数の抽象化レベルでは、「ドキュメントをもうフォーマットしない」または「ドキュメントを印刷する代わりにメールで送信する」などの新しい要件は、「同じ理由」、つまり「印刷ワークフローが変更されました」です。そのようなことについて話す場合、適切なレベルの抽象化に固執することが重要です。


私は通常、TDDを使用して常に開発するため、この例では、テストすることができないため、物理的に1つのモジュールにすべてのロジックを保持することはできませんでした。これはTDDの副産物であり、SRPを意図的に適用しているからではありません。私の例にはかなり明確で別々の責任があったので、良い例ではないかもしれません。私が求めているのは、新しいコードを書いて、はっきりと言うことができると思います、はい、これはSRPに違反していませんか?「変化する理由」は本質的にビジネスによって定義されていませんか?
SeeNoWeevil

3
@thecapsaicinkid:はい、できます(少なくとも即時のリファクタリングによって)。しかし、非常に小さな関数を取得します-すべてのプログラマーがそれを好むわけではありません。この例を参照してください。sites.google.com
Doc Brown

変更する理由を予測してSRPを適用している場合、あなたの例では、理由の変更は複数あると主張できます。企業は、ドキュメントをフォーマットする必要がなくなったと判断し、後で印刷する代わりに電子メールで送信することを決定できます。編集:リンクを読むだけで、最終結果が特に好きではありませんが、「これ以上抽出できないまで抽出する」の方がはるかに理にかなっており、「変更する唯一の理由」よりも曖昧ではありません。しかし、あまり実用的ではありません。
SeeNoWeevil

1
@thecapsaicinkid:私の編集をご覧ください。外部機能の主な責任は、特定のデバイスに印刷したり、ドキュメントをフォーマットしたりすることではなく、印刷ワークフローを統合することです。そして、ときに関数が変更される理由1-と、唯一の理由でこのワークフローの変更、
ドク・ブラウン

適切なレベルの抽象化に固執することについてのあなたのコメントは、私が欠けていたもののようです。例として、「JSON配列からデータ構造を作成する」と記述するクラスがあります。私には単一の責任のように思えます。JSON配列内のオブジェクトをループし、POJOにマップします。説明と同じレベルの抽象化に固執する場合、変更する理由が1つ以上あると主張するのは困難です。つまり、「JSONがオブジェクトにマッピングされる方法」です。ビーイングあまり抽象私はそれは私が数値をなどの日にマッピングする方法日付フィールドの変更、マップする方法など、複数の理由があると主張できた
SeeNoWeevil

7

SRPを誤解していると思います。

変更の唯一の理由は、コードの変更ではなく、コードの機能です。


3

「変更する理由が1つある」というSRPの定義は、まさにこの理由から誤解を招くと思います。額面どおりにそれを受け入れてください。単一責任の原則では、クラスまたは機能には必ず1つの責任があります。変更する理由が1つしかないことは、最初から1つのことを行うだけの副作用です。将来、コードがどのように変化するかについて何も知らずに、少なくともコード内の単一の責任に向けて努力することができない理由はありません。

この種の最良の手がかりの1つは、クラス名または関数名を選択するときです。クラスの名前がす​​ぐにわからない場合、または名前が特に長い/複雑な場合、または名前が「マネージャー」や「ユーティリティ」などの一般的な用語を使用している場合、おそらくSRPに違反しています。同様に、APIをドキュメント化するとき、説明している機能に基づいてSRPに違反している場合、すぐに明らかになります。

もちろん、プロジェクトの後半まで知ることができないSRPの微妙な違いがあります。単一の責任が2つまたは3つであるように見えたものです。これらは、SRPを実装するためにリファクタリングする必要がある場合です。ただし、それは、変更要求を取得するまでSRPを無視する必要があるという意味ではありません。それはSRPの目的に反します!

例に直接話すには、印刷方法を文書化することを検討してください。あなたは「この方法は、印刷用のデータをフォーマットし、プリンタに送信し、」と言いたい場合、あなたが得るものです:書式設定、プリンタに送信:単一の責任ではないこと、すなわち、2つの責務です。これを認識し、それらを2つの関数/クラスに分割した場合、変更要求が発生したときに、各セクションを変更する理由は1つしかありません。


3

例は、ドキュメントを印刷するコードです。それをPDFに印刷するように変更する要求が来てから、ドキュメントにいくつかの異なるフォーマットを適用するように変更するために2番目の要求が行われます。この時点で、変更する理由(およびSRP違反)が複数あることを示す証拠があり、適切なリファクタリングを行う必要があります。

これらの変更に対応するためにコードを適合させるのに多くの時間を費やすことで、何度も足を踏み入れました。ただの愚かなPDFを印刷する代わりに。

コードを削減するためのリファクタリング

使い捨てパターンは、コードの膨張を引き起こす可能性があります。パッケージが、個々に意味をなさないごみの山を作成する小さな特定のクラスで汚染されている場合。印刷部分に到達する方法を理解するためだけに、多数のソースファイルを開く必要があります。さらに、実際の印刷を行う10行のコードを実行するためだけに、数千行ではないにしても数百行のコードを配置できます。

ブルズアイを作成する

単一使用パターンは、ソースコードを削減し、コードの再利用を改善することを目的としていました。これは、専門化と特定の実装を作成することを目的としていました。bullseyeあなたのためのソースコードの一種go to specific tasks。印刷に問題が発生したとき、それを修正する場所を正確に知っていました。

単回使用はあいまいな破砕を意味しない

はい、既にドキュメントを印刷するコードがあります。はい、PDFも印刷するにはコードを変更する必要があります。はい、ドキュメントのフォーマットを変更する必要があります。

usage大幅に変更されましたか?

リファクタリングによりソースコードのセクションが過度に一般化される場合。の元の意図がprinting stuffもはや明確ではなくなるまで、ソースコードにあいまいなフラクチャリングを作成しました。

新しい男はこれをすぐに理解できるでしょうか?

理解しやすい組織で常にソースコードを保守してください。

時計職人にならないでください

あまりにも多くの場合、開発者がアイピースを装着し、バラバラになった場合に他の誰もピースを元に戻せない程度まで細部に焦点を当てているのを見てきました。

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


2

変更の理由は、最終的には、アプリケーションの実行環境に関する仕様または情報の変更です。したがって、単一の責任原則は、各コンポーネント(クラス、関数、モジュール、サービス...)を記述するように指示することであり、仕様と実行環境をできるだけ考慮しないようにします。

コンポーネントを記述するときに仕様と環境を知っているので、原則を適用できます。

ドキュメントを印刷するコードの例を検討する場合。文書がPDFになることを考慮せずにレイアウトテンプレートを定義できるかどうかを検討する必要があります。あなたはそうすることができるので、SRPはあなたにすべきだと言っています。

もちろん、YAGNIはあなたがすべきではないと言っています。設計原則間のバランスを見つける必要があります。


2

Flupは正しい方向に向かっています。「単一責任原則」はもともと手順に適用されていました。たとえば、デニスリッチーは、関数は1つのことを実行し、適切に実行する必要があると言います。それから、C ++では、Bjarne Stroustrupは、クラスが1つのことを実行し、それを適切に実行する必要があると言います。

経験則を除き、これら2つは正式には相互にほとんどまたはまったく関係がないことに注意してください。プログラミング言語で表現するのに便利なものだけに対応します。まあ、それは何かです。しかし、それはflupが推進しているものとはまったく異なる話です。

現代の(つまり、アジャイルおよびDDD)実装は、プログラミング言語が表現できるものよりも、ビジネスにとって重要なものに重点を置いています。驚くべきことは、プログラミング言語がまだ追いついていないことです。古いFORTRANに似た言語は、当時の主要な概念モデルに適合する責任を獲得します。カードリーダーを通過するときに各カードに適用されるプロセス、または(Cのように)各割り込みに伴う処理。その後、ADT言語が登場し、DDDの人々が後に重要なものとして再発明するものを捉えるまでに成熟しました(ただし、ジムネイバーズは1968年までにそのほとんどを把握し、公開し、使用していました)。 。(モジュールではありません。)

このステップは、振り子スイングよりも進化ではありませんでした。振り子がデータに揺れ動くと、FORTRAN固有のユースケースモデリングが失われました。主な焦点が画面上のデータまたは形状に関係している場合、それは問題ありません。PowerPointのようなプログラム、または少なくともその単純な操作のための優れたモデルです。

失われたのはシステムの責任です。DDDの要素は販売していません。そして、私たちはメソッドをうまくクラス化しません。システムの責任を売ります。あるレベルでは、単一の責任原則に基づいてシステムを設計する必要があります。

ですから、Rebecca Wirfs-Brockや私のようにクラスメソッドについて話していた人を見ると、ユースケースの観点から話していることになります。それが私たちの販売するものです。これらはシステム操作です。ユースケースには単一の責任が必要です。ユースケースはめったに建築単位ではありません。しかし、誰もがそのふりをしようとしていました。たとえば、SOAの人々を目撃してください。

これが、Trygve ReenskaugのDCIアーキテクチャに興奮している理由です。これは、上記のLean Architectureの本で説明されています。それは最終的に、「単一の責任」に対するand意的で神秘的な従順であったものにいくつかの真の身分を与えます-上記の議論のほとんどで見られるように。その特徴は、人間のメンタルモデルに関係しています。エンドユーザーが最初で、プログラマーが2番目です。それはビジネス上の懸念に関連しています。そして、ほとんど偶然に、それはflupが私たちに挑戦するときの変化をカプセル化します。

私たちが知っている単一責任の原則は、その起源の時代から残された恐竜または私たちが理解の代わりに使用する趣味の馬のいずれかです。優れたソフトウェアを実行するには、これらの趣味の馬をいくつか残す必要があります。そして、それは箱から出して考える必要があります。物事をシンプルで理解しやすくすることは、問題がシンプルで理解しやすい場合にのみ機能します。私はそれらの解決策にそれほど興味はありません。それらは典型的なものではなく、挑戦がどこにあるのかではありません。


2
あなたが書いたものを読むとき、途中でどこかであなたが話していることを見失いました。良い答えは、質問を森の中の散歩の出発点としてではなく、すべての文章をリンクする明確なテーマとして扱います。
ドナルドフェローズ

1
ああ、あなたは私の古いマネージャーの一人のように、その一人です。「私たちはそれを理解したくない:それを改善したい!」ここで重要なテーマの問題は、原則の1つです。それは「SRP」の「P」です。もし正しい質問だったら、私は質問に直接答えたでしょう。そうではありませんでした。誰が質問をしたかでそれを取り上げることができます。
コープ

ここのどこかに良い答えが埋まっています。私は思う
...-ラバーダック

0

はい、単一責任原則を新しいコードに適用する必要があります。

しかし!責任とは何ですか?

「レポートの印刷は責任」ですか?答えは、「たぶん」だと思います。

SRPの定義を「変更する唯一の理由がある」として使用してみましょう。

レポートを印刷する機能があるとします。2つの変更がある場合:

  1. レポートの背景を黒にする必要があるため、その関数を変更します
  2. PDFに印刷する必要があるため、その関数を変更します

最初の変更は「レポートスタイルの変更」、もう1つの変更は「レポート出力形式の変更」です。これらは異なるものであるため、2つの異なる関数に配置する必要があります。

しかし、2番目の変更が次の場合は:

2b。レポートに異なるフォントが必要なため、その関数を変更します

両方の変更は「レポートスタイルの変更」であり、1つの機能にとどまることができます。

では、それはどこに私たちを残すのでしょうか?いつものように、物事をシンプルで理解しやすいものにするようにしてください。背景色の変更が20行のコードを意味し、フォントの変更が20行のコードを意味する場合は、2つの関数を再度作成します。各行が1行の場合は、1行に収めてください。


0

新しいシステムを設計するとき、その存続期間中に行う必要のある変更の種類と、配置するアーキテクチャに与える変更のコストを考慮するのが賢明です。システムをモジュールに分割することは、間違いを犯すための費用のかかる決定です。

良い情報源は、ビジネスのドメインエキスパートの頭の中にあるメンタルモデルです。ドキュメント、フォーマット、およびPDFの例を取り上げます。ドメインの専門家は、ドキュメントテンプレートを使用してレターをフォーマットすることを通知するでしょう。静止しているか、Wordで、または何でも。コーディングを開始してデザインで使用する前に、この情報を取得できます。

これらについての素晴らしい記事:Coplienによるリーンアーキテクチャ


0

「印刷」は、MVCの「ビュー」に非常によく似ています。オブジェクトの基本を理解している人なら誰でもそれを理解できます。

それはシステムの責任です。これは、プリンター(ビュー)、印刷対象(モジュール)、プリンター要求とオプション(コントローラーから)を含むメカニズム(MVC)として実装されます。

これをクラスまたはモジュールの責任としてローカライズしようとするのは簡単で、30歳の考え方を反映しています。それ以来、私たちは多くのことを学び、それは文献や成熟したプログラマーのコードで十分に証明されています。


0

コードを変更する要求が入り始めたときにのみ、SRPの適用を実際に開始することをお勧めしますか?

理想的には、コードのさまざまな部分の責任が何であるかを既によく知っているはずです。おそらく、使用しているライブラリが何をしたいのかを考慮に入れて、最初の本能に従って責任に分割します(タスク、責任、ライブラリへの委任は、ライブラリが実際にタスクを実行できる場合、通常は素晴らしいことです) )。次に、変化する要件に応じて責任の理解を深めます。最初にシステムをよく理解すればするほど、責任の割り当てを根本的に変更する必要が少なくなります(ただし、責任はサブ責任に分割するのが最適であることがわかります)。

それについて心配するのに長い時間を費やすべきではない。コードの重要な特徴は、後で変更できることです。最初に完全に正しくする必要はありません。将来的に間違いを少なくできるように、形状の責任の種類を学習することで、時間の経過とともに良くなるようにしてください。

例は、ドキュメントを印刷するコードです。それをPDFに印刷するように変更する要求が来てから、ドキュメントにいくつかの異なるフォーマットを適用するように変更するために2番目の要求が行われます。この時点で、変更する理由(およびSRP違反)が複数あることを示す証拠があり、適切なリファクタリングを行う必要があります。

これは、全体的な責任(コードの「印刷」)に副次的な責任があることを示しており、細分化する必要があります。これはSRP 自体の違反ではなく、パーティション分割(おそらく「フォーマット」および「レンダリング」サブタスクへ)がおそらく必要であることを示しています。サブタスク内で何が起こっているのかを実装を見ずに理解できるように、これらの責任を明確に説明できますか?可能であれば、合理的な分割になる可能性があります。

単純な実際の例を見ると、より明確になるかもしれません。のsort()ユーティリティメソッドを考えてみましょうjava.util.Arrays。それは何をするためのものか?配列を並べ替えますが、それだけです。要素を出力せず、最も道徳的に適合したメンバーを見つけず、Dixieを口whiもしません。配列を並べ替えるだけです。どちらかを知る必要はありません。ソートはそのメソッドの唯一の責任です。(実際、プリミティブ型を処理するためのややsomewhatい技術的な理由でJavaには多くのソートメソッドがありますが、すべて同等の責任があるため、それに注意を払う必要はありません。)

メソッド、クラス、モジュールを作成し、それらが人生で明確に指定された役割を持つようにします。一度に理解しなければならない量を抑え、それが大規模システムの設計と保守を処理できるようにします。

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