きれいなコード:パラメーターが少ない短いメソッドの結果


15

最近、コードのレビュー中に、新しい同僚が書いたコードに出会いました。これには匂いのあるパターンが含まれています。同僚の決定は、有名なClean Codeの本(およびおそらく他の同様の本)によって提案されたルールに基づいていると思います。

クラスコンストラクターは有効なオブジェクトの作成に完全に責任があり、その主なタスクはオブジェクトの(プライベート)プロパティの割り当てであることを理解しています。もちろん、オプションのプロパティ値がクラスコンストラクター以外のメソッドによって設定される可能性がありますが、そのような状況はかなりまれです(クラスの残りの部分がそのようなプロパティのオプション性を考慮する場合は、必ずしも間違っているわけではありません)。これは、オブジェクトが常に有効な状態であることを保証できるため、重要です。

しかし、私が遭遇したコードでは、ほとんどのプロパティ値は実際にはコンストラクター以外のメソッドによって設定されています。計算の結果の値は、クラス全体のいくつかのプライベートメソッド内で使用されるプロパティに割り当てられます。著者は、クラスプロパティを、それらを必要とする関数にパラメーター化するのではなく、クラス全体でアクセスできるグローバル変数であるかのように使用しているようです。さらに、クラスのメソッドは特定の順序で呼び出す必要があります。そうしないと、クラスはそれほど多くのことをしないからです。

このコードは、大きなパラメーターリスト(<3パラメーター)を回避するために、メソッドを短くする(<= 5行のコード)アドバイスにインスパイアされており、コンストラクターが機能しないようにする必要がある(何らかの計算を実行するなど)これはオブジェクトの有効性にとって不可欠です)。

もちろん、メソッドが特定の順序で呼び出されない場合に、あらゆる種類の未定義エラーが発生する可能性があることを証明できれば、もちろんこのパターンに反論することができます。ただし、これに対する応答では、プロパティの設定が必要なメソッドが呼び出されたらプロパティを設定する必要があることを検証する検証が追加されると予測しています。

ただし、特定の順序で(手続き的に)呼び出される一連のメソッドではなく、クラスが実際のオブジェクトの青写真になるように、コードを完全に変更することをお勧めします。

私が遭遇したコードは臭いを感じます。実際、クラスプロパティに値を保存するタイミングと、使用する別のメソッドのパラメーターに値を配置するタイミングについては、かなり明確な違いがあると思います。 。この区別の言葉を探しています。


6
1.しばらくの間、悪魔の擁護者を演じます... コードは実際に機能しますか?ので、データ転送オブジェクトは、完全に有効な技術であり、それがこのすべてが...あるかどう
ロバート・ハーヴェイ

7
2.問題を説明する言葉が不足している場合、同僚の立場に反論する十分な経験がありません。
ロバートハーヴェイ

4
3.動作可能なコードがある場合は、投稿してCode Reviewに投稿し、彼らにそれを見てもらいます。そうでなければ、これはたださまよう一般性です。
ロバートハーヴェイ

5
@RobertHarvey「計算の結果として得られる値は、クラス全体のいくつかのプライベートメソッド内で使用されるプロパティに割り当てられます」は、私にとって自尊心のあるDTOとは思えません。もう少し具体性が役立つことに同意します。
トポ

4
余談:誰かが実際にコードをバッシングする前にクリーンコードを読んでいないようです。私はそれをもう一度スキャンしただけで、「コンストラクタは作業をしてはいけない」と示唆する場所を見つけることができませんでした(実際にはいくつかの例は作業を実行します)。関数のろくでなしではなく、パラメータのグループ。そして、本、メソッド間の一時的な依存を避けるためにコードをリファクタリングすることを提案しています。彼の好むコードスタイルのいくつかに対するあなたの偏見が、本に対するあなたの認識を色付けしたと思います。
エリックキング

回答:


13

Clean Codeを読んでClean Codersシリーズを何度も見て、他の人にクリーナーコードを書くことを教えたり指導したりする人として、あなたの観察が正しいことを確かに保証することができます-あなたが指摘するメトリックはすべて本で言及されています。

ただし、この本は、指摘したガイドラインと併せて適用する必要がある他のポイントを作成します。これらは、あなたが扱っているコードでは無視されているように見えました。これは、同僚がまだ学習段階にあるために発生した可能性があります。この場合、コードの匂いを指摘する必要がある限り、彼らは善意で学習し、試行していることを覚えておくのは良いことです。より良いコードを書くために。

Clean Codeは、可能な限り引数を少なくして、メソッドを短くすることを提案しています。しかし、それらのガイドラインに沿って、S OLIDの原則に従い、凝集度を高め、結合を減らす必要があることを提案しています。

SOLID のSは、単一の責任原則を表します。これは、オブジェクトは1つのことだけを担当する必要があることを示しています。「もの」は非常に正確な用語ではないため、この原則の説明は大きく異なります。ただし、Clean Codeの作者であるボブおじさんもこの原則を生み出した人物であり、「同じ理由で変化するものを集めます。異なる理由で変化するものを分離します」と説明しています。彼は、ここここを変える理由 とともに、彼が意味することを言い続けます(ここでの説明は長すぎます)。この原則があなたが扱っているクラスに適用された場合、多くの理由に応じて、クラスを2つ以上に分割することにより、計算を扱う部分が保持状態を扱う部分から分離される可能性が非常に高いそれらの計算を変更するには。

また、Cleanクラスは凝集的である必要があります。つまり、ほとんどのメソッドはほとんどの属性を使用します。そのため、最大限に凝集したクラスは、すべてのメソッドがその属性をすべて使用するクラスです。例として、グラフィカルアプリでは、Vector属性Point aとを持つクラスPoint bscaleBy(double factor)ありprintTo(Canvas canvas)、メソッドはとのみで、両方とも両方の属性で動作します。対照的に、最小凝集クラスは、各属性が1つのメソッドでのみ使用され、各メソッドで複数の属性が使用されることのないクラスです。凝集部品の平均では、クラスのプレゼント非粘着性の「グループ」 -すなわち、A、いくつかの方法は属性を使用しabそしてc、残りの使用中cd -クラスを2つに分割すると、2つのまとまりのあるオブジェクトになります。

最後に、Cleanクラスは可能な限り結合を減らす必要があります。ここで議論する価値のある多くのタイプの結合がありますが、手元のコードは主に一時的な結合に苦しんでいるようです。上記の2つのガイドラインのように、これに対する解決策は通常、クラスを2つ以上の凝集したオブジェクトに分割することを伴います。この場合の分割戦略には通常、BuilderやFactoryなどのパターンが含まれ、非常に複雑な場合にはState-Machinesが含まれます。

TL; DR:同僚が従ったクリーンコードガイドラインは優れていますが、本で言及されている残りの原則、実践、およびパターンも順守している場合に限ります。表示されている "クラス" のクリーンバージョンは、それぞれが単一の責任、凝集メソッドを持ち、一時的な結合を持たない複数のクラスに分割されます。これは、小さなメソッドとほとんどない引数が理にかなっているコンテキストです。


1
あなたとtopo mortoの両方が良い答えを書いていますが、私は1つしか受け入れません。私はあなたがSRP、凝集性、結合に取り組んだことを気に入っています。これらは、コードレビューで使用できる便利な用語です。オブジェクトをそれ自体の責任を持つ小さなオブジェクトに分割することは、明らかに進むべき方法です。多数のクラスプロパティの値を初期化する1つの(非コンストラクター)メソッドは、新しいオブジェクトを返す必要があるという完全無料です。私はそれを見るべきだった。
user2180613

1
SRPは最も重要なガイドラインです。それらすべてを支配するための1つのリング。よくできたSRPは当然、より短い方法をもたらします。例:2つのパブリックメソッドと約8つの非パブリックメソッドのみを備えた前面クラスがあります。〜3行を超えるものはありません。クラス全体は約35 LOCです。しかし、私はこのクラスを最後に書きました!基礎となるコードがすべて記述されるまでに、このクラスは本質的にそれ自体を記述したため、メソッドを大きくする必要はありませんでした。「私を殺すなら、これらのメソッドを5行で書くつもりだ」とは言わなかった。SRPを適用するたびに発生します。
レーダーボブ

11

クラスコンストラクターは有効なオブジェクトの作成に完全に責任があり、その主なタスクはオブジェクトの(プライベート)プロパティの割り当てであることを理解しています。

通常は、オブジェクトを有効な初期状態にする責任があります。その後、他のプロパティまたはメソッドが状態を別の有効な状態に変更する場合があります。

しかし、私が遭遇したコードでは、ほとんどのプロパティ値は実際にはコンストラクター以外のメソッドによって設定されています。計算の結果の値は、クラス全体のいくつかのプライベートメソッド内で使用されるプロパティに割り当てられます。著者は、クラスプロパティを、それらを必要とする関数にパラメーター化するのではなく、クラス全体でアクセスできるグローバル変数であるかのように使用しているようです。さらに、クラスのメソッドは特定の順序で呼び出す必要があります。そうしないと、クラスはそれほど多くのことをしないからです。

あなたが暗示する読みやすさと保守性の問題だけでなく、クラス自体の内部でデータフロー/変換の複数の段階が進行しているように聞こえます。

このコードは、大きなパラメーターリスト(<3パラメーター)を避けるために、メソッドを短くする(<= 5行のコード)アドバイスにインスパイアされており、コンストラクターが動作してはならない(そのような計算を実行するなど)オブジェクトの有効性に不可欠です)。

いくつかのコーディングガイドラインに従う一方で、他のガイドラインを無視すると、愚かなコードにつながることがよくあります。たとえば、コンストラクターが作業を行うのを避けたい場合、賢明な方法は通常、構築前に作業を行い、その作業の結果をコンストラクターに渡すことです。(そのアプローチの1つの議論は、クラスに2つの責任を与えることを避けているということかもしれません:初期化の作業とその「主な仕事」、それが何であれ。)

私の経験では、クラスとメソッドを小さくすることは、別個の考慮事項として念頭に置く必要があることはめったにありません。むしろ、それは単一の責任からある程度自然に続きます。

ただし、特定の順序で(手続き的に)呼び出される一連のメソッドではなく、クラスが実際のオブジェクトの青写真になるように、コードを完全に変更することをお勧めします。

あなたはおそらくそうするのが正しいでしょう。簡単な手続き型コードを書くことに何の問題もありません。オブジェクト指向のパラダイムを乱用して難読化された手続き型コードを記述することには問題があります。

クラスプロパティに値を保存するタイミングと、使用する別のメソッドのパラメーターに値を入れるタイミングについては、かなり明確な違いがあると思います-互いに代用できるとは本当に信じていません。この区別の言葉を探しています。

通常、あるメソッドから別のメソッドに値を渡す方法としてフィールドに値を入れるべきではありません。フィールドの値は、特定の時点でのオブジェクトの状態の重要な部分である必要があります。(私はいくつかの有効な例外を考えることができますが、そのようなメソッドがパブリックであるか、クラスのユーザーが認識すべき順序依存性がある場合は例外ではありません)


2
理由:1. SRPを強調する。「...小さなメソッド...自然に続く」 2.コンストラクターの目的-有効な状態。3.「いくつかのコーディングガイドラインに従う一方で、他のガイドラインは無視します。」これは、コーディングのウォーキングデッドです。
レーダーボブ

6

あなたはここで間違ったパターンに対して手すりをしている可能性が高いです。小さな関数と低いアリティは、それ自体で問題になることはめったにありません。ここでの実際の問題は、関数間の順序依存性を引き起こすカップリングです。したがって、小さな関数の利点を捨てずにそれに対処する方法を探してください。

コードは言葉よりも雄弁です。あなたが実際にリファクタリングの一部を行い、おそらくペアプログラミングの演習として改善を示すことができれば、人々はこれらの種類の修正をはるかに良く受け取ります。これを行うと、多くの場合、すべての基準のバランスを取りながら、設計を正しくすることは思ったよりも難しいと感じます。

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