メソッドの理想的な長さは何ですか?[閉まっている]


122

オブジェクト指向プログラミングでは、メソッドの最大長に関する厳密なルールはもちろんありませんが、これらの2つの引用符は相反するものであることに気付いたので、ご意見をお聞かせください。

クリーンコード:アジャイルソフトウェアクラフトマンシップのハンドブックは、ロバート・マーティン氏は述べています:

関数の最初のルールは、関数は小さくなければならないということです。関数の2番目の規則は、関数はそれよりも小さくなければならないということです。関数の長さは100行であってはなりません。関数の長さが20行になることはほとんどありません。

そして、彼はKent Beckから見たJavaコードから例を示します。

彼のプログラムのすべての機能は、わずか2、3、または4行でした。それぞれが透過的に明らかでした。それぞれが物語を語った。そして、それぞれがあなたを説得力のある順序で次へと導きました。それはあなたの関数がどれほど短いかです!

これは素晴らしいように聞こえますが、一方で、Code Completeでは、Steve McConnellが非常に異なることを言っています。

ルーチンは、100〜200行まで有機的に成長できるようにする必要があります。数十年の証拠によると、このような長さのルーチンは、より短いルーチンよりもエラーが発生しにくいと言われています。

そして彼は、65行以上のルーチンを開発する方が安価であるという研究への言及を提供します。

この問題について意見が分かれていますが、機能的なベストプラクティスはありますか?


17
関数は理解しやすいものでなければなりません。状況に応じて、その長さはそれに従う必要があります。
ヘンクホルターマン

56
本当の制限は53行だと思います。平均行サイズは32.4文字です。真剣に、決定的な答えはありません。100行の方法は非常に明確で保守しやすく、4行の方法は理解するのが難しい場合があります。ただし、一般的に、長いメソッドは責任が多すぎる傾向があり、小さなメソッドよりも理解および保守が難しくなります。私は責任の面で考え、メソッドごとに1つの責任を負おうとします。

23
プログラミングには「機能的一貫性」と呼ばれる用語があります。関数の実装は、アプリケーション内のロジックの単一の一貫したユニットを構成するという条件で、関数の長さを変えることができます。関数を任意に分割して小さくすると、コードが肥大化し、保守性が損なわれる可能性が高くなります。

9
また、関数の複雑さを制限する場合は、長ではなく循環的な複雑さを測定する必要があります。switch100個のcase条件を持つステートメントは、if相互にネストされた10レベルのステートメントよりも保守が容易です。

10
Bob Martinのアプローチは2008年、Steve Mc Connellの1993年からのものです。「良いコード」とは異なる哲学を持っているため、IMHO Bob Martinはより高いレベルのコード品質を獲得しようとします。
ドックブラウン

回答:


115

通常、関数は短くする必要があります。JavaまたはC#でコーディングする場合、5〜15行が私の個人的な「経験則」です。これはいくつかの理由で適切なサイズです。

  • スクロールせずに画面に簡単に収まります
  • それはあなたが頭の中で保持できる概念的なサイズについてです
  • (スタンドアロンの意味のあるロジックの塊として)独自の機能を必要とするのに十分な意味があります
  • 5行未満の関数は、おそらくコードを分割しすぎている可能性があることを示すヒントです(関数間を移動する必要がある場合、読みにくく、理解しにくくなります)。それとも、あなたの特別なケース/エラー処理を忘れていますか!

しかし、ルールから逸脱する有効な例外/理由が常に存在するため、絶対ルールを設定することは役に立たないと思います。

  • 型キャストを実行する1行のアクセサー関数は、状況によっては明らかに受け入れられます。
  • 5行未満であることが明らかに必要な、非常に短いが便利な機能(ユーザーが知らないスワップなど)がいくつかあります。大したことではありませんが、いくつかの3行関数はコードベースに害を与えません。
  • 何が行われているかが非常に明確な場合、単一の大きなswitchステートメントである100行関数が受け入れられる場合があります。このコードは、さまざまなケースを説明するために多くの行を必要とする場合でも、概念的に非常に単純にすることができます。時には、これを個別のクラスにリファクタリングし、継承/ポリモーフィズムを使用して実装することをお勧めしますが、IMHOこれはOOPを取りすぎています-対処する40の新しいクラスよりも大きな40-way switchステートメントが1つだけ必要ですそれらを作成するための40-way switchステートメントに。
  • 複雑な関数には、パラメータとして異なる関数間で渡されると非常に煩雑になる状態変数がたくさんある場合があります。この場合、単一の大きな関数にすべてを保持する場合、コードが単純で従うのが簡単であるという議論を合理的に行うことができます(ただし、Markが正しく指摘しているように、これは両方のロジックをカプセル化するクラスに変わる候補でもありますおよび状態)。
  • 小さい関数や大きい関数にはパフォーマンス上の利点がある場合があります(おそらく、フランクが言及しているインライン化またはJITの理由による)。これは実装に大きく依存しますが、違いを生む可能性があります-ベンチマークを確認してください!

基本的に、ほとんどの場合、小さな関数サイズに固執する常識を使用しますが、異常に大きな関数を作成する正当な理由がある場合は、それについて独断的ではありません。


9
短いメソッドには技術的/パフォーマンス上の理由もあります:JITキャッシュヒット。多くの小さくて再利用可能なメソッドは、以前に呼び出された可能性が高くなります。さらに、診断上の利点として、StackTracesはポップになったロジックにより重点を置いています。
ルークプレット

20
「常識を使用する」が最も重要なアドバイスです
サイモン14年

このヒューリスティックは、デバッグ時に、ブレークポイント内のスタックの深さに起因する「ラビオリコード」の苦情をもたらすことに注意する必要があります。
フランクヒルマン14

5
コードを書くとき、私はスパゲッティの上に任意の日をラビオリがかかります@FrankHileman

1
@Snowman:これらの選択肢は相互に排他的ではありません...スパゲッティなしの短いスタック深度が理想的です。側面にスパゲッティが付いた深いスタック深度?
フランクヒルマン14

30

正しいLOC番号について厳しい規則はないと言ったとき、他のコメントに同意しますが、過去に見たプロジェクトを振り返り、上記のすべての機能を識別した場合、たとえば150行のコードだと思いますこれらの関数のうち10個のうち9個がSRPを破り(おそらくOCPも同様)、ローカル変数が多すぎ、制御フローが多すぎて、一般に読み取りと保守が難しいというコンセンサスに達すると推測します。

したがって、LOCは不正なコードの直接的な指標ではないかもしれませんが、特定の関数をより適切に記述することができることを示す間接的な指標であることは確かです。

私のチームでは、私はリードの地位に落ちました。何らかの理由で、人々は私に耳を傾けているようです。私が一般的に解決したのは、絶対的な制限はありませんが、コードのレビュー中に少なくとも50行以上のコードの機能は最低でも赤旗を上げる必要があることをチームに伝え、それを再確認して再評価することです複雑さとSRP / OCP違反。その再検討の後、私たちはそれを放っておくか、変更するかもしれませんが、少なくとも人々にこれらのことを考えさせます。


3
これは理にかなっているように思えます-LOCは複雑さやコード品質に関して何も意味しませんが、物事がリファクタリングされるべき可能性の良いマーカーになります。
コリ

4
「これらの関数のうち10個のうち9個がSRPを破るというコンセンサスに達する」-これらの関数のうち10個のうち10個がそれを破ると確信しています;-)
Doc Brown

1
コードレビュー中にフラグを立てる+1:つまり、厳密なルールではありませんが、このコードをグループとして説明しましょう。

22

私はコーディングのガイドラインを気にしていないプロジェクトに足を踏み入れました。コードを調べると、6000行以上のコードと10個未満のメソッドを持つクラスを見つけることがあります。これは、バグを修正する必要がある場合の恐怖シナリオです。

メソッドの最大サイズの一般的なルールは、あまり良くない場合があります。ロバートC.マーティン(ボブおじさん)のルールが好きです:「メソッドは小さく、小さくなければなりません」。私は常にこのルールを使用しようとします。私の方法は1つのことだけを行い、それ以上のことはしないことを明確にすることで、方法をシンプルで小さくしようとしています。


4
6000ライン機能が短いものよりもデバッグが困難である理由、それはあまりにも他の問題を持っていない限り、私は(例えば、繰り返し。)...表示されない
Calmarius

17
デバッグとは関係ありません。複雑さと保守性についてです。別の方法で行われた6000行の方法をどのように拡張または変更しますか?
スモークフット

10
@Calmariusの違いは通常、6000行の関数が非常に遠く(視覚的に)宣言されたローカル変数を含む傾向があるため、プログラマーがコードについて高い信頼性を得るために必要なメンタルコンテキストを構築することを困難にすることです。任意の時点で変数がどのように初期化および構築されるかを確認できますか?行3879で変数を設定した後、変数が混乱することはありませんか?一方、15行のメソッドでは、確実にできます。
ダニエルB 14年

8
@Calmariusは同意しましたが、これらのステートメントは両方とも6000個のLOC関数に対する引数です。
ダニエルB 14年

2
600行メソッドのローカル変数は、本質的にグローバル変数です。
MK01

10

行数ではなく、SRPについてです。この原則によれば、メソッドは1つのことだけを行う必要があります。

あなたのメソッドがこれとこれとこれまたはそのOR =>を行う場合、おそらく多くのことをしています。このメソッドと分析を見てみてください:「ここでこのデータを取得し、ソートし、必要な要素を取得します」、「ここでこれらの要素を処理します」、「結果を得るために最終的にそれらを結合します」。これらの「ブロック」は、他のメソッドにリファクタリングする必要があります。

単純にSRPに従う場合、メソッドのほとんどは小さく、明確な意図があります。

「この方法は20行を超えているので間違っている」と言うのは正しくありません。これは、このメソッドに何か問題がある可能性があることを示している可能性があります。

メソッド内で400行のスイッチを使用することもあります(多くの場合、テレコムで発生します)。それは依然として単一の責任であり、完全に問題ありません。


2
大規模なswitchステートメント、出力フォーマット、ハッシュ/辞書の定義は、一部のデータベースでは柔軟ではなくハードコーディングする必要がありますが、これはよく発生し、完全に問題ありません。ロジックが提供されている限り、あなたは大丈夫です。大規模な方法では、「これを分割すべきか」と考えるよう促される可能性があります。答えは非常によく「いいえ、これで問題ありません」(または、はい、これは絶対的な混乱です)
マーティン

「SRP」とはどういう意味ですか?
トムトム

3
SRPは単一責任原則の略で、すべてのクラス(またはメソッド)に1つの責任のみを持たせる必要があると述べています。それは、凝集と結合に関連しています。SRPに従うと、クラス(またはメソッド)の凝集度は高くなりますが、クラス(またはメソッド)が増えるため、結合が増加する可能性があります。
クリスチャンダスケ14年

SRPの場合は+1。凝集関数を記述することにより、関数スタイルでそれらをより簡単に組み合わせて、より複雑な結果を得ることができます。最終的には、1つの関数が何らかの形で関連していても、3つの離散的なことを行うよりも、関数が結合された3つの他の関数で構成される方が適切です。
マリオT.ランザ14年

ええ、しかし、単一の責任は何ですか。それはあなたの頭の中で作成された単なるコンセプトです。あなたは、単一の責任のための400本のラインを必要とする単一責任のあなたの概念は、おそらく私のやり方と異なる場合
Xitcod13

9

それは、真剣に、あなたが作業している言語が問題であるため、この質問に対する確かな答えが実際にはないことに依存します。この答えで言及さている5〜15行目はC#またはJavaで動作するかもしれません一緒に仕事をします。同様に、作業しているドメインによっては、大きなデータ構造でコード設定値を記述している場合があります。一部のデータ構造では、設定する必要がある要素が数十個ある場合がありますが、関数が長時間実行されているという理由だけで、関数を別の関数に分割する必要がありますか?

他の人が指摘したように、最良の経験則は、関数は単一のタスクを処理する単一の論理エンティティでなければならないということです。関数をn行より長くすることはできず、その値を小さくしすぎると言う厳格なルールを適用しようとすると、開発者がルールを回避しようとする巧妙なトリックを使用するため、コードが読みにくくなります。同様に、設定した値が高すぎると問題にならず、怠thoughではありますが、悪いコードになる可能性があります。あなたの最善の策は、機能が単一のタスクを処理していることを確認するためにコードレビューを実施することです。


8

ここでの問題の1つは、関数の長さがその複雑さについて何も述べていないことだと思います。LOC(Line of Code)は、何でも測定するのに悪い道具です。

メソッドは過度に複雑であってはなりませんが、長いメソッドを簡単に維持できるシナリオがあります。次の例では、メソッドに分割できないとは言わず、メソッドが保守性を変更しないだけであることに注意してください。

たとえば、着信データのハンドラーは、大きなswitchステートメントを持ち、その後、ケースごとに単純なコードを持つことができます。私はそのようなコードを持っています-フィードからの着信データを管理します。70(!)数値コードハンドラー。さて、「定数を使用」と言うでしょう-はい、APIがそれらを提供せず、私はここで「ソース」の近くに留まるのが好きです。メソッド?確かに-悲しいことに、それらのすべてが同じ2つの巨大な構造からのデータを処理します。より多くのメソッド(読みやすさ)がある場合を除いて、それらを分割してもメリットはありません。コードは本質的に複雑ではありません-フィールドに応じて1つのスイッチ。次に、すべてのケースには、データのx個の要素を解析して公開するブロックがあります。メンテナンスの悪夢はありません。フィールドにデータがあるかどうかを決定するif条件が1つ繰り返されます(pField = pFields [x]、pField-> IsSet(){blabla}の場合)-すべてのフィールドでほぼ同じです。

それを、ネストされたループと多くの実際の切り替えステートメントを含むはるかに小さなルーチンに置き換えてください。巨大なメソッドは、1つの小さなメソッドよりも保守が簡単です。

そのため、申し訳ありませんが、LOCは最初は適切な測定値ではありません。どちらかといえば、複雑さ/決定点を使用する必要があります。


1
LOCは、関連する測定値を提供する1つの領域に使用するための優れたツールです。非常に大規模なプロジェクトでは、同様のプロジェクトが完了するまでにかかる時間を見積もるのに役立ちます。それを超えて、人々は彼らについてあまりにも心配する傾向があります。
rjzii

右。LOCがコードの記述方法やhformattingの要件などについてLOCが3度目を遅らせているわけではありません。のみ。LOCが悪い測定値である理由を理解していない人々のリストに自分を入れることは自由ですが、それは明らかにあなたが耳を傾ける誰かのように見えないでしょう。
トムトム

LOCが測定と使用の1つの領域(つまり、推定に使用できる非常に大きなプロジェクト)にのみ適したツールであることに注意してください。大規模なものよりも小さなものであり、人々が幸せになるために、会議での素早い音の噛み込み以外のすべての価値ではないにしても、ほとんどの価値を使用します。彼らは、コーヒーショップがオフィスからどれだけ公正であるかを光年を使って測定しようとするようなものです。あなたはそれを行うことができますが、測定は役に立ちません。しかし、星間の距離を議論する必要があるとき、それらはうまく機能します。
rjzii

2
+1関数には、その関数を実行するために必要なすべてのコードが必要です。1つのことと1つのことだけを行う必要がありますが、それが1000行のコードを必要とする場合は、そうしてください。
ジェームズアンダーソン

受信ソケットデータのハンドラーを作成しました。はい、1000以上のLOCが必要になる場合があります。ただし、そのために必要な回数をカウントすることはできますが、適切なコーディング方法ではなかった回数はカウントできません。

7

さらに別の引用文を挿入します。

プログラムは、人々が読むために、そして偶然にマシンが実行するためだけに書かれなければなりません

-ハロルド・アベルソン

100〜200に成長する関数がこの規則に従うことはほとんどありません


1
スイッチが含まれている場合を除きます。
カルマリオ14年

1
または結果セット内の行ごとのフィールドの数十を返すデータベースクエリの結果に基づいて、オブジェクト...構築
jwenting

データベース結果は確かに受け入れられる例外です-さらに、通常は、従う必要のあるロジックではなく、クラス(または何でも)のインスタンスを生成する「ダム」ステートメントです。
MetalMikester 14

6

1970年以来、私はこのクレイジーなラケットを何とかしてきました。

その間ずっと、すぐに到達する2つの例外を除いて、複数の印刷ページが必要な適切に設計された「ルーチン」(メソッド、プロシージャ、関数、サブルーチンなど)を見たことはありません(約60行)。それらの大部分は、10〜20行程度の非常に短いものでした。

しかし、私は明らかにモジュール化について聞いたことがない人々によって書かれた「意識の流れ」コードをたくさん見ました。

2つの例外は非常に特殊なケースでした。1つは実際には例外ケースのクラスであり、私はひとまとめにします。通常、それらを実装するよりクリーンな方法がないため、大きないswitchステートメントとして実装される大きな有限状態オートマトンです。これらは通常、自動テスト機器に表示され、テスト対象のデバイスからのデータログを解析します。

もう1つは、CDC 6600 FORTRAN IVで記述されたMatuszek-Reynolds-McGehearty-Cohen STARTRKゲームの光子魚雷ルーチンです。コマンドラインを解析し、摂動を加えて各魚雷の飛行をシミュレートし、魚雷と命中する可能性のあるあらゆる種類の相互作用を確認し、さらに、他の星の隣にある星を魚雷で照らした新星。


2
この答えから得られる「芝生から降りる」というバイブに対して+1。また、OOP言語が普及する前からの個人的な経験から。

私が長年にわたってたくさんのがらくたコードを見てきたという観察として、それは「私の芝生を降りる」ということではなく、さらに悪くなっているようです。
ジョンR.ストローム14

私の上司は、数百行のメソッドを書く習慣があり、多くの場合、ネストされたifのいくつかのレベルがあります。彼はまた、クラスをいくつかのファイルに「分割」するために部分クラス(.NET)を使用しているため、それらを短くしていると主張できます。これらは、私が対処しなければならない2つのことです。約25年間これを行っており、事態が悪化していることを確認できます。そして今、私がその混乱に戻る時間です。
MetalMikester 14

5

長い方法を見つけた場合、この方法は適切に単体テストされていないか、ほとんどの場合、まったく単体テストされていないと思います。TDDを開始する場合、25の異なる責任と5つのネストされたループを持つ100行のメソッドを構築することはありません。テストでは、混乱を絶えずリファクタリングし、ボブおじさんのきれいなコードを書くことが必要です。


2

メソッドの長さに関する絶対的な規則はありませんが、次の規則が役立ちました。

  1. 関数の主な目的は、戻り値を見つけることです。それが存在する他の理由はありません。その理由がいっぱいになったら、他のコードを挿入しないでください。これは必然的に機能を小さく保ちます。他の関数の呼び出しは、戻り値を見つけやすくする場合にのみ行う必要があります。
  2. 一方、インターフェイスは小さくする必要があります。これは、多数のクラスがあるか、または多数の関数があることを意味します。重要なことを行うのに十分なコードを取得し始めると、2つのうちの1つが発生します。大きなプログラムは両方を持つことができます。

1
ファイルへの書き込み、ステータスのリセットなどの副作用についてはどうですか?
ヴォラック

2

著者は「機能」と「ルーチン」で同じことを意味していますか?通常、「関数」と言うとき、値を返さないサブルーチン/操作と、返さない(呼び出しが単一のステートメントになる)「プロシージャ」を意味します。これは、実世界のSEでは一般的な区別ではありませんが、ユーザーのテキストで見ました。

いずれにしても、これに対する正しい答えはありません。どちらか一方の好み(まったく好みがある場合)は、言語、プロジェクト、および組織間で非常に異なると予想されるものです。すべてのコード規約と同じです。

私が追加する1つのビットは、「長い操作は短い操作よりもエラーが発生しにくい」という主張全体が厳密に真実ではないということです。より多くのコードがより多くの潜在的なエラースペースに等しいという事実に加えて、コードをセグメントに分割すると、エラーが回避しやすくなり、見つけやすくなることは明白です。そうしないと、コードを断片に分割する理由がまったくなく、繰り返しを節約できます。しかし、これはおそらく、セグメントが十分に文書化されていて、実際のコードを読み通したりトレースしたりせずに操作呼び出しの結果を判断できる場合にのみ当てはまります(コードの領域間の具体的な依存関係ではなく、仕様に基づく契約ごとの設計)。

さらに、より長い操作を適切に機能させる場合は、より厳密なコード規則を採用してそれらをサポートすることができます。操作の途中でreturnステートメントをトスすることは、短い操作では問題ないかもしれませんが、長い操作では、条件付きであるが明らかにクイックリードスルーでは条件付きではないコードの大きなセクションを作成できます(1つの例のみ)。

そのため、どのスタイルがバグに満ちた悪夢である可能性が低いかは、コードの残りの部分でどの規約に従うかによって大きく左右されると思います。:)


1

私見、あなたはあなたの機能を読むためにスクロールバーを使用する必要はないはずです。スクロールバーを移動する必要があるとすぐに、機能がどのように機能するかを理解するのにもう少し時間がかかります。

したがって、チーム作業の通常のプログラミング環境(画面解像度、エディター、フォントサイズなど)に依存します。80年代には、25行80列でした。今、私のエディターで、ほぼ50行を表示します。画面を2つに分割して2つのファイルを表示するため、表示する列の数は変わりませんでした。

手短に言えば、それは同僚の設定に依存します。


2
当時は24行ではなかったでしょうか?私は3270または9750端末のことを考えています。25番目はステータス行でした。そして、端末エミュレーションはこれに従いました。
ott--

一部のシステム/エディターには、最初から40行または50行がありました。最近では、150行は珍しくなく、200行以上が実行可能であるため、これは実際には適切なメトリックではありません。
モー14年

画面を縦向きで使用しています。一度に200行のコードを表示できます。
カルマリオ14年

行を分割するために改行を使用しない場合は、1行で5000行のメソッドをコーディングできます...
jwenting 14

1

TomTomの答えは、私がそれについてどう感じているかに近いと思います。

私は、線ではなく循環的複雑さをますます気づいています。

私は通常、多次元配列を処理するために必要なループの数を除いて、メソッドごとに1つだけの制御構造を目指しています。

スイッチケースに1行のifを入れることもあります。理由は、何らかの理由で、それを分割するのが助けではなく妨げになる場合があるからです。

この制限に対してガードロジックをカウントしないことに注意してください。


大量の実際の製品コードでは、循環的複雑度が生のSLOCと非常に強く相関していることが示されており、循環的複雑度の計算は時間、エネルギー、およびクロックサイクルの無駄になります。
ジョン・R. Strohm

@ JohnR.Strohm全体ではなく、メソッドごとに話しています。確かに、全体像では非常に相関関係にあります。問題は、そのコードをメソッドに分割する方法です。100行の10のメソッドまたは10行の100のメソッドは、全体的に同じSLOCと複雑さを持ちますが、前者は操作がはるかに難しくなります。
ローレンペクテル14

私もそうです。相関の研究は、コードの多くとルーチンの多くに注目しました。(それは大きな公共リポジトリの1つでした。)
ジョンR.ストローム14

-3

OOPではすべてのものがオブジェクトであり、次の機能があります。

  1. 多型
  2. 抽象化
  3. 継承

これらのルールを遵守する場合、メソッドは通常小さいですが、小さいルールまたは非常に小さいルール(2〜3行など)のルールは存在しません。小さなメソッドの利点(メソッドや関数などの小さな単位)は次のとおりです。

  1. より読みやすい
  2. より良く維持する
  3. 修正されたバグ
  4. より良く変化する
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.