最近コードレビューの最中に、臭いのあるパターンを含む新しい同僚によって書かれたコードに出くわしました。私の同僚の決定は、有名なClean Codeブック(およびおそらく他の同様のブック)によって提案されたルールに基づいていると思います。
クラスコンストラクターが有効なオブジェクトの作成を完全に担当し、その主なタスクがオブジェクトの(プライベート)プロパティの割り当てであることは私の理解です。もちろん、オプションのプロパティ値がクラスコンストラクター以外のメソッドによって設定されることもありますが、そのような状況はかなりまれです(ただし、クラスの残りの部分がそのようなプロパティのオプション性を考慮している場合は、必ずしも間違っているわけではありません)。オブジェクトが常に有効な状態であることを保証できるため、これは重要です。
ただし、私が遭遇したコードでは、ほとんどのプロパティ値は実際にはコンストラクター以外のメソッドによって設定されています。計算結果の値は、クラス全体のいくつかのプライベートメソッド内で使用されるプロパティに割り当てられます。作成者は、これらの値を必要とする関数にパラメーター化するのではなく、クラスのプロパティを、クラス全体からアクセスできるグローバル変数であるかのように使用しているようです。さらに、クラスのメソッドは特定の順序で呼び出す必要があります。これは、クラスが他の方法で行うことはほとんどないためです。
このコードは、メソッドを短くして(コードの5行以下)、大きなパラメーターリスト(3パラメーター未満)を避け、コンストラクターが機能してはならない(何らかの計算を実行するなど)アドバイスに触発されたと思いますこれはオブジェクトの有効性に不可欠です)。
もちろん、メソッドが特定の順序で呼び出されない場合にあらゆる種類の未定義エラーが発生する可能性があることを証明できれば、このパターンに反論することができます。ただし、これに対する応答には、プロパティを設定する必要があるメソッドが呼び出されたときにプロパティを設定する必要があることを確認する検証を追加する予定です。
ただし、コードを完全に変更して、クラスを特定の順序で(手続き的に)呼び出す必要のある一連のメソッドではなく、実際のオブジェクトの設計図になるようにすることをお勧めします。
出会ったコードの匂いがします。実際、いつクラスプロパティに値を保存するか、および別のメソッドを使用するためにいつパラメータに値を保存するかについては、かなり明確な違いがあると思います-それらが互いに代替できるとは本当に信じていません。この区別のための言葉を探しています。
Clean Codeを読み、Clean Codersシリーズを複数回見て、多くの場合、よりクリーンなコードを書くことを他の人々に教え、指導する人として、私は確かにあなたの観察が正しいことを保証できます-あなたが指摘するメトリックはすべて本に記載されています。
しかし、本はあなたが指摘したガイドラインと一緒に適用されるべきである他のポイントを作り続けます。これらは、処理しているコードでは無視されているようです。これは、同僚がまだ学習段階にあるために発生した可能性があります。その場合、コードのsmellsを指摘する必要がある限り、彼らがそれを実行していることを覚えておくことは良いことです善意で、より良いコードを学び、書き込もうとしています。
Clean Codeは、可能な限り引数を少なくして、メソッドを短くすることを提案しています。しかし、それらのガイドラインに沿って、私たちはフォローする必要があることを提案します[〜#〜] s [〜#〜]OLID原則、増加cohesionおよびcouplingを減らします。
SOLIDの[〜#〜] s [〜#〜]は、単一責任の原則を表し、オブジェクトは1つのことだけを担当する必要があります。「物」はあまり正確な用語ではないため、この原則の説明は大きく異なります。ただし、クリーンコードの作者であるボクおじさんは、この原則を作り出した人物でもあります。それは、「同じ理由で変化するものを集めます。異なる理由で変化するものを分けてください。」彼は続けて変化する理由ここ および ここ (ここでの詳細な説明は多すぎるでしょう。)この原則を処理しているクラスに適用した場合、計算を処理する部分が分離される可能性が非常に高くなります。 変更する理由これらの計算の数に応じて、クラスを2つ以上に分割することにより、保持状態を処理するものから。
また、Cleanクラスはcohesiveである必要があります。これは、そのメソッドのほとんどがその属性のほとんどを使用することを意味します。そのため、最大限にまとまりのあるクラスは、すべてのメソッドがその属性のすべてを使用するクラスです。例として、グラフィカルアプリでは、属性_Point a
_と_Point b
_を持つVector
クラスを使用できます。ここで、メソッドはscaleBy(double factor)
とprintTo(Canvas canvas)
、両方が両方の属性で動作します。対照的に、最小限のまとまりのあるクラスとは、各属性が1つのメソッドでのみ使用され、各メソッドで複数の属性が使用されることのないクラスです。平均して、クラスはまとまりのない部分のまとまりのない「グループ」を提示します。つまり、いくつかのメソッドは属性a
、b
およびc
を使用し、残りはc
とd
-クラスを2つに分割すると、最終的に2つのまとまりのあるオブジェクトになることを意味します。
最後に、Cleanクラスは、couplingを可能な限り削減する必要があります。ここで議論する価値のあるカップリングには多くのタイプがありますが、手元のコードは主に一時的なカップリングに苦しんでいるようです。メソッドは、正しい順序で呼び出された場合にのみ期待どおりに機能します。上記の2つのガイドラインと同様に、これに対する解決策は通常、クラスを2つ以上のcohesiveオブジェクトに分割することです。この場合の分割戦略には、通常、ビルダーやファクトリーなどのパターンが含まれ、非常に複雑なケースでは、ステートマシンが含まれます。
TL; DR:同僚が従ったクリーンコードガイドラインは適切ですが、この本で言及されている残りの原則、実践、およびパターンにも従う場合に限られます。表示されている「クラス」のCleanバージョンは複数のクラスに分割され、それぞれが単一の責任、一貫性のあるメソッドを持ち、時間的な結合はありません。これは、小さなメソッドとほとんどない引数が意味をなすコンテキストです。
クラスコンストラクターが有効なオブジェクトの作成を完全に担当し、その主なタスクがオブジェクトの(プライベート)プロパティの割り当てであることは私の理解です。
通常、オブジェクトを初期の有効な状態にする責任があります。その後、他のプロパティまたはメソッドが状態を別の有効な状態に変更する場合があります。
ただし、私が遭遇したコードでは、ほとんどのプロパティ値は実際にはコンストラクター以外のメソッドによって設定されています。計算結果の値は、クラス全体のいくつかのプライベートメソッド内で使用されるプロパティに割り当てられます。作成者は、これらの値を必要とする関数にパラメーター化するのではなく、クラスのプロパティを、クラス全体からアクセスできるグローバル変数であるかのように使用しているようです。さらに、クラスのメソッドは特定の順序で呼び出す必要があります。これは、クラスが他の方法で行うことはほとんどないためです。
あなたが暗示する可読性と保守性の問題に加えて、クラス自体の内部でデータフロー/変換の複数の段階が進行しているように思えます。これは、クラスが単一責任の原則に反していることを示している可能性があります。
このコードは、メソッドを短くして(コードの5行以下)、大きなパラメーターリスト(3パラメーター未満)を避け、コンストラクターが機能してはならない(ある種の計算を実行するなど)アドバイスに触発されていると思われますオブジェクトの有効性に不可欠です)。
他のコーディングガイドラインを無視しながら、いくつかのコーディングガイドラインに従うことは、多くの場合、ばかげたコードにつながります。 Ifたとえば、コンストラクターが作業を行うことを避けたい場合、たとえば、通常、適切な方法は、作成前に作業を行い、その作業の結果をコンストラクターに渡すことです。 (そのアプローチについての1つの議論は、クラスに2つの責任を与えることを避けているということかもしれません:初期化の作業とその「主な仕事」、それが何であれ。)
私の経験では、クラスとメソッドを小さくすることは、個別の考慮事項として心に留めておかなければならないことはめったにありません。むしろ、単一の責任からいくぶん自然に続くものです。
ただし、コードを完全に変更して、クラスを特定の順序で(手続き的に)呼び出す必要のある一連のメソッドではなく、実際のオブジェクトの設計図になるようにすることをお勧めします。
あなたはおそらくそうするのが正しいでしょう。簡単な手続き型コードを書くことには何の問題もありません。 OOパラダイムを乱用して難読化された手続き型コードを記述することには問題があります。
いつクラスプロパティに値を保存するか、いつ別のメソッドを使用するためにパラメーターに値を入れるかについては、かなり明確な違いがあると思います。それらが互いに代替できるとは私は思いません。この区別のための言葉を探しています。
通常、あるメソッドから別のメソッドに値を渡す方法としてフィールドに値を入れるべきではありません。フィールドの値は、ある時点でのオブジェクトの状態の意味のある部分である必要があります。 (私はいくつかの有効な例外を考えることができますが、そのようなメソッドがパブリックである場合や、クラスのユーザーが知っておくべき順序の依存関係がある場合は例外です)
あなたはおそらくここで間違ったパターンに対して手すりをしているでしょう。小さな関数と低いアリティがそれ自体で問題になることはほとんどありません。ここでの本当の問題は、関数間の順序依存を引き起こす結合です。そのため、小さな関数の利点を捨ててなしに対処する方法を探してください。
コードは言葉よりも雄弁です。おそらくペアプログラミングの演習として、実際にリファクタリングの一部を実行し、改善を示すことができれば、人々はこの種の修正をはるかによく受け取ります。これを行うと、-all基準をバランスさせて、設計を正しくするのは思ったより難しいことがよくあります。