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


106

モデレーターノート
この質問には、すでに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つのメソッドといくつかのコメントを持つ単一のクラスである可能性があります。


98
組織がコードベースのメトリックとしてLOC のみを使用している場合、クリーンなコードを正当化することはまず望みません
キリアンフォス

24
保守性が目標である場合、LOCは判断するのに最適な指標ではありません。LOC の1つですが、単純に短くすることよりもはるかに多くのことを考慮する必要があります。
ジボブズ

29
答えではありませんが、指摘する必要があります。可能な限り少ない行/シンボルでコードを記述することに関して、サブコミュニティがあります。codegolf.stackexchange.com答えのほとんどは、読みやすいほどではないと主張することができます。
アンティテオス

14
ルールだけでなく、すべてのベストプラクティスの背後にある理由を学びます。理由なく規則に従うことはカーゴカルトです。すべてのルールには独自の理由があります。
ガーマン

9
余談ですが、例を使用して、メソッドに物事をプッシュすることで、「これを実行できるライブラリ関数があるかもしれません」と思うようになります。たとえば、メールアドレスを検証するには、System.Net.Mail.MailAddressを作成して、それを検証します。その後、(できれば)そのライブラリの作成者を信頼して、それを正しくすることができます。これは、コードベースの抽象化が増え、サイズが小さくなることを意味します。
グレゴリーカリー

回答:


130

...私たちは比較的大きなドキュメント化されていないコードベース(継承したもの)をサポートする非常に小さなチームです。そのため、開発者/マネージャーの中には、より少ないコードを書いて物事を成し遂げる価値があると考えているため、メンテナンスするコードが少なくなっています

これらの人々は何かを正しく特定しました。彼らはコードの保守を簡単にしたいのです。しかし、彼らが間違っているのは、コードが少ないほど維持しやすいと仮定していることです。

コードを保守しやすくするには、変更が簡単である必要があります。変更しやすいコードを達成するための最も簡単な方法は、変更が破壊的なものである場合に失敗する自動テストの完全なセットを用意することです。テストはコードであるため、これらのテストを書くとコードベースが大きくなります。そしてそれは良いことです。

第二に、変更が必要なものを解決するために、コードは読みやすく、推論しやすいものでなければなりません。行カウントを抑えるためだけにサイズを縮小した非常に簡潔なコードは、読みやすくなることはほとんどありません。長いコードを読むのに時間がかかるため、明らかに妥協点があります。しかし、理解するのが早い場合、それだけの価値があります。それがその利点を提供しない場合、その冗長性は利点でなくなります。しかし、コードが長いほど読みやすさが向上する場合、これもまた良いことです。


27
「変更が容易なコードを達成する最も簡単な方法は、変更が破壊的なものである場合に失敗する自動テストの完全なセットを用意することです。」これは単に真実ではありません。テストは変更する必要があるため、すべての動作の変更に対して追加の作業が必要です。これは仕様によるものであり、多くの人は変更をより安全にすると主張しますが、変更を必然的に難しくします。
ジャックエイドリー

63
確かに、これらのテストを維持するために失われる時間は、テストによって防止されるバグの診断と修正を失うことになるため、d小になります。
メタファイト

29
@JackAidley、コードとともにテストを変更する必要があるため、より多くの作業の外観が得られる可能性がありますが、テストされていないコードへの変更が導入し、出荷後まで発見されないことが多い見つけにくいバグを無視する場合のみ。後者は、作業量が少ないという幻想を提供するだけです。
デビッドアルノ

31
@JackAidley、私はあなたに全く同意しません。テストにより、コードの変更が容易になります。あまりにも密に結合されており、したがってテストに密接に結合された不適切に設計されたコードは変更するのが難しい場合がありますが、適切に構造化され、十分にテストされたコードは私の経験では簡単に変更できますが、私は認めます。
デビッドアルノ

22
@JackAidley APIやインターフェイスを変更せずに、多くのリファクタリングを行うことができます。つまり、単体テストまたは機能テストの1行を変更することなく、コードを変更しながら夢中になります。つまり、テストが特定の実装をテストしない場合です。
エリックドゥミニル

155

はい、それは許容できる副産物であり、ほとんどのコードをほとんど読む必要がないように構成されているという正当性があります。変更を行うたびに30行の関数を読み取る代わりに、5行の関数を読み取って全体的なフローを取得します。変更がその領域に触れる場合は、いくつかのヘルパー関数を読み取ります。新しい「余分な」クラスが呼び出されEmailValidator、問題が電子メールの検証にないことがわかっている場合は、すべてを読むことをスキップできます。

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

そして、メール検証ルールを変更する必要がある場合に何を行う必要があるかを検討します。またはいくつかの場所が欠落している可能性がありますか?


10
退屈な「単体テストがすべての問題を解決する」よりもはるかに良い答え
Dirk Boer

13
この答えは、ボブおじさんや友人がいつも見逃しているように見えるキーポイントに当たります。小さなメソッドにリファクタリングするのは、コードが何をしているのかを理解するためにすべての小さなメソッドを読む必要がない場合にのみ役立ちます。電子メールアドレスを検証するために別のクラスを作成するのが賢明です。iterations < _maxIterations呼ばれるメソッドにコードを引っ張ることShouldContinueToIterate愚かです。
BJマイヤーズ

4
@DavidArno:「役に立つ」!=「すべての問題を解決する」
クリスチャン・ハックル

2
@DavidArno:ユニットテストが「すべての問題を解決する」ことを暗示している人々に文句を言うとき、それは明らかに、ユニットテストがソフトウェアエンジニアリングのほとんどすべての問題を解決するか、少なくとも解決に貢献することを暗示する人々を意味します。戦争、貧困、病気を終わらせる方法としてユニットテストを提案していると非難する人はいないと思います。別の言い方をすれば、この質問に対するだけでなく、SE全般に対する多くの回答での単体テストの極端な過大評価が(正当に)批判されているということです。
クリスチャンハックル

2
こんにちは、@ DavidArno、私のコメントは明らかにストローマンではなく誇張でした;)私にとってはこのようなものです:私は自分の車を修理し、宗教の人々がやって来て、罪の少ない生活を送るべきだと言う方法を尋ねています。理論的には議論する価値のあるものですが、車を修理する上で私が良くなるのを本当に助けているわけではありません。
ダークボー

34

ビル・ゲイツは、「コード行でプログラミングの進捗を測定することは、航空機の建物の進捗を重量で測定するようなものです」と言われたことで有名です。

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

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

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

この場合、明らかにあなたはそれをしていないので、あなたがやっているように続けることをお勧めします。変更を加えることで読みやすくなるかどうかを常に自問する必要があります。その場合は、それを実行してください。


1
ただし、航空機の重量は重要な指標です。また、設計中、予想される重量が厳密に監視されます。進歩の兆候としてではなく、制約として。コード行を監視することは、航空機の設計では重量が少ないほど優れていることを示唆しています。だから私は、ミスター・ゲイツが彼の主張のためにより良いイラストを選んだと思う。
ジョス

21
特定のチームOPの@josが作業している場合、LOCが少ないと「より良い」と見なされるようです。ビル・ゲイツ氏が作っていた点は、LOCがされていることである関連していないだけで航空機構造重量が有意義な方法で進行に関連していないのように、意味のある形で進行します。建設中の航空機は、最終重量の95%が比較的迅速に得られる場合がありますが、制御システムを持たない空のシェルであり、95%完成しているわけではありません。ソフトウェアでも同じですが、プログラムに10万行のコードがある場合、1000行ごとに機能の1%が提供されるわけではありません。
ミンダミドル

7
進行状況の監視は大変な仕事ですね。貧しいマネージャー。
ジョス

@jos:他のすべてが等しい場合、コード内の同じ機能の行数を少なくすることをお勧めします。
RemcoGerlich

@jos注意深く読んでください。ゲイツは、重量が航空機自体にとって重要な尺度であるかどうかについては何も語っていません。彼は、飛行機の建造の進行を計るには体重がひどい尺度と言います。結局のところ、船体全体を地面に投げるとすぐにその測定によって、飛行機の重量の9x%に相当すると思われるので、基本的には完了です。
Voo

23

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

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

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

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


ロギング

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

しかし、多くの時間が、問題とバグの調査、および何が悪かったのかを解明することに費やされます。

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

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

これは、数分、数時間または数日節約できます。コードベースのサイズと複雑さに依存します。


回帰

DRYフレンドリーではないアプリケーションAを使用します。
DRYのアプリケーションBを使用しますが、抽象化が追加されたため、より多くの行が必要になりました。

変更要求が提出されますが、これにはロジックの変更が必要です。

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

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

  • 彼がすべてのインスタンスを覚えることができたとしても、同じ変更を数回実装する必要があります。
  • 彼がすべてのインスタンスを思い出せない場合、矛盾するコードベースに対処していることになります。開発者がめったに使用されないコードを忘れた場合、このバグは将来に至るまでエンドユーザーに明らかにならない可能性があります。その時点で、エンドユーザーは問題の原因を特定するつもりですか?たとえそうだとしても、開発者は変更の結果を覚えていない可能性があり、この忘れられたロジックを変更する方法を見つけ出す必要があります。開発者はそれまでに会社で働いていなかったのかもしれませんし、それから他の誰かがゼロからすべてを把握しなければなりません。

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


開発者の互換性

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

開発者Aは休日のために1か月間欠席しています。緊急変更要求が提出されます。開発者Aが戻ってくるまであと3週間待つことはできません。

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

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

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

開発者Aは彼の休暇から戻ってきて、Bがコードを理解しなかったため、実装がうまくいかなかったことを確認します。彼は利用可能なすべてのリソースを使用したため、Bのせいではありません。ソースコードが適切に読めなかったからです。Aはコードの可読性を修正するのに時間を費やす必要がありますか?


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

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

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

私のgotoの説明は、3か月で開発できる100KLOCコードベース、または6か月で開発できる50KLOCコードベースを持つアプリケーションを管理者に望んでいます。

経営陣はKLOCを気にしないので、明らかに短い開発時間を選択します。KLOCに注力するマネージャーは、管理しようとしていることについて知らされていない間、マイクロ管理しています。


23

全体的な複雑さが増す場合に備えて、「クリーンなコード」の実践を非常に慎重に適用すべきだと思います。時期尚早のリファクタリングは多くの悪いことの根源です。

関数から条件を抽出すると条件がから抽出された時点でコードが単純になりますが、プログラム内のより多くの点から見える関数があるため、全体的な複雑さが増します。この新しい関数が表示される他のすべての関数に、わずかな複雑さの負担を追加します。

必要な場合は慎重に検討する必要があるというだけで、条件を抽出するべきではないということではありません

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

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

疑わしい場合は、常に最も単純で最も簡単なコードを選択することをお勧めします。


7
同意する必要があります。すべての条件を検証方法に変えると、メンテナンスやコードのレビューに関して、より望ましくない複雑さが生じる可能性があります。ここで、条件付きメソッドが正しいことを確認するために、コードを前後に切り替える必要があります。また、同じ値に対して異なる条件がある場合はどうなりますか?これで、一度しか呼び出されず、ほとんど同じように見えるいくつかの小さなメソッドを使用したネーミングの悪夢が生じるかもしれません。
pboss3010

7
ここで簡単にベストアンサー。特に、(3番目の段落の)複雑さは、単にコード全体のプロパティではなく、複数の抽象化レベルに同時に存在し、異なるものであるという観察結果です。
クリスチャンハックル

2
これを設定する1つの方法は、一般に、条件の抽出は、その条件に意味のある難読化されていない名前がある場合にのみ実行する必要があることだと思います。これは必要ですが、十分ではありません。
ジミージェームズ

「...あなたは今のプログラムでより多くのポイントから見える機能があるため」に:パスカル -地元の機能を持つことが可能です」...各プロシージャまたは関数は後藤ラベルの独自の宣言を持つことができますが、定数、型、変数、およびその他の手続きと関数、...」
Peter Mortensen

2
@PeterMortensen:C#とJavaScriptでも可能です。そしてそれは素晴らしいことです!しかし、ポイントは残ります。関数は、ローカル関数であっても、インラインコードフラグメントよりも大きなスコープで見ることができます。
JacquesB

9

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

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

少なくともこれが一度使用されたと仮定します。

私はこれで非常に簡単に問題を抱えている可能性があります:

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

私が見たいいくつかのこと:

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

それが一度使用されている場合、解析するのは簡単であり、1行未満しかかかりません。次に決定を推測します。チームからの特定の問題でない場合、おそらく私はそれを呼び出すことはありません。

一方、私はメソッドが次のようなことをするのを見ました:

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 
{

}

3
Whether the comments above an if statement or inside a tiny method is to me, pedantic.これは「ラクダの背中を壊したstra」の問題です。あなたは、このことを完全に読むことは特に難しくないことは正しいです。あなたはこれらの小さな評価の数十を持っている大きな方法(例えば大輸入)、読み込み可能なメソッド名にカプセル化これらを持つを持っている場合でも、( 、、IsUserActive 、...)のコードを読んで顕著な改善となります。この例の問題は、ラクダの背中を折るバンドル全体ではなく、1本のストローしか観察しないことです。GetAverageIncomeMustBeDeleted
フラット

@Flaterと私は、これが読者の精神であることを願っています。
AthomSfere

1
この「カプセル化」はアンチパターンであり、答えは実際にこれを示しています。デバッグの目的とコードの拡張の目的で、コードを読みに戻ります。どちらの場合も、コードが実際に行うことを理解することが重要です。コードブロックの開始if (contact.email != null && contact.email.contains('@'))はバグです。ifがfalseの場合、else if行はどれもtrueにできません。これはLooksSortaLikeAnEmailブロックではまったく見えません。1行のコードを含む関数は、その行がどのように機能するかを説明するコメントよりも優れています。
気まぐれ

1
せいぜい、別のインダイレクション層が実際のメカニズムを覆い隠し、デバッグを難しくします。最悪の場合、関数名はコメントが嘘と同じように嘘になりました-内容は更新されますが、名前はそうではありません。これは、一般的なカプセル化に対するストライキではありませんが、この特定のイディオムは、「エンタープライズ」ソフトウェアエンジニアリングに関する重要な現代の問題、つまり関連ロジックを埋める抽象化層と接着層の兆候です。

@quirkあなたは私の全体的なポイントに同意していると思いますか?接着剤を使用すると、まったく別の問題が発生します。新しいチームコードを見るとき、実際にコードマップを使用します。mvcパターンレベルでさえ、一連の大きなメソッドを呼び出すいくつかの大きなメソッドに対して私が見たことは恐ろしいことです。
AthomSfere

6

Clean Codeは優れた本であり、読む価値がありますが、そのような問題に関する最終的な権限ではありません。

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

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

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

if (isEmailValid) {
...

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

もう1つの問題は、Clean Codeが本としてかなり古くなっていることです。多くのソフトウェアエンジニアリングは関数型プログラミングの方向に向かっていますが、Martinは物事に状態を追加し、オブジェクトを作成するために彼の邪魔をしません。もし彼が今日それを書いていたら、彼はまったく違う本を書いていたと思う。


条件の近くに余分なコード行があることを心配する人もいます(私はまったくそうではありません)が、おそらくあなたの答えでそれに対処しています。
ピーターモーテンセン

5

あなたが現在持っている「電子メールが有効」条件が非常に無効な電子メールアドレス「@」を受け入れるという事実を考えると、EmailValidatorクラスを抽象化する理由はすべてあると思います。さらに良いことに、十分にテストされた適切なライブラリを使用して、電子メールアドレスを検証します。

メトリックとしてのコード行は無意味です。ソフトウェアエンジニアリングの重要な質問は次のとおりではありません。

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

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

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

Code Golf以外の目的でコードを作成するとき、LoCを考えたことはありません。「これをもっと簡潔に書くことができますか?」と自問しましたが、単に長さではなく、読みやすさ、保守性、効率性のために。

確かに、ユーティリティメソッドの代わりにブール演算の長いチェーンを使用できるかもしれませんが、私はすべきでしょうか?

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


3

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

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

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

最も安価で、最速で、最も信頼性の高いコンポーネントは、そこにないものです。-ゴードン・ベル

要するに、持っているコードが少なければ少ないほど間違ってしまう可能性があります。何か必要ない場合は、カットします。
複雑すぎるコードがある場合は、実際の機能要素がすべて残るまで単純化します。

ここで重要なのは、これらすべてが機能を指し、それを行うために最低限必要なものだけを持っているということです。それがどのように表現されているについては何も言わない。

クリーンなコードを作成しようとして何をしているのは、上記に反するものではありません。LOCに追加していますが、未使用の機能は追加していません。

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

比metaは車を作ることです。コードの機能部分は、シャーシ、エンジン、ホイール...車を動かすものです。それをどのように分解するかは、サスペンション、パワーステアリングなどに似ており、扱いやすくなります。物事がうまくいかない可能性を最小限に抑えるために、仕事をしながら、メカニックをできるだけシンプルにしたいのですが、それはあなたがいい席を持つことを妨げません。


2

既存の回答には多くの知恵がありますが、もう1つの要素、言語を追加したいと思います

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

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

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

(これは、コードの「表面」の複雑さと、それが実装するアイデア/モデル/処理の複雑さとの違いを強調しています。コードの行は最初の悪い指標ではありませんが、2番目のコードとはあまり関係ありません)

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

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


1

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()は、テストデータを使用して1つの単純な呼び出しを行うだけです。


ボーナスコンテンツ: テスト容易性と優れたデザインの間の深い相乗効果に関するMFeatherの講演


1

LOCの減少は、欠陥の減少と相関があることがわかっています。その場合、LOCを下げると、欠陥が本質的に相関関係が因果関係に等しいと思われるというtrapに陥る可能性が低くなります。LOCの削減は、優れた開発慣行の結果であり、コードの良さをもたらすものではありません。

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

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

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


1

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

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

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

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

複雑さにより、特定の意味でコードが読みやすくなるのではなく読みにくくなりました。以前の仕事では、非常に慎重かつ正確に複数の層に構造化されたHTTP APIを扱いました。すべてのエンドポイントは、着信メッセージの形状を検証し、「ビジネスロジック層」マネージャーに渡すコントローラーによって指定されます、その後、「データアクセスオブジェクト」層にいくつかのクエリを実行する「データ層」を要求し、実際にあなたの質問に答えるいくつかのSQLデリゲートを作成します。最初に言えることは、コードの90%がコピーアンドペーストボイラープレートでした。つまり、何もしませんでした。したがって、多くの場合、コードの特定の部分を読み取ることは非常に「簡単」でした。「ああ、このマネージャーはリクエストをそのデータアクセスオブジェクトに転送するだけだからです」。多くのコンテキストスイッチングとファイルの検索、追跡してはならない情報の追跡を試みます。「これはこのレイヤーではXと呼ばれ、この他のレイヤーではX 'と呼ばれ、次にXと呼ばれます」他のレイヤー。」

私が辞めたとき、このシンプルなCRUD APIは、ページあたり30行で印刷すると、棚に10〜20ページのテキストブックが必要になる段階にあったと思います。それは繰り返しの百科事典全体でしたコード。本質的な複雑さに関しては、そこに本質的な複雑さの教科書の半分さえあったかどうかはわかりません。それを処理するためのデータベースダイアグラムは5〜6個しかありませんでした。それにわずかな変更を加えることは巨大な仕事であり、それが巨大な仕事であることを学び、新しい機能を追加することは非常に苦痛になり、新しい機能を追加するために使用するボイラープレートテンプレートファイルが実際にありました。

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

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

(注:このセクションのタイトルはやや冗談です;私はよく友人に「ベストプラクティスがそう言っているので Xをするべきだ」と言っているとき、彼らはSQLインジェクションやパスワードハッシュのようなものについて話していない時間の90%であるとしばしば言いますまたは、一方的なベストプラクティス-そして、その文はその90%の時間に「私はそう言うので Xをすべきです」と翻訳することができます。 X 'ではなくX'を使用しますが、一般的にあなたのビジネスがそのビジネスに似ているという保証はありません。また、一般に、XではなくX 'でより良い仕事をした他のビジネスからの他の記事があります。真剣に。)

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

そして、彼の反抗的な反応は、「必要ならいつでもいつでもできる」というものでした。

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

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

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

すでに述べたように、これらの統計値を現在のLinuxソースツリーと比較してテストし、おおよその割合を見つけましたが、文学の類推でも理にかなっています。

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