web-dev-qa-db-ja.com

あなたにとってメソッドの理想的な長さは何ですか?

もちろん、オブジェクト指向プログラミングでは、メソッドの最大長に厳密な規則はありませんが、これらの2つの引用符は互いに相反しているので、あなたの考えを聞きたいと思います。

Clean Code:A Handbook of Agile Software Craftsmanship で、Robert Martinは次のように述べています。

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

そして彼はJava彼がケントベックから見るコードからの例を与えます:

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

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

ルーチンは100から200行まで有機的に成長することを許可されるべきであり、数十年の証拠によれば、そのような長さのルーチンは、より短いルーチンよりもエラーを起こしにくいということです。

そして彼は、ルーティンが65行以上の方が開発コストが安いと言う研究に言及しています。

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

128
iPhoneDeveloper

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

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

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

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

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

119
mikera

正しいLOC番号についてハードルールはないと言ったときの私は他のコメントに同意しますが、過去に調べたプロジェクトを振り返って、上記のすべての関数を特定するとしたら、150行のコードがあるとしましょう。これらの関数のうち10個のうち9個がSRPを壊し(そしてOCPの可能性が非常に高い)、ローカル変数が多すぎ、制御フローが多すぎて、一般に読み取りや保守が難しいというコンセンサスになると思います。

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

私のチームでは、私はリードのポジションに落ちました、そして、どんな理由であれ、人々は私に耳を傾けているようです。私が一般的に解決したのは、絶対的な制限はありませんが、50行以上のコードを追加した関数は、少なくともコードレビュー中に赤信号を発生させる必要があることをチームに伝え、再検討して再評価することです。複雑さとSRP/OCP違反のため。その再検討の後、私たちはそれをそのままにするか、それを変更するかもしれませんが、少なくとも人々はこれらのことについて考えさせます。

32
DXM

コーディングのガイドラインをまったく気にしていないプロジェクトに参加しました。コードを調べると、コードが6000行を超え、メソッドが10未満のクラスが見つかることがあります。これは、バグを修正する必要がある場合の恐ろしいシナリオです。

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

22
Smokefoot

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

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

あなたが単にSRPに従うならば、あなたの方法のほとんどは小さく、明確な意図があります。

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

メソッドに400回線のスイッチがある場合があり(多くの場合テレコムで発生します)、それは単一の責任であり、完全に問題ありません。

11
Alexey

状況によります、真剣に、あなたが扱っている言語が問題であり、5行目から15行目が言及されているため、この質問に対する確かな答えはありません- この回答では はC#またはJavaで動作する可能性がありますが、他の言語では動作するほど多くはありません。同様に、作業しているドメインによっては、大きなデータ構造でコード設定値を記述していることがあります。一部のデータ構造では、設定する必要がある要素が数十ある場合があります。関数が長く実行されているという理由だけで、別の関数に分割する必要がありますか?

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

9
rjzii

ここでの1つの問題は、関数の長さがその複雑さについて何も言わないことです。 LOC(コード行)は、何かを測定するには不適切な手段です。

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

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

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

そのため、申し訳ありませんが、LOCは最初から適切な測定方法ではありません。どちらかと言えば、複雑性/決定ポイントを使用する必要があります。

8
TomTom

さらにもう1つ引用します。

プログラムは、人々が読むために、そして偶然にマシンが実行するためにのみ書かれなければならない

-ハロルド・アベルソン

100から200に成長する関数がこのルールに従うことはほとんどありません

8
Pete

私は1970年以来、このクレイジーなラケットに何らかの形で参加しています。

その間、私がすぐに得る2つの例外を除いて、私は1つ以上の印刷されたページである必要がある(ルーチン、メソッド、プロシージャ、関数、サブルーチンなど)うまく設計された約60行)。それらの大部分は、10から20行程度のかなり短いものでした。

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

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

もう1つは、CDC 6600 FORTRAN IVで書かれた、Matuszek-Reynolds-McGehearty-Cohen STARTRKゲームの光子魚雷ルーチンです。コマンドラインを解析してから、各魚雷の飛行を摂動でシミュレートし、魚雷と衝突する可能性のあるあらゆる種類の物との相互作用を確認し、そして再帰をシミュレートして、他の星の隣にある星の魚雷発射による新星。

6
John R. Strohm

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

5
Sergii Shevchyk

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

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

作者は「機能」と「ルーティン」で同じ意味ですか?通常、「関数」とは、値を返さないサブルーチン/操作と、返さない(および呼び出しが単一のステートメントになる)ための「手順」を意味します。これは、現実世界のSE全体で共通の違いではありませんが、ユーザーのテキストで見ました。

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

私が追加する1つのビットは、「長い操作は短い操作よりもエラーが発生しにくい」というアサーション全体が厳密には当てはまらないことです。コードの数が増えるとエラースペースが増える可能性があるという事実に加えて、コードをセグメントに分割すると、エラーの回避と特定が容易になることは明らかです。そうでなければ、コードを断片に分割する理由はまったくなく、繰り返しを省きます。しかし、これはおそらく、セグメントが十分に文書化されており、実際のコードを読み取ったり追跡したりせずに操作呼び出しの結果を判断できる場合にのみ当てはまります(コード領域間の具体的な依存関係ではなく、仕様に基づく契約ごとの設計)。

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

したがって、どのスタイルがバグに満ちた悪夢である可能性が低いかは、大部分、コードの残りの部分に準拠する規則に依存すると思います。 :)

2
Trixie Wolf

私見、関数を読むためにスクロールバーを使用する必要はありません。スクロールバーを移動する必要があるとすぐに、関数の動作を理解するのにもう少し時間がかかります。

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

簡単に言えば、それはあなたの同僚のセットアップに依存します。

1

TomTom's answer は私がそれについてどう感じているかに近づいたと思います。

線ではなく、循環的な複雑性にどんどん気づいています。

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

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

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

1
Loren Pechtel