上司から、小さな関数の作成をやめて、同じループですべてを実行するように頼まれました


209

Clean Codeという本を読みましたRobert C. Martinによる。この本では、小さな関数の作成、名前の慎重な選択など、コードをクリーンアップするための多くの方法を見てきました。しかし、今日、上司はこの本を読んだ後のコードの書き方が好きではありませんでした。

彼の議論は

  • 小さな関数を書くと、コードが何をしているのかを見るために各小さな関数に移動しなければならないので苦痛です。
  • メインループが300行を超える場合でも、すべてをメインの大きなループに入れると、読みやすくなります。
  • コードを複製する必要がある場合にのみ、小さな関数を作成してください。
  • コメントの名前を使用して関数を記述しないでください。上記のコメントを使用して複雑なコード行(3〜4行)を入力してください。同様に、失敗したコードを直接変更できます

これは私が読んだすべてのものに反しています。通常、どのようにコードを記述しますか?1つの大きなループ、小さな関数はありませんか?

私が使用する言語は主にJavascriptです。明確に名前が付けられた小さな関数をすべて削除し、すべてを大きなループに入れたので、今は本当に読みにくいです。しかし、上司はこの方法が好きです。

一例は次のとおりです。

// The way I would write it
if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

// The way he would write it
// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

たとえば、私が読んだ本では、コメントはきれいなコードを書くのに失敗したとみなされます。なぜなら、小さな関数を書くと時代遅れであり、しばしばコメントが更新されないからです(コメントではなくコードを修正します)。しかし、私がしていることはコメントを削除し、コメントの名前で関数を書くことです。

さて、私はいくつかのアドバイスが欲しいのですが、クリーンなコードを書くにはどの方法/練習が良いですか?



4
phoneNumber = headers.resourceId?:DEV_PHONE_NUMBER;
ジョシュア

10
解決する必要があるのではなく、管理者があなたの仕事をする方法を教えてくれた場所で働きたいことを検証します。
コンスタンチンペトルフノフ

8
@rjmunro仕事が本当に好きでない限り、仕事よりも開発者が少ないことに留意してください。マーティン・ファウラーを引用すると、「組織を変更できない場合は、組織を変更してください!」そして、コードの書き方を教えてくれるボスは、あなたが変えたいと思うアドバイスです。
ニールスファンレイマースダール

10
isApplicationInProduction()機能を持たないでください!テストが必要であり、コードが実稼働時と異なる動作をする場合、テストは役に立ちません。それは、実稼働環境でテストされていない/発見されていないコードを意図的に持っているようなものです。意味がありません。
ローナンパイサン

回答:


215

最初にコード例を取り上げます。あなたが好む:

if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

そして、あなたの上司はそれを

// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

私の見解では、どちらにも問題があります。あなたのコードを読んだとき、私の目下の考えは「それifを三項式に置き換えることができる」というものでした。それから私はあなたの上司のコードを読み、「なぜ彼はあなたの機能をコメントに置き換えたのですか?」と考えました。

最適なコードは2つの間にあることをお勧めします。

phoneNumber = isApplicationInProduction(headers) ? headers.resourceId : DEV_PHONE_NUMBER;

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

これにより、両方の長所が得られます。単純化されたテスト式とコメントはテスト可能なコードに置き換えられます。

ただし、コード設計に関する上司の見解について:

小さな関数を記述すると、コードが何をしているのかを見るために各小さな関数に移動する必要があるため、苦痛です。

関数の名前が適切である場合、これは当てはまりません。 isApplicationInProduction自明であり、それが何をするかを見るためにコードを調べる必要はないはずです。実際、逆のことが当てはまります。コードを調べると、関数名よりも意図が明らかになりません(上司がコメントに頼らなければならない理由です)。

メインループが300行を超える場合でも、すべてをメインの大きなループに入れると、読み取りが速くなります。

スキャンする方が高速かもしれませんが、コードを本当に「読み取る」ためには、頭の中でコードを効果的に実行できる必要があります。これは小さな関数では簡単で、100行の長さのメソッドでは非常に困難です。

コードを複製する必要がある場合は、小さな関数のみを記述してください

同意しません。あなたのコード例が示すように、小さくてよく名前の付いた関数はコードの可読性を改善します。例えば、機能の「どのように」ではなく「何にだけ」興味がないときはいつでも使用すべきです。

コメントの名前を使用して関数を作成しないでください。上記のコメントを使用して、複雑なコード行(3〜4行)を入力してください。このように、失敗したコードを直接変更できます

これが本当に深刻だと仮定すると、私はこの理由を本当に理解できません。それは、The Expert Beginnerによるパロディで書かれたものです。 twitterアカウントです。コメントには根本的な欠陥があります。それらはコンパイル/解釈されないため、ユニットテストができません。コードが変更され、コメントがそのまま残され、どちらが正しいかわからなくなります。

自己文書化コードの作成は難しく、補足ドキュメント(コメントの形式であっても)が必要になる場合があります。しかし、コメントがコーディングの失敗であるという「ボブおじさん」の見解は、ほとんどの場合当てはまります。

上司にClean Codeブックを読んでもらい、彼を満足させるためだけにコードを読みにくくしないようにしましょう。最終的には、彼に変更を説得できない場合は、列に並ぶか、コードを改善できる新しいボスを見つける必要があります。


26
小さな機能は簡単にユニットテストされます
Mawg

13
Quoth @ ExpertBeginner1:「コードのいたるところにたくさんの小さなメソッドが表示されるのにうんざりしているので、今後はすべてのメソッドに最低15 LOCがあります。」
グレッグベーコン

34
「コメントには根本的な欠陥があります。コンパイル/解釈されていないため、ユニットテストができません。」ここで悪魔の擁護者を演じ、これは「コメント」を「関数名」に置き換えた場合も同様です。
マットカプ

11
@mattecapu、私はあなたの擁護を取り上げ、それをあなたにすぐに倍増させます。古いゴミの開発者なら誰でも、コードが何をするのかを説明しようとするコメントでワッフルできます。適切な関数名でコードを簡潔に説明するには、熟練したコミュニケーターが必要です。コードを書くことは主に他の開発者との通信に関係しており、コンパイラーは二次的な懸念事項であるため、最高の開発者は熟練したコミュニケーターです。優れた開発者であるエルゴは、適切な名前の関数を使用し、コメントは使用しません。貧しい開発者は、コメントを使用するための言い訳の後ろに彼らの貧しいスキルを隠します。
デビッドアルノ

4
@DavidArnoすべての関数には事前条件と事後条件があり、問題はそれらを文書化するかどうかです。私の関数が測定されたフィート単位の距離であるパラメーターを取る場合、キロメートル単位ではなくフィート単位で指定する必要があります。これは前提条件です。
ヨルゲンフォグ

223

他の問題があります

どちらも基本的にコードをデバッグテストケースで膨張させるため、どちらのコードも適切ではありません。何らかの理由でもっと多くのものをテストしたい場合はどうしますか?

phoneNumber = DEV_PHONE_NUMBER_WHICH_CAUSED_PROBLEMS_FOR_CUSTOMERS;

または

phoneNumber = DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY;

さらにブランチを追加しますか?

重要な問題は、基本的にコードの一部を複製するため、実際のコードを実際にテストしていないことです。デバッグコードを作成してデバッグコードをテストしますが、製品コードはテストしません。並列コードベースを部分的に作成するようなものです。

悪いコードをもっと巧みに書く方法について上司と議論しています。代わりに、コード自体の固有の問題を修正する必要があります。

依存性注入

コードは次のようになります。

phoneNumber = headers.resourceId;

ここのロジックには分岐がないため、ここには分岐がありません。プログラムは、ヘッダーから電話番号を取得する必要があります。期間。

DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY結果として欲しい場合は、に入れる必要がありheaders.resourceIdます。それを行う1つの方法はheaders、テストケースに別のオブジェクトを単純に挿入することです(これが適切なコードではない場合、申し訳ありませんが、私のJavaScriptスキルは少し錆びています)。

function foo(headers){
    phoneNumber = headers.resourceId;
}

// Creating the test case
foo({resourceId: DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY});

それheadersがサーバーから受信する応答の一部であると仮定すると:理想的には、headersテスト目的でさまざまな種類を提供するテストサーバー全体があります。この方法では、実際の製品コードを現状のままテストし、製品コードのように機能する場合と機能しない場合がある半分重複したコードをテストしません。


11
私は自分の答えでこのトピックに取り組むことを検討しましたが、すでに十分に長いと感じました。そうすることであなたに+1します:)
デビッドアルノ

5
@DavidArno質問へのコメントとして追加しようとしていました。質問を最初に読んだときにはまだロックされていたので、驚いたことに再び開いて、答えとして追加しました。自動テストを行うためのフレームワーク/ツールが多数あることを追加する必要があります。特にJSでは、新しいものが毎日出てくるようです。それを上司に売るのは難しいかもしれません。
nullの

56
@DavidArnoたぶん、あなたはあなたの答えをより小さな答えに分割すべきだったでしょう。;)
krillgar

2
@ user949300ビット単位のORを使用するのは賢明ではありません;)
curiousdannii

1
@curiousdanniiええ、編集するには遅すぎることに気づいた
...-user949300

59

これに対する「正しい」または「間違った」答えはありません。ただし、ソフトウェアシステムの設計と開発の36年間の専門的な経験に基づいて意見を述べます...

  1. 「自己文書化コード」などはありません。どうして?その主張は完全に主観的だからです。
  2. コメントは決して失敗ではありません。失敗と、それなしではまったく理解できないコードですコメント。
  3. 1つのコードブロックで300行の連続したコードがメンテナンスの悪夢であり、エラーが発生しやすい。このようなブロックは、設計と計画が不適切であることを強く示しています。

あなたが提供した例に直接話す... isApplicationInProduction()独自のルーチンに配置することは賢いことです。今日、このテストは単に「ヘッダー」のチェックであり、三項?:演算子()で処理できます。明日、テストはもっと複雑になるかもしれません。また、「headers.resourceId」は、アプリケーションの「本番ステータス」と明確な関係がありません。そのようなステータスのテストは、基礎となるデータから切り離す必要があると私は主張します。サブルーチンはこれを行いますが、3項は行いません。さらに、resourceIdが「実稼働中」のテストである理由から役立つコメントがあります。

「小さく明確に名前が付けられた関数」で行き過ぎないように注意してください。ルーチンは、「単なるコード」よりもアイデアをカプセル化する必要があります。私はamonの提案をサポートし、「プロダクションステータス」テストを行う必要があるphoneNumber = getPhoneNumber(headers)ことを追加しgetPhoneNumber()ます。isApplicationInProduction()


25
失敗ではない良いコメントのようなものがあります。ただし、コメントはコードがほぼ逐語的であるか、メソッド/クラス/などの前にある単なる空のコメントブロックです。間違いなく失敗です。
jpmc26

3
それが何をするのか、それが処理し、処理しないコーナーケースの英語の説明よりも小さくて読みやすいコードを持つことが可能です。さらに、関数が独自のメソッドに引き出された場合、関数を読んでいる人は、その呼び出し元が処理するコーナーケースまたは処理していないコーナーケースを知りません。また、関数の名前が非常に冗長でない限り、ケースは関数によって処理されます。
-supercat

7
コメントが本質的に失敗することはありません。コメントは失敗する可能性があり、コメントが不正確な場合に失敗します。ブラックボックスモードでの誤った動作を含め、複数のレベルで誤ったコードを検出できます。間違ったコメントは、2つのモデルが記述されており、そのうちの1つが間違っているという認識を通じて、ホワイトボックスモードでの人間の理解によってのみ検出できます。
ティンボ

7
@Timbo「... 少なくともそれらの1つが間違っています。」;)
jpmc26

5
あなたが理解できない場合は@immibis のコードはコメントせずに行い、その後、コードはおそらく十分に明確ではありません。コメントの主な目的は、コードが実行していることを実行している理由を明確にすることです。将来のメンテナーに彼の設計を説明するコーダーです。コードはそのような説明を提供できないため、コメントは理解のギャップを埋めます。
グラハム

47

「必要以上にエンティティを増やすことはできません。」

- オッカムのかみそり

コードはできるだけシンプルでなければなりません。バグは、そこに見つけるのが難しいため、複雑さの間に隠れることを好みます。それでは、コードを単純にするものは何ですか?

小さな単位(ファイル、関数、クラス)は良い考えです。一度に理解しなければならないものが少ないので、小さなユニットは理解しやすいです。普通の人間は、一度に〜7個のコンセプトしかジャグリングできません。しかし、サイズはコードの行で測定されるだけではありません。コードを「ゴルフ」することで、できるだけ少ないコードを書くことができます(短い変数名を選択し、「賢い」ショートカットを使用し、できるだけ多くのコードを1行にスマッシュします)が、最終結果は単純ではありません。そのようなコードを理解しようとすることは、読むことよりもリバースエンジニアリングに似ています。

関数を短縮する1つの方法は、さまざまなヘルパー関数を抽出することです。自己完結型の複雑な部分を抽出するとき、それは良い考えですです。単独では、その複雑な部分は、無関係な問題に埋め込まれている場合よりも管理(およびテスト)がはるかに簡単です。

しかし、すべての関数呼び出しには認識上のオーバーヘッドがあります。現在のコードのコードを理解するだけでなく、外部のコードと相互作用する方法も理解する必要があります。抽出した関数は、抽出する関数よりも複雑な関数になると言っていいと思います。それは、上司が「小さな機能」が意味することです。それは、コードが何をしているかを見るために各小さな機能に移動することを強制するためです。

時々 、長い退屈な機能は非常に単純なことができ、彼らは長い行の数百人であっても、理解すること。これは、ドラッグアンドドロップエディタなしで手動でGUIを作成する場合など、初期化および構成コードで発生する傾向があります。合理的に抽出できる複雑な自己完結型の部分はありません。しかし、書式設定が読みやすく、コメントがある場合は、実際に何が起こっているのかを追跡するのは難しくありません。

他にも多くの複雑さの指標があります。スコープ内の変数数は可能な限り少なくする必要があります。それは変数を避けるべきだという意味ではありません。これは、各変数を、必要な最小のスコープに制限する必要があることを意味します。また、変数に含まれる値を変更しないと、変数はより単純になります。

非常に重要なメトリックは、循環的複雑度(McCabe複雑度)です。コードを通る独立したパスの数を測定します。この数は、条件ごとに指数関数的に増加します。各条件付きまたはループは、パスの数を2倍にします。10ポイントを超えるスコアは複雑すぎることを示唆する証拠があります。つまり、スコアが5の非常に長い関数は、スコアが25の非常に短く密な関数よりも優れている可能性があります。制御フローを個別の関数に抽出することで、複雑さを軽減できます。

条件は、完全に抽出できる複雑さの一例です。

function bigFatFunction(...) {
  ...
  phoneNumber = getPhoneNumber(headers);
  ...
}

...

function getPhoneNumber(headers) {
  return headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;
}

これはまだ有用であるという端にまだあります。この条件付けはあまり条件付けられていないのでそれが大幅に複雑さを減らすかどうかはわかりません。本番環境では、常に同じパスを使用します。


複雑さは消えることはありません。シャッフルのみ可能です。多くの小さなものは、いくつかの大きなものよりも単純ですか?それは状況に大きく依存します。通常、適切な組み合わせがいくつかあります。異なる複雑さの要因の間で妥協点を見つけるには、直感と経験、そして少しの運が必要です。

非常に小さな関数と非常に単純な関数の書き方を知ることは有用なスキルです。なぜなら、選択肢を知ることなく選択をすることはできないからです。現在の状況にどのように適用するかを考えずに、ルールやベストプラクティス盲目的に順守すると、せいぜい平均的な結果になり、最悪の場合はカーゴカルトなプログラミングにつながります。

それは私があなたの上司に反対するところです。彼の議論は無効ではありませんが、Clean Codeの本も間違っていません。おそらく上司のガイドラインに従う方が良いでしょうが、これらの問題について考えているという事実は、より良い方法を見つけようとして非常に有望です。経験を積むにつれて、コードに適したファクタリングを見つけるのが簡単になります。

(注:この回答は、Jimmy HoffaよるThe WhiteboardのReasonable Codeブログ投稿の考えに一部基づいています。これは、コードを単純にするものに関する高レベルのビューを提供します。)


私はあなたの反応が好きでした。ただし、mcabesの循環的複雑度の測定には問題があります。私がそれを見たことから、それは複雑さの真の尺度を示していません。
ロバートバロン

27

Robert Martinのプログラミングスタイルは二極化です。経験豊富なプログラマを含む多くのプログラマーが、「それほど」分割するのが多すぎる理由と、関数を少し大きくすることが「より良い方法」である理由をたくさん見つけます。しかし、これらの「議論」のほとんどは、多くの場合、古い習慣を変え、新しい何かを学びたくないという表現です。

聞いてはいけません!

コードを表現力のある名前の別の関数にリファクタリングすることでコメントを保存できる場合は、いつでもそれを実行します。コードを改善する可能性が最も高いでしょう。ボブ・マーティンが彼のきれいなコードブックで行っているようには行きませんが、保守問題を引き起こした過去に見たコードの大部分は、小さすぎるものではなく、大きすぎる機能を含んでいます。したがって、自己記述的な名前で小さな関数を作成しようとするのは、試してみるべきです。

自動リファクタリングツールを使用すると、メソッドを簡単に、簡単に、安全に抽出できます。そして、300行以上の関数を書くことを勧める人を真剣に受け取らないでください。そのような人は、あなたがどのようにコーディングするべきかをあなたに伝える資格がありません。


53
「彼らの言うことを聞かないで!」:OPが上司からコードの分割を停止するように求められているという事実を考えると、OPはおそらくあなたのアドバイスを避けるべきです。上司が古い習慣を変えたくない場合でも。また、以前の回答で強調されているように、OPのコードと彼の上司のコードの両方がひどく書かれており、あなたはあなたの回答でそれを言及していないことに注意してください。
Arseni Mourzenko

10
@ArseniMourzenko:私たち全員が上司の前で座屈する必要はありません。OPが正しいことをしなければならないとき、または上司の言うことをしなければならないときを知るのに十分な年齢のOPであることを願っています。そして、はい、私は意図的にこの例の詳細には触れませんでした。それらの詳細について既に議論している他の十分な答えがあります。
ドックブラウン

8
@DocBrown同意しました。クラス全体で300行は問題です。300行関数はわいせつです。
ジミージェームズ

30
300行以上のクラスがたくさんありますが、これらは完全に良いクラスです。Javaは非常に冗長であるため、多くのコードがなければクラスで意味のあることはほとんどできません。したがって、SLOCがプログラマの生産性にとって意味のあるメトリックであると考える以上に、「クラス内のコードの行数」自体はすべて意味のあるメトリックではありません。
ロバートハーヴェイ

9
また、ボブおじさんの賢明なアドバイスが誤って解釈され、乱用されているのを見たことがあり、経験豊富なプログラマ以外の誰にも役立つのではないかと疑っています。
ロバートハーヴェイ

23

あなたの場合:電話番号が必要です。電話番号を取得する方法が明らかな場合は、明らかなコードを記述します。または、電話番号を取得する方法がわからない場合は、そのメソッドを作成します。

あなたの場合、電話番号を取得する方法は明らかではないため、そのためのメソッドを作成します。実装は明らかではありませんが、そのため、別のメソッドに実装するので、一度だけ処理する必要があります。実装が明らかではないため、コメントが役立ちます。

「isApplicationInProduction」メソッドはまったく意味がありません。getPhonenumberメソッドから呼び出すと、実装が明確にならず、何が起こっているのかがわかりにくくなります。

小さな関数を書かないでください。明確に定義された目的を持ち、その明確に定義された目的を満たす関数を作成します。

PS。私は実装がまったく好きではありません。電話番号がないことは開発版であることを前提としています。したがって、電話番号が本番環境にない場合は、それを処理するだけでなく、ランダムな電話番号に置き換えます。10,000人の顧客がいて、17人が電話番号を持っていなくて、本番で困っていると想像してください。生産中か開発中かは、他の何かから派生したものではなく、直接確認する必要があります。


1
「小さな関数を作成しないでください。明確に定義された目的を持ち、その明確に定義された目的を満たす関数を作成してください。」 それがコードを分割するための正しい基準です。関数が多すぎる(複数の)異種関数を実行する場合、それを分割します。 単一責任の原則が指針となる原則です。
ロバートブリストージョンソン

16

どちらの実装もそれほど優れていないという事実を無視しても、これは本質的には少なくとも単一使用の些細な機能を抽象化するレベルでの味の問題であることに注意するでしょう。

ほとんどの場合、行数は有用なメトリックではありません。

300行(または3000行)のまったく些細な純粋にシーケンシャルなコード(セットアップなど)が問題になることはほとんどありません(ただし、自動生成またはデータテーブルなどとして)、100行のネストされたループと多くの複雑なガウス消去法やマトリックス反転などで見られるような終了条件と数学は、簡単に従うには多すぎるかもしれません。

私にとって、物を宣言するために必要なコードの量が実装を形成するコードの量よりもはるかに少ない場合を除き、私は単一の使用関数を書きません(フォールトインジェクションを簡単にできるようにしたいという理由がない限り)。単一の条件付き条件がこの法案に適合することはほとんどありません。

今、私は小さなコア組み込み世界から来ています。そこでは、スタックの深さや呼び出し/リターンのオーバーヘッドなどを考慮する必要があります(ここでも提唱されているように見える小さな機能の種類に反対します)、これは私の設計にバイアスをかける可能性がありますしかし、コードレビューでその元の関数を見た場合、古いスタイルのusenetフレームが応答します。

味はデザインを教えることは難しく、実際に経験を積むだけであり、関数の長さに関する規則に減らすことができるかどうかはわかりません。また、循環的複雑さでもメトリックとして制限があります(あなたがそれらに取り組む場合でも、物事は単に複雑です)。
これは、クリーンなコードがいくつかの良いことを議論しないということではなく、これらのことを考慮する必要がありますが、ローカルのカスタムと既存のコードベースが行うことにも同様に重みを付ける必要があります。

この特定の例は些細なことを選んでいるように見えますが、システムを簡単に理解してデバッグする能力にとってははるかに重要なので、はるかに高いレベルのものに関心があります。


1
私は強く同意します-それを関数でラップすることを検討するには非常に複雑なワンライナーが必要です...私は確かに三値/デフォルト値の行をラップしません。ライナーを1つラップしましたが、通常は、シェルスクリプトで、何かを解析するために10本のパイプがあり、コードを実行しないと理解できません。
TemporalWolf

15

すべてを1つの大きなループに入れないでください。しかし、あまり頻繁にしないでください。

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

大きなループの問題は、多くの画面にまたがる場合、その全体的な構造を見ることは非常に難しいことです。そのため、大きなチャンク、理想的には単一の責任を持ち、再利用可能なチャンクを取り出してみてください。

上記の小さな機能の問題は、原子性とモジュール性は一般に優れていますが、それはあまりにも遠すぎます。上記の関数を再利用しない場合、コードの可読性と保守性が損なわれます。詳細にドリルダウンするには、詳細をインラインで読み取ることができるのではなく、関数にジャンプする必要があります。関数呼び出しは、詳細自体よりも少ないスペースをほとんど占有しません。

明らかに行う方法の間で発見されるバランスがありすぎてやると方法少なすぎるが。それは複数の場所から呼び出されることになるだろうし、その後も、私はそれについてよく考えない限り機能はちょうどので、私は、上記のように小さな機能を抜け出すことはないという実質的ではない新しいロジックを導入するという点でとしてそのようなものは、それが自身の存在であることをほとんど保証しません。


2
単一のブール値1ライナーは読みやすいことを理解していますが、それだけでは実際に「何が」起こっているのかを説明するだけです。関数の名前は、この条件チェックを実行する理由を説明するのに役立つため、単純な3項式をラップする関数をまだ作成しています。これは、新しい人(または6か月後の自分)がビジネスロジックを理解する必要がある場合に特に役立ちます。
AJX。16年

14

あなたが実際に欲しいのはこれです:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER

これは、それを読むすべての人にとって自明であるはずです。利用可能であれば設定phoneNumberし、resourceId利用できDEV_PHONE_NUMBERない場合はデフォルトに設定します。

実稼働環境でのみその変数を本当に設定したい場合は、実行元を決定するために、他のより標準的なアプリ全体のユーティリティメソッド(パラメーターを必要としない)が必要です。その情報のヘッダーを読むことは意味がありません。


それが何をしているのかは自明ですが(使用している言語を少し推測すると)、何が起こっているのかはまったく明らかではありません。どうやら、開発者はphoneNumberがプ​​ロダクションバージョンの「resourceId」の下に格納され、resourceIdが開発バージョンに存在せず、開発バージョンでDEV_PHONE_NUMBERを使用することを想定しているようです。タイトル、および製品版で電話番号が欠落していると事態がひどく悪くなることを意味します
gnasher729

14

率直に言って、環境(言語/フレームワーク/クラス設計など)が「クリーン」なコードにはあまり適していないように思えます。本当に近いはずのない数行のコードで、考えられるすべての種類のものを混ぜています。単一の機能が持つビジネスは、resourceId==undef生産中ではないこと、非生産システムでデフォルトの電話番号を使用していること、resourceIdがいくつかの「ヘッダー」などに保存されていることを知っていることです。headersHTTPヘッダーであると想定しているので、どの環境にいるかについての決定はエンドユーザーに任せますか?

その単一の要素を関数に分解しても、その根本的な問題にはあまり役立ちません。

探すべきキーワード:

  • 分離
  • 凝集
  • 依存性注入

コードの責任を変更し、最新のフレームワーク(環境/プログラミング言語に存在する場合も存在しない場合もあります)を使用することで、コードのゼロ行で(他のコンテキストで)望むものを実現できます。

あなたの説明(「メイン」関数の300行のコード)から、「メソッド」ではなく「関数」という言葉でさえ、達成しようとしていることには意味がないと思い込ませます。その昔ながらのプログラミング環境(つまり、構造がほとんどなく、確かに意味のあるクラスも、MVCなどのクラスフレームワークパターンもない基本的な命令型プログラミング)には、実際に何もする意味はありません。基本的な変更なしにサンプから抜け出すことはありません。少なくともあなたの上司は、コードの重複を避けるための関数を作成できるようです。これは良い第一歩です!

コードの種類と、あなたが説明しているプログラマの種類の両方をよく知っています。率直に言って、もしそれが同僚であれば、私のアドバイスは異なるでしょう。しかし、それはあなたの上司であるので、あなたがこれについて戦うことは無意味です。上司があなたを覆すことができるだけでなく、コードを追加すると、「自分のこと」を部分的に行うだけでコードが悪化し、上司(およびおそらく他の人)が以前のように続けます。(もちろん、この特定のコードベースで作業している間のみ)プログラミングのスタイルに適応し、このコンテキストでそれを最大限に活用しようとすると、最終結果が向上する可能性があります。


1
ここには分離すべき暗黙のコンポーネントがあることに100%同意しますが、言語/フレームワークについて詳しく知らなければ、OOアプローチが理にかなっているかどうかを知ることは困難です。デカップリングと単一の責任原則は、純粋な機能(例:Haskell)から純粋な命令(例:C)まで、どの言語でも重要です。アウトラインや目次など)宣言型スタイル(アルゴリズムではなくポリシーを記述)で読み取り、作業を他の機能にファームします。
デビッドレピク

JavaScriptはプロトタイプであり、一流の機能を備えています。それは本質的にオブジェクト指向ですが、古典的な意味ではないので、あなたの仮定は正しくないかもしれません。YouTubeでCrockfordのビデオを視聴するキュー時間...
ケビンキンジー

13

「クリーン」は、コードを記述する際の1つの目標です。それが唯一の目標ではありません。もう1つの価値のある目標は、共存性です。非公式に言えば、共局在性とは、コードを理解しようとする人々が、あなたが何をしているかを見るためにあちこち飛び回る必要がないことを意味します。三項式の代わりに適切な名前の関数を使用するのは良いことのように思えるかもしれませんが、そのような関数の数とその場所に応じて、この方法は迷惑になります。あなたがその境界線を越えたかどうかはわかりませんが、人々が不平を言っているなら、特に彼らがあなたの雇用状況について発言しているなら、あなたは耳を傾けるべきだと言うことを除いて。


2
「...人々が不平を言っている場合、特にそれらの人々があなたの雇用状況について発言権を持っている場合、あなたは耳を傾けるべきだと言うことを除いて」。IMOこれは本当に悪いアドバイスです。あなたが得ることができる仕事に感謝する必要がある真剣に貧しい開発者でない限り、常に「あなたの仕事を変えることができないなら、あなたの仕事を変える」原則を適用してください。会社に見られることはありません。彼らはあなたが必要とする以上にあなたを必要としているので、彼らがあなたが望むものを提供しないなら、より良い場所に立ち去ってください。
デビッドアルノ

4
私はキャリアの中で少し動きました。私は、上司と一緒にコーディング方法について100%目を見た仕事をしたことがないと思います。私たちは私たち自身の背景と哲学を持つ人間です。ですから、私が好きではないコーディング基準がいくつかあったからといって、私は個人的に仕事を辞めません。(マネージャーが好むように指を曲げる命名規則は、自分のデバイスに任せた場合のコーディング方法に特に反しているように見えます。)しかし、あなたは正しいです。 。
user1172763

6

一般に、小さな関数を使用するのは良い習慣です。しかし、理想的には、関数を導入することで、大きな論理チャンクを分離するか、DRYによってコード全体のサイズを小さくする必要があると考えています。両方を指定した例では、コードが長くなり、開発者が読むのに時間がかかりますが、短い選択肢では、"resourceId"値が本番環境にのみ存在することを説明していません。そのような単純なものは、特にコードベースを初めて使用する場合は、忘れがちであり、操作しようとするときに混乱する可能性があります。

私は絶対に3進数を使うべきだとは言いません。私と一緒に仕事をした人の中には、少し長い方を好む人もいますif () {...} else {...}。私は「1行で1つのアプローチを行う」ことを好む傾向がありますが、基本的にはコードベースが通常使用するものは何でも使用します。

三項を使用する場合、論理チェックによって行が長すぎるか複雑になる場合、値を保持するために適切な名前の変数を作成することを検討してください。

// NOTE "resourceId" not present in dev build, use test data
let isProduction = 'resourceId' in headers;
let phoneNumber = isProduction ? headers.resourceId : DEV_PHONE_NUMBER;

また、コードベースが300行の関数に向かって伸びている場合は、細分化が必要であることも言いたいと思います。しかし、少し広いストロークを使用することをお勧めします。


5

あなたが与えたコード例、あなたの上司は正しいです。その場合は、1本の明確な線の方が適しています。

一般に、複雑なロジックを小さな部分に分割すると、読みやすさ、コードの維持、およびサブクラスの動作が異なる場合があります(わずかでも)。

欠点を無視しないでください:関数のオーバーヘッド、不明瞭化(関数はコメントや関数名が意味することを行いません)、複雑なスパゲッティロジック、死んだ関数の可能性(一度は呼び出されない目的で作成された)。


1
「関数のオーバーヘッド」:それはコンパイラ次第です。「隠蔽」:OPは、そのプロパティをチェックする唯一の方法であるか最善の方法であるかを示していません。確実に知ることもできません。「複雑なスパゲッティロジック」:どこ?「死んだ機能の可能性」:その種の死んだコード分析は簡単な成果であり、それを欠く開発ツールチェーンは未熟です。
ライモイド

答えは利点に重点を置いていましたが、欠点も指摘したかっただけです。sum(a、b)のような関数の呼び出しは、常に「a + b」よりも高価になります(関数がコンパイラーによってインライン化されない限り)。残りの欠点は、過度の複雑さが独自の問題のセットにつながる可能性があることを示しています。悪いコードは悪いコードであり、小さなバイトに分割されている(または300行のループに保持されている)ために、飲み込みやすくなるという意味ではありません。
フィルM

2

長い関数を支持する少なくとも2つの引数を考えることができます。

  • これは、各行の前後に多くのコンテキストがあることを意味します。これを形式化する方法:コードの制御フローグラフを描画します。関数の入口と出口の間の頂点(〜=行)で、すべての着信エッジがわかります。関数が長いほど、そのような頂点が多くなります。

  • 多くの小さな関数は、より大きく複雑な呼び出しグラフがあることを意味します。ランダム関数でランダムな行を選択し、「この行はどのコンテキストで実行されますか?」という質問に答えます。コールグラフがより大きく複雑になるほど、これは難しくなります。なぜなら、そのグラフではより多くの頂点を見る必要があるからです。

また、長い関数に対する議論もあります。ユニットテスト可能性が思い浮かびます。どちらか一方を選択するときは、t̶h̶e̶̶f̶o̶r̶c̶e̶の経験を使用してください。

注:あなたの上司が正しいと言っているのではなく、彼の視点が完全に価値を欠いているわけではないということだけです。


私の考えでは、最適化パラメーターは関数の長さではないと考えています。考えてみると、より便利なのは次のものだと思います。他のすべてが同じであれば、ビジネスロジックと実装の両方の高レベルの記述をコードから読み取ることができることが望ましいです。(関連するコードを見つけることができれば、低レベルの実装の詳細をいつでも読むことができます。)


David Arnoの答えにコメントする:

小さな関数を記述すると、コードが何をしているのかを見るために各小さな関数に移動する必要があるため、苦痛です。

関数の名前が適切である場合、これは当てはまりません。isApplicationInProductionは自明であり、コードの内容を確認するためにコードを調べる必要はありません。実際、反対のことが当てはまります。コードを調べると、関数名よりも意図が明らかになりません(上司がコメントに頼らなければならない理由です)。

名前は明らかにどのような戻り値になり手段が、それはについては何も述べていない効果(=コードがどのようなコードを実行しませんが)。名前(のみ)はインテントに関する情報を伝え、コードは振る舞いに関する情報を伝えます(インテントの一部を推測できる場合があります)。

場合によっては、一方が必要な場合があり、時にはもう一方が必要な場合があります。そのため、この観察では、一方的に普遍的に有効な決定ルールは作成されません。

メインループが300行を超える場合でも、すべてをメインの大きなループに入れると、読み取りが速くなります。

スキャンする方が高速かもしれませんが、コードを本当に「読み取る」ためには、頭の中でコードを効果的に実行できる必要があります。これは小さな関数では簡単で、100行の長さのメソッドでは非常に困難です。

あなたの頭の中でそれを実行しなければならないことに同意します。1つの大きな関数と多くの小さな関数に500行の機能がある場合、なぜこれが簡単になるのかはわかりません。

500行の非常に副作用の大きい直線の極端なケースで、エフェクトAがエフェクトBの前または後に発生するかどうかを知りたいとします。大きな関数の場合、Page Up / Downを使用して2行を見つけて比較します行番号。多機能の場合、コールツリーのどこでエフェクトが発生するかを覚えておく必要があります。忘れてしまった場合は、このツリーの構造を再発見するのに重要な時間を費やす必要があります。

サポート関数のコールツリーを走査するとき、ビジネスロジックから実装の詳細に移行した時期を判断するという課題にも直面します。コールグラフが単純であればあるほど、この区別がしやすいという証拠はありません*。

(*)少なくとも私はそれについて正直です;-)

繰り返しますが、どちらのアプローチにも長所と短所があると思います。

コードを複製する必要がある場合は、小さな関数のみを記述してください

同意しません。あなたのコード例が示すように、小さくてよく名前の付いた関数はコードの読みやすさを改善します。

「どのように」または「何に」興味があるかどうかは、コードを読む目的の関数です(たとえば、一般的なアイデアを得るか、バグを追跡するか)。コードを読んでいる目的は、プログラムの作成中には利用できません。おそらく、さまざまな目的でコードを読むでしょう。さまざまな決定がさまざまな目的に最適化されます。

とはいえ、これはおそらく私が最も反対する上司の意見の一部です。

コメントの名前を使用して関数を作成しないでください。上記のコメントを使用して、複雑なコード行(3〜4行)を入力してください。このように、失敗したコードを直接変更できます

これが本当に深刻であると仮定すると、私はこの理由を本当に理解できません。[...]コメントには根本的な欠陥があります。それらはコンパイル/解釈されないため、ユニットテストができません。コードが変更され、コメントがそのまま残され、どちらが正しいかわからなくなります。

コンパイラは、名前が等しいかどうかを比較するだけで、MisleadingNameErrorが発生することはありません。また、いくつかの呼び出しサイトが名前で特定の関数を呼び出すことがあるため、名前を変更するのが難しく、エラーが発生しやすいことがあります。コメントにはこの問題はありません。ただし、これはやや推測的なものです。これを本当に解決するためには、プログラマが誤解を招くコメントと誤解を招く名前を更新する可能性が高いかどうかに関するデータが必要になるでしょう。


-1

私の意見では、必要な機能の正しいコードは次のとおりです。

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER;

または、関数に分割したい場合、おそらく次のようなものです:

phoneNumber = getPhoneNumber(headers);

function getPhoneNumber(headers) {
  return headers.resourceId || DEV_PHONE_NUMBER
}

しかし、「生産中」という概念にはもっと根本的な問題があると思います。関数の問題isApplicationInProductionは、これがシステム内で「生産」に関係する唯一の場所であり、resourceIdヘッダーの有無に常に依存できることは奇妙に見えることです。一般的なisApplicationInProduction方法またはgetEnvironment環境を直接確認する方法が必要です。コードは次のようになります。

function isApplicationInProduction() {
  process.env.NODE_ENV === 'production';
}

その後、次の方法で電話番号を取得できます。

phoneNumber = isApplicationInProduction() ? headers.resourceId : DEV_PHONE_NUMBER;

-2

箇条書きの2つについてのコメントのみ

  • 小さな関数を書くと、コードが何をしているのかを見るために各小さな関数に移動しなければならないので苦痛です。
  • メインループが300行を超える場合でも、すべてをメインの大きなループに入れると、読みやすくなります。

多くのエディター(IntelliJなど)では、Ctrlキーを押しながら使用方法をクリックするだけで、関数/クラスにジャンプできます。さらに、多くの場合、コードを読み取るために関数の実装の詳細を知る必要がないため、コードの読み取りが速くなります。

上司に伝えることをお勧めします。彼はあなたのアドボカシーを気に入って、それをリーダーシップと見なします。丁寧に。

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