web-dev-qa-db-ja.com

クリーンなコードプラクティスに従って、より多くのコードが記述されていることをどのように正当化しますか?

モデレーターのメモ
この質問にはすでに17の回答が投稿されています。新しい回答を投稿する前に、既存の回答を読んで、自分の視点が十分にカバーされていないことを確認してください。

私はRobert Martinの "Clean Code"ブックで推奨されているプラ​​クティスのいくつか、特に、使用するソフトウェアの種類に適用されるものと、自分にとって意味のあるもの(私はドグマとしてそれに従いません)に従いました。 。

しかし、私が気付いた副作用の1つは、私が書く「クリーン」なコードは、いくつかのプラクティスに従わなかった場合よりも多くのコードになることです。これにつながる具体的なプラクティスは次のとおりです。

  • 条件式のカプセル化

だから代わりに

if(contact.email != null && contact.emails.contains('@')

私はこのような小さなメソッドを書くことができました

private Boolean isEmailValid(String email){...}
  • インラインコメントを別のプライベートメソッドで置き換え、メソッド名の上にインラインコメントを付けるのではなく、メソッド名がそれ自体を説明するようにする
  • クラスに変更する理由は1つだけです

そして他のいくつか。つまり、コメントを置き換えたり条件式をカプセル化したりする小さなメソッドのために、30行のメソッドが最終的にクラスになるということです。非常に多くのメソッドがあることに気づくと、「理にかなっています」すべての機能を1つのクラスに入れてください。

極端に実践すると害を及ぼす可能性があることを認識しています。

私が答えを探している具体的な質問は次のとおりです。

これはクリーンなコードを書くことの許容できる副産物ですか?もしそうなら、より多くのLOCが記述されているという事実を正当化するために使用できるいくつかの引数は何ですか?

組織はLOCの増加については特に懸念していませんが、LOCが増加するとクラスが非常に大きくなる可能性があります(読みやすくするために、1回限りのヘルパー関数の束がない長いメソッドに置き換えることもできます)。

十分な大きさのクラスを見ると、クラスが忙しく、責任が果たされているような印象を与えます。したがって、他の機能を実現するために、さらに多くのクラスを作成することになります。その結果、多くのクラスが作成され、すべてが多くの小さなヘルパーメソッドを使用して「1つのこと」を実行します。

これは特定の懸念事項です...これらのクラスは、多くの小さなメソッドの助けなしに、「1つのこと」を実現する単一のクラスである可能性があります。おそらく3つまたは4つのメソッドといくつかのコメントを持つ単一のクラスである可能性があります。

108
CommonCoreTawan

...私たちは、比較的大規模でドキュメント化されていないコードベース(継承したもの)をサポートする非常に小さなチームです。そのため、一部の開発者/マネージャは、より少ないコードを記述して物事を成し遂げることに価値を見出し、維持するコードを少なくしています。

これらの人々は何かを正しく識別しました:彼らはコードを維持しやすくしたいと考えています。ただし、コードが少ないほど保守が容易であると想定しているため、問題が発生しています。

コードを保守しやすくするには、変更が容易である必要があります。変更が簡単なコードを実現する最も簡単な方法は、変更が破壊的なものである場合に失敗する、完全な一連の自動テストを行うことです。テストはコードであるため、これらのテストを作成するとコードベースが膨らみます。そしてそれは良いことです。

次に、変更が必要なものを解決するために、コードは読みやすく、理由付けが容易である必要があります。非常に簡潔なコードで、行数を減らすためだけにサイズを縮小しても、読みやすくなることはほとんどありません。コードが長いほど読み取りに時間がかかるため、明らかに妥協する必要があります。しかし、理解する方が早い場合は、それだけの価値があります。それがその利点を提供しない場合、その冗長性は利点ではなくなります。しかし、より長いコードが読みやすさを向上させるのであれば、これも良いことです。

129
David Arno

はい、それは許容できる副産物であり、正当な理由は、ほとんどの場合、ほとんどのコードを読む必要がないように構成されていることです。変更を加えるたびに30行の関数を読み取る代わりに、5行の関数を読み取って全体のフローを取得します。変更がその領域に触れた場合は、いくつかのヘルパー関数を取得します。新しい「追加」クラスの名前がEmailValidatorであり、メールの検証に問題がないことがわかっている場合は、全体を読み飛ばしてもかまいません。

また、小さな断片を再利用する方が簡単で、プログラム全体の行数が減る傾向があります。 EmailValidatorはどこでも使用できます。メールの検証は行うが、データベースアクセスコードと一緒に実行されないコードの一部の行は、再利用できません。

次に、電子メール検証ルールを変更する必要がある場合に何を行う必要があるかを検討します。または多くの場所、おそらくいくつか欠落していますか?

154
Karl Bielefeldt

ビルゲイツ氏は、「コード行によるプログラミングの進行状況の測定は、航空機の建造の進行状況を重量で測定するようなものです」と言ったことが有名でした。

私はこの感情に謙虚に同意します。これは、プログラムが多かれ少なかれコード行を求めて努力すべきであるということではありませんが、これが最終的に機能して機能するプログラムを作成するために重要なことではありません。最終的にコード行を追加する背後にある理由は、理論的にはそのように読みやすくなることを覚えておくと役立ちます。

特定の変更が多かれ少なかれ読みやすいかどうかについて意見の不一致が発生する可能性がありますが、プログラムに変更を加えるのは間違っていないと思います理由それはもっと読みやすいです。たとえば、isEmailValidを作成することは、特にそれを定義するクラスによって1回だけ呼び出されている場合は、不必要で不要であると考えることができます。ただし、AND条件の文字列ではなく、条件でisEmailValidを確認したいので、個々の条件が何をチェックするのか、なぜチェックされるのかを判断する必要があります。

問題が発生するのは、副作用があるか、電子メール以外のものをチェックするisEmailValidメソッドを作成するときです。これは、単にすべてを書き出すよりも悪いであるためです。それは誤解を招くものであり、それが原因でバグを見逃す可能性があるため、状況はさらに悪化します。

このケースでは明らかにそうしていませんが、そうしている間は続けることをお勧めします。変更を加えることで読みやすくなるかどうかを常に自問する必要があります。そうである場合は、変更してください。

34
Neil

したがって、一部の開発者/マネージャーは、物事を成し遂げるために少ないコードを書くことに価値を見いだし、維持する必要のあるコードを少なくしています。

これは、実際の目標を見失う問題です。

重要なのは開発に費やす時間の短縮です。これは、コード行ではなく、時間(または同等の作業)で測定されます。
これは、各ねじを取り付けるのにゼロ以外の時間がかかるため、自動車メーカーはより少ないねじで車を製造する必要があると言っているようなものです。それが持っているか持っていないネジの数。何よりも、自動車は高性能で、安全で、保守が容易である必要があります。

残りの答えは、クリーンなコードが時間の増加につながる方法の例です。


ロギング

ロギングのないアプリケーション(A)を取ります。次に、アプリケーションBを作成します。これは、アプリケーションAと同じですが、ログが記録されています。 Bには常により多くのコード行があるため、より多くのコードを記述する必要があります。

しかし、多くの時間は、問題やバグを調査し、何がうまくいかなかったかを理解することに没頭します。

アプリケーションAの場合、開発者はコードを読み取れず、問題を継続的に再現し、コードをステップ実行して問題の原因を見つける必要があります。つまり、開発者は、実行の最初から最後まで、使用されているすべてのレイヤーでテストする必要があり、使用されているすべてのロジックを監視する必要があります。
たぶん彼はすぐにそれを見つけることができて幸運ですが、おそらく答えは彼が見たと思っている最後の場所になるでしょう。

アプリケーションBの場合、完全なロギングを想定して、開発者はログを観察し、障害のあるコンポーネントをすぐに識別でき、どこを見るかがわかります。

これは、数分、数時間、または数日の節約になります。コードベースのサイズと複雑さによって異なります。


回帰

アプリケーションAを取り上げます。これは、まったくDRYフレンドリーではありません。
DRYであるアプリケーションBを取り上げますが、抽象化が追加されたため、最終的にはより多くの行が必要になりました。

ロジックへの変更が必要な変更要求が提出されます。

アプリケーションBの場合、開発者は変更要求に従って(一意で共有された)ロジックを変更します。

アプリケーションAの場合、開発者は、使用されていることを覚えているこのロジックのすべてのインスタンスを変更する必要があります。

  • 彼がすべてのインスタンスを覚えていたとしても、同じ変更を何度か実装する必要があります。
  • 彼がすべてのインスタンスを覚えていない場合は、矛盾している矛盾したコードベースを扱っています。開発者がめったに使用されないコードを忘れた場合、このバグは将来に至るまでエンドユーザーには明らかにならない可能性があります。その時点で、エンドユーザーは問題の原因を特定する予定ですか?そうであっても、開発者は変更が何を伴うのか覚えておらず、この忘れられたロジックの一部を変更する方法を理解する必要があります。たぶん、開発者はその時までに会社で働いていなくて、それから誰かがそれをすべてゼロから理解しなければなりません。

これは膨大な時間の浪費につながる可能性があります。開発だけでなく、バ​​グを探して見つけることでも。アプリケーションは、開発者が簡単に理解できない方法で不規則な動作を開始する可能性があります。そして、それは長いデバッグセッションにつながります。


開発者互換性

開発者AがアプリケーションAを作成しました。コードはクリーンでも読み取り可能でもありませんが、魅力のように機能し、運用環境で実行されています。当然のことながら、ドキュメントもありません。

開発者Aは、休暇のために1か月間不在です。緊急変更要求が提出されました。 Dev Aが戻るまでさらに3週間待つことはできません。

開発者Bはこの変更を実行する必要があります。彼は今、コードベース全体を読み、すべてがどのように機能するか、理由、およびそれが達成しようとすることを理解する必要があります。これには年齢がかかりますが、3週間でできるとしましょう。

同時に、アプリケーションB(開発Bが作成したもの)には緊急事態があります。開発者Bは使用されていますが、開発者Cはコードベースを知らなくても使用できます。私たちは何をしますか?

  • BをAで作業し続け、CをBで作業する場合、2人の開発者が何をしているのか分からないため、作業が最適に実行されていません。
  • BをAから引き離し、BにBを実行させ、CをAに配置すると、開発者Bのすべての作業(またはその大部分)が破棄される可能性があります。これは、数日/数週間の無駄な作業になる可能性があります。

開発者Aは休暇から戻ってきましたが、Bはコードを理解していなかったので、実装が不適切でした。それはBのせいではありません。利用可能なすべてのリソースを使用したため、ソースコードが十分に読めなかっただけです。 Aはコードの読みやすさの修正に時間を費やす必要がありますか?


これらすべての問題、さらに多くの問題は、最終的に無駄な時間になります。はい、短期的には、クリーンなコードはより多くの努力nowを必要としますが、最終的には配当を支払うことになりますfuture避けられないバグ/変更に対処する必要がある場合。

経営陣は、短いタスクが将来いくつかの長いタスクを節約することを理解する必要があります。 計画の失敗は失敗の計画です。

もしそうなら、より多くのLOCが記述されているという事実を正当化するために使用できるいくつかの引数は何ですか?

私のgotoの説明は、経営者に何を好むかを尋ねています。3か月で開発できる100KLOCコードベースのアプリケーション、または6か月で開発できる50KLOCコードベースのアプリケーションです。

管理はKLOCを考慮しないため、開発者は明らかに短い開発時間を選択します。 KLOCに焦点を当てているマネージャーは、自分が何を管理しようとしているのかについて知らされずに、細かく管理しています。

23
Flater

私はあなたがすべきだと思いますvery全体的な複雑さにつながる場合に備えて、「クリーンなコード」の実践に注意する必要があります。時期尚早のリファクタリングは、多くの悪いことの原因です。

関数に条件を抽出すると、より単純なコード条件が抽出された時点でになりますが、全体より複雑になります。プログラムのより多くのポイント。この新しい関数が表示されている他のすべての関数に少し複雑な負担を加えます。

私はあなたに言っているわけではありませんすべきではありません条件を抽出します。

  • 特に電子メール検証ロジックをテストする場合。次に、そのロジックを別の関数(おそらくクラス)に抽出する必要があります。
  • 同じロジックがコードの複数の場所から使用されている場合は、それを単一の関数に抽出する必要があります。繰り返してはいけません!
  • ロジックが明らかに別の責任である場合。電子メールの検証は、並べ替えアルゴリズムの途中で行われます。メールの検証は、並べ替えアルゴリズムとは関係なく変更されるため、個別のクラスに含める必要があります。

上記のすべてにおいて、それが単なる「クリーンなコード」である以上の抽出の理由です。さらに、それが正しいことであるかどうかは疑いもないでしょう。

疑わしい場合は、常に最も単純で最も簡単なコードを選択してください。

23
JacquesB

これには本質的に問題がないことを指摘しておきます。

if(contact.email != null && contact.email.contains('@')

少なくとも、これが1回使用されたと仮定します。

私はこれで非常に簡単に問題が発生する可能性があります:

private Boolean isEmailValid(String email){
   return email != null && email.contains('@');
}

私が注意するいくつかのこと:

  1. なぜプライベートなのですか?潜在的に有用なスタブのように見えます。プライベートな方法になるのに十分便利で、それがもっと広く使われる可能性はありませんか?
  2. 私はメソッドにIsValidEmailという名前を付けません。おそらくContainsAtSignまたはLooksVaguelyLikeEmailAddressそれは本当の検証をほとんど行わないためです。
  3. 複数回使用されていますか?

それが1回使用されていて、解析が簡単で、1行もかからない場合は、2番目に判断します。それは、チームからの特定の問題でなければ、おそらく私が言うことではありません。

一方、メソッドが次のようなことをするのを見てきました。

if (contact.email != null && contact.email.contains('@')) { ... }
else if (contact.email != null && contact.email.contains('@') && contact.email.contains("@mydomain.com")) { //headquarters email }
else if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") ) { //internal contract teams }

その例は明らかにDRYではありません。

または、最後のステートメントだけでも別の例を示すことができます。

if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") )

目標は、コードを読みやすくすることです。

if (LooksSortaLikeAnEmail(contact.Email)) { ... }
else if (LooksLikeFromHeadquarters(contact.Email)) { ... }
else if (LooksLikeInternalEmail(contact.Email)) { ... }

別のシナリオ:

あなたは次のような方法を持っているかもしれません:

public void SaveContact(Contact contact){
   if (contact.email != null && contact.email.contains('@'))
   {
       contacts.Add(contact);
       contacts.Save();
   }
}

これがビジネスロジックに適合し、再利用されない場合、ここでは問題はありません。

しかし、誰かが「@はなぜ保存されないのか、それは正しくないためです!」そして、ある種の実際の検証を追加して、それを抽出することにしました!

大統領の2番目の電子メールアカウントPr3 $ sid3nt @ h0m3!@ mydomain.comも考慮する必要があり、RFC 2822をすべて試してサポートすることにした場合は、喜んで対応します。

読みやすさについて:

// If there is an email property and it contains an @ sign then process
if (contact.email != null && contact.email.contains('@'))

コードがこれで明らかな場合は、ここにコメントは必要ありません。実際、ほとんどの場合、コードが何をしているのかコメントする必要はありませんが、なぜそれがしているのか:

// The UI passes '@' by default, the DBA's made this column non-nullable but 
// marketing is currently more concerned with other fields and '@' default is OK
if (contact.email != null && contact.email.contains('@'))

Ifステートメントの上にあるコメントでも、小さなメソッド内のコメントでも、私にとっては知識が豊富です。別のメソッド内で適切なコメントを付けて有用な反対を主張することさえあるかもしれません。これは、別のメソッドに移動して方法と理由を実行する必要があるためです。

要約すると、これらのものを測定しないでください。テキストが作成された原則(DRY、SOLID、KISS)に焦点を当てます。

// A valid class that does nothing
public class Nothing 
{

}
9
AthomSfere

クリーンコードは優れた本であり、一読の価値は十分にありますが、そのような問題に関する最終的な権威ではありません。

通常、コードを論理関数に分解することは良い考えですが、マーティンのようにそれを実行するプログラマーはほとんどいません。ある時点で、すべてを関数に変換することから得られる利益が減少し、すべてのコードが小さい場合、追跡が難しくなる場合があります。個。

まったく新しい関数を作成する価値がない場合の1つのオプションは、単に中間変数を使用することです。

boolean isEmailValid = (contact.email != null && contact.emails.contains('@');

if (isEmailValid) {
...

これにより、ファイルを何度もジャンプすることなく、コードを簡単に追跡できます。

別の問題はクリーンコードが本としてかなり古くなっていることです。多くのソフトウェアエンジニアリングは関数型プログラミングの方向に移動しましたが、マーティンは状態を物事に追加し、オブジェクトを作成するために自分の道を離れます。もし彼が今日それを書いたなら、彼はまったく別の本を書いたのではないかと思います。

6
Rich Smith

あなたが現在持っている「電子メールは有効である」状態が非常に無効な電子メールアドレス「@ "、あなたはEmailValidatorクラスを抽象化するすべての理由があると思います。さらに良いことに、十分にテストされた優れたライブラリを使用して、メールアドレスを検証します。

メトリックとしてのコード行は無意味です。ソフトウェア工学における重要な質問は以下ではありません:

  • コードが多すぎませんか?
  • コードが少なすぎますか?

重要な質問は次のとおりです。

  • アプリケーションは全体として正しく設計されていますか?
  • コードは正しく実装されていますか?
  • コードは保守可能ですか?
  • コードはテスト可能ですか?
  • コードは適切にテストされていますか?

私は、コードゴルフ以外の目的でコードを書くときにLoCをまったく考えていません。 「もっと簡潔に書いてもらえませんか?」と自問しましたが、単に長さではなく、読みやすさ、保守性、および効率性の目的で使用しました。

確かに、ユーティリティメソッドの代わりにブール演算の長いチェーンを使用することもできますが、それでいいですか?

あなたの質問は、実際に私が書いたいくつかの長いブールの連鎖を振り返って、おそらく代わりに1つ以上のユーティリティメソッドを書いたほうがよいと気づかせます。

5
Clement Cherlin

あるレベルでは、それらは正しいです-コードが少ないほど良いです。ゲートが引用した別の答え、私は好む:

「デバッグがソフトウェアのバグを取り除くプロセスである場合、プログラミングはそれらを組み込むプロセスでなければなりません。」 – Edsger Dijkstra

「デバッグ時には、初心者は修正コードを挿入します。専門家は欠陥のあるコードを削除します。」 –リチャードパティス

最も安く、最も速く、最も信頼できるコンポーネントは、そこにないコンポーネントです。 -ゴードンベル

つまり、コードが少なければ少ないほど、失敗する可能性が低くなります。何かが必要ない場合は、カットしてください。
コードが複雑すぎる場合は、実際の機能要素がすべて残るまでコードを簡略化します。

ここで重要なことは、これらはすべて機能を参照し、それを行うために必要な最小限のものだけを持っていることです。 howについては何も表されていません。

クリーンなコードを作成するために何をしているのかは、上記に反しません。 LOCに追加していますが、未使用の機能は追加していません。

最終目標は、コードを読みやすくすることですが、余分なものはありません。 2つの原則は互いに反作用してはなりません。

比喩は自動車を作ることでしょう。コードの機能的な部分は、シャーシ、エンジン、ホイールなどです。それを分解する方法は、サスペンションやパワーステアリングなどに似ており、扱いやすくなります。問題が発生する可能性を最小限に抑えるために、仕事をしながらメカニックをできるだけシンプルにする必要がありますが、それでもニースの座席を確保することはできます。

3
Baldrickk

既存の答えには多くの知恵がありますが、もう1つの要素を追加したいと思います。the languageです。

一部の言語では、同じ効果を得るために他の言語よりも多くのコードを使用します。特に、Java(問題の言語だと思います))は非常によく知られており、一般に非常に堅固で明快で簡単ですが、より現代的な言語の方がはるかに簡潔で表現力があります。

たとえば、Javaでは、ゲッターとセッター、および1つ以上のコンストラクターを持つ3つのプロパティを持つ新しいクラスを作成するのに、50行かかることがあります。 Kotlin *またはScalaの1行(適切なequals()hashCode()、およびtoString()メソッドも必要な場合はさらに節約できます。)

その結果、Javaでは余分な作業により、実際には適合しない一般的なオブジェクトを再利用したり、プロパティを既存のオブジェクトに絞り込んだり、一連の「ベア」プロパティを個別に渡したりする可能性が高くなります。簡潔で表現力豊かな言語では、より優れたコードを書く可能性が高くなります。

(これにより、コードの「表面」の複雑さと、コードが実装するアイデア/モデル/処理の複雑さとの違いが明らかになります。コードの行は、最初の行の悪い尺度ではありませんが、2番目の行とはほとんど関係がありません。 。)

したがって、物事を正しく行うための「コスト」は言語によって異なります。おそらく、良い言語の1つの兆候は、しないことで、物事をうまく行うか、単純に行うかを選択できるようになります!

(*これは実際にはプラグの場所ではありませんが、Kotlinは一見の価値があります。)

2
gidds

LOCの減少は相関であることが判明し、欠陥が減少したこと以外は何もありません。次に、LOCを削減すると、欠陥が本質的に相関が因果関係に等しいと信じる罠に陥る可能性が減ると仮定します。 LOCの削減は、優れた開発手法の結果であり、コードを優れたものにするものではありません。

私の経験では、より少ないコード(マクロレベル)で問題を解決できる人は、同じことを行うためにより多くのコードを書く人よりもスキルが高い傾向があります。これらの熟練した開発者がコード行を減らすために行うことは、一般的な問題を解決するための抽象化および再利用可能なソリューションの使用/作成です。彼らはコードの行を数えたり、あちこちで行を切ったりできるかどうか悩むことに時間を費やしません。多くの場合、作成するコードは必要以上に冗長であり、作成するコードは少なくなります。

例を挙げましょう。期間に関するロジックと、それらがどのようにオーバーラップするか、それらが隣接しているかどうか、そしてそれらの間にどのようなギャップが存在するかを扱う必要がありました。私がこれらの問題に取り組み始めたとき、私はどこでも計算を行うコードのブロックを持っていました。最終的に、オーバーラップ、補数などを計算した期間と操作を表すクラスを作成しました。これにより、コードの大きな帯がすぐに削除され、それらがいくつかのメソッド呼び出しに変わりました。しかし、それらのクラス自体はまったく簡潔に書かれていませんでした。

はっきり言って、コードをあちこちに切り詰めてLOCを減らしようとすると、間違っていることになります。それは、食べる野菜の量を減らして減量しようとするようなものです。理解、保守、デバッグが容易なコードを記述し、再利用と抽象化によりLOCを削減します。

1
JimmyJames

有効なトレードオフを特定しました

したがって、実際にはここにトレードオフがあり、全体として抽象化に固有です。名前を付けて分離するために誰かが[〜#〜] n [〜#〜]コード行を独自の関数にプルしようとするときはいつでも、それらは同時に呼び出しサイトを読みやすくします(その名前の根底にあるすべての悲惨な詳細ではなく名前に)より複雑(コードベースの2つの異なる部分に絡み合っているという意味になります)。 「簡単」は「ハード」の反対ですが、「複雑」の反対である「シンプル」の同義語ではありません。この2つは反対ではなく、抽象化は常に、何らかの形式の挿入を容易にするために複雑さを増大させます。

ビジネス要件の変更によって抽象化が漏れ始めたとき、複雑さが増していることが直接わかります。おそらく、抽象化されたコードがツリーを横断していて、実際にある種の情報を収集(およびおそらく操作)したい場合など、新しいロジックのいくつかは、事前に抽出されたコードの途中で最も自然に行われるでしょう。ツリーをトラバースします。一方、このコードを抽象化した場合、他の呼び出しサイトが存在する可能性があり、必要なロジックをメソッドの途中に追加すると、それらの他の呼び出しサイトが壊れる可能性があります。コード行を変更するときはいつでも、そのコード行の直接のコンテキストのみを確認する必要があります。メソッドを変更するときは、ソースコード全体をCmd-Fして、そのメソッドのコントラクトを変更した結果として破損する可能性のあるものを探すか、テストで破損を検出することを期待する必要があります。

これらの場合、貪欲アルゴリズムは失敗する可能性があります

複雑さにより、ある意味でコードもmoreではなくless読みやすくなっています。以前の仕事で、いくつかのレイヤーに非常に注意深く正確に構造化されたHTTP APIを扱いました。すべてのエンドポイントは、着信メッセージの形状を検証し、それを「ビジネスロジックレイヤー」マネージャーに渡すコントローラーによって指定されます次に、「データアクセスオブジェクト」層に対していくつかのクエリを作成する「データレイヤー」を要求し、実際に質問に答えるSQLデリゲートをいくつか作成しました。最初に言えることは、コードの90%がボイラープレートをコピーして貼り付けるようなもの、つまり何もしないことでした。したがって、多くの場合、「ああ、このマネージャは要求をそのデータアクセスオブジェクトに転送するだけなので」、コードの特定の箇所を読み取ることは非常に「簡単」でした。しかし、この特定のケースにカスタマイズされたものがあるかどうかを判断するために、これらすべての異なるレイヤー間をジャンプして一連のボイラープレートを読む必要があったという事実は、lotのコンテキストを実行していたことを意味します-ファイルを切り替えて見つけ、追跡すべきではなかった情報を追跡しようとしています。「これはこのレイヤーではXと呼ばれ、この他のレイヤーではX 'と呼ばれ、次にこの他のレイヤーではXと呼ばれます。」

やめたとき、この単純なCRUD APIは、1ページあたり30行で印刷した場合、棚に5〜100ページの教科書が10〜20冊必要になる段階だったと思います。繰り返しの完全な百科事典でした。コード。本質的な複雑さに関しては、本質的な複雑さの教科書の半分もそこにあったかどうかはわかりません。それを処理するためのデータベース図は5〜6個しかありませんでした。これにわずかな変更を加えることは、マンモスの仕事であり、それがマンモスの仕事であることを学び、新しい機能を追加するのは非常に面倒なので、新しい機能を追加するために使用するボイラープレートテンプレートファイルが実際にありました。

そのため、各部分を非常に読みやすく、わかりやすくすることで、全体を非常に読みにくく、わかりにくくする方法を直接見てきました。これは、貪欲アルゴリズムが失敗する可能性があることを意味します。貪欲アルゴリズムを知っていますか? 「私はローカルで状況を最も改善するあらゆるステップを実行するつもりであり、それから私は自分が世界的に改善された状況にいると確信します。」多くの場合、これは美しい最初の試みですが、複雑な状況では失敗する可能性もあります。たとえば、製造業では、複雑な製造プロセスの特定の各ステップの効率を高めようとする場合があります-より大きなバッチを実行し、他のことに忙しくするために何もしていないように見える床の人々に叫びます-そしてこれは、システムの全体的な効率を損なうことがよくあります。

ベストプラクティス:DRYおよび長さを使用して電話をかける

(注:このセクションのタイトルは冗談のようなものです。誰かに「Xを実行する必要があるので、Xを実行する必要がありますベストプラクティスはそう言っている」と言っているとき、彼らは話をしていません。 SQLインジェクションやパスワードハッシュなどの何か-片側のベストプラクティス-したがって、ステートメントはその90%の時間で「Xを実行する必要があるそう言う」と翻訳できます。彼らはX 'ではなくXでより良い仕事をしたいくつかのビジネスからのいくつかのブログ記事を持っているかもしれませんが、一般的にあなたのビジネスがそのビジネスに似ているという保証はなく、一般的に他のビジネスからのより良い仕事をした他のいくつかの記事がありますXではなくX 'です。したがって、タイトルをあまり真剣に受け取らないでください。)

私がお勧めするのは、ジャック・ディーデリッヒによる Stop Writing Classes(youtube.com) と呼ばれる講演に基づいています。彼はその話でいくつかの素晴らしい点を指摘します:たとえば、クラスが2つのパブリックメソッドしかなく、そのうちの1つがコンストラクター/イニシャライザーである場合、クラスは実際には単なる関数であることがわかります。しかし、あるケースでは、彼は、「Muffin」が、組み込みのdictタイプのサブクラスである独自のクラス「MuffinHash」を宣言したため、トークのために文字列置換された仮想ライブラリがどのように宣言されたかについて話しているPythonがあります。実装は完全に空でした。誰かが「Python辞書にカスタム機能を追加する必要があるかもしれません。今すぐ抽象化を導入しましょう。場合。"

そして彼の反抗的な反応は、単に「必要な場合はいつでもそれを後から行うことができる」というものでした。

私達は時々私達が今現在よりも悪いプログラマーになりそうなふりをしているので、将来私達を幸せにするかもしれないある種の小さなことを挿入したいかもしれません。私たちは将来の私たちのニーズを予想しています。 「トラフィックが予想よりも100倍大きい場合、そのアプローチはスケーリングされないので、スケーリングするこのより難しいアプローチに先行投資を投入する必要があります。」非常に疑わしい。

そのアドバイスを真剣に受け止めた場合、「後の」時期を特定する必要があります。おそらく最も明白なことは、スタイル上の理由から、物の長さに上限を設けることでしょう。そして、残りの最良のアドバイスは、SOLIDの原則にある穴をパッチするために線の長さに関するこれらのヒューリスティックを使用して、DRYを使用することです。自分自身を繰り返さないでください。30のヒューリスティックに基づいています。行はテキストの「ページ」であり、散文との類似、

  1. コピー/貼り付けしたい場合は、チェックを関数/メソッドにリファクタリングします。時々コピーペーストする正当な理由がありますが、それについて常に汚れているべきです。実際の作者は、彼らが本当にテーマを強調しようとしているのでない限り、物語全体を通してあなたが大きな長い文章を50回読み直すことはありません。
  2. 関数/メソッドは、理想的には「段落」である必要があります。ほとんどの関数は、ページの長さの約半分、つまり1〜15行のコードである必要があり、たった10%の関数だけが1ページと半分、45行以上の範囲になるようにする必要があります。 120行以上のコードとコメントになったら、その部分を分割する必要があります。
  3. ファイルは理想的には「章」である必要があります。ほとんどのファイルは12ページ以下の長さにする必要があるため、コードとコメントは360行です。たぶんあなたのファイルの10%だけが50ページの長さ、または1500行のコードとコメントの範囲を許可されるべきです。
  4. 理想的には、ほとんどのコードは関数のベースラインまたは1レベルの深さでインデントする必要があります。 Linuxソースツリーに関するいくつかのヒューリスティックに基づいて、信心深い場合、コードの10%だけがベースライン内で2レベル以上インデントされ、5レベル未満が3レベル以上インデントされるべきです。これは特に、大きなtry/catchでのエラー処理など、他の懸念事項を「ラップ」する必要があるものは、実際のロジックから除外する必要があることを意味します。

先ほど触れたように、これらの統計を現在のLinuxソースツリーに対してテストして、これらのおおよそのパーセンテージを見つけましたが、それらはまた、文学の類推である程度の合理的な理由です。

1
CR Drost

現在、クラスContactを使用していると仮定します。メールアドレスの検証に別の方法を書いているという事実は、クラスContactが単一の責任を処理していないという事実の証拠です。

それはまた、理想的には独自のクラスである必要がある、いくつかの電子メールの責任も処理しています。


さらに、コードがContactクラスとEmailクラスの融合であることの証明は、電子メール検証コードを簡単にテストできないことです。適切な値を持つ大きなメソッドでメール検証コードに到達するには、多くの操作が必要になります。以下のメソッドvizを参照してください。

_private void LargeMethod() {
    //A lot of code which modifies a lot of values. You do all sorts of tricks here.
    //Code.
    //Code..
    //Code...

    //Email validation code becoming very difficult to test as it will be difficult to ensure 
    //that you have the right data till you reach here in the method
    ValidateEmail();

    //Another whole lot of code that modifies all sorts of values.
    //Extra work to preserve the result of ValidateEmail() for your asserts later.
}
_

一方、メール検証用のメソッドを持つ別のメールクラスがある場合、検証コードを単体テストするには、テストデータを使用してEmail.Validation()を呼び出すだけです。


ボーナスコンテンツ:MFeatherの講演 テスト容易性と優れた設計の深い相乗効果について。

1
displayName