関数が長すぎるのはいつですか?[閉まっている]


130

35行、55行、100行、300行?いつそれを分解し始めるべきですか?60行(コメントを含む)の関数があり、それを分解することを考えていたので、私は尋ねています。

long_function(){ ... }

に:

small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

関数はlong_functionの外では使用されません。関数を小さくすると、より多くの関数が呼び出されるなどになります。

関数をいつより小さな関数に分解しますか?どうして?

  1. メソッドは論理的なことを1つだけ実行する必要があります(機能について考えます)
  2. 方法を一文で説明できるはずです
  3. ディスプレイの高さに合わせる必要があります
  4. 不必要なオーバーヘッドを避けてください(明らかなことを指摘するコメント...)
  5. 小さな論理関数の場合、単体テストは簡単です
  6. 関数の一部が他のクラスまたはメソッドで再利用できるかどうかを確認します
  7. クラス間の過剰な結合を回避する
  8. 深くネストされた制御構造を避ける

答えをありがとう、リストを編集し、正しい答えに投票してください。

私はそれらの考えを念頭に置いて今リファクタリングしています:)


あなたの質問にはタイプミスがあります、「関数が長すぎるのはいつですか?」
トム

1
あなたは、コードの行の面でそれを提起することによって質問を誤解しています。決定要因はコード行では測定されません。
dkretz 2009年

この質問は、コードと言語によっては複雑になる可能性があります。多分あなたはそれを投稿することができます。
Ray Tayek、2009年

それが単一責任の原則に準拠している場合-ちょうどそれをしてください。私は通常、ヘッダーを作成する必要があるか、コードを20行ごとに作成する必要があると感じています。
Yevgeniy Afanasyev 2017年

回答:


75

それには実際に厳格なルールはありません。一般に、私は自分のメソッドが「1つのこと」だけをするのが好きです。したがって、データを取得し、そのデータを使用して何かを実行し、それをディスクに書き込む場合は、取得と分割を別々のメソッドに分割して、「main」メソッドに「何かを実行する」ことだけを含めます。

ただし、「何かを行う」というのは、まだかなりの数行になる可能性があるため、行数が使用する適切なメトリックであるかどうかはわかりません。

編集:これは、先週の仕事の周りにメールで送った1行のコードです(要点を証明するためです。これは、私が習慣にするものではありません:))-私のメソッドでは、これらの悪い男の子を50〜60人は欲しくないでしょう。 :D

return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList();

1
LOLええと、メソッド内のすべての空白を削除することができました。これは、非常に長い行であり、長い関数ではありません。おかげで、おそらくより答えであることを、一つのことをやって

@Movaxes私が投稿したコードスニペットは単一のステートメントですが、1行に多数の行が含まれているだけではありません。セミコロンはありません:) GetResources()を毎回展開してさらに邪悪にすることができました:P
スティーブンロビンス

そうですね。ソースファイル全体を1行で入力するのはなぜですか。
つまり

昔の雑誌(私はBBC Micro oldと呼んでいます)で、BBCが処理できる最大長まで、各行にいくつかのステートメントがあった「10行プログラム」を使用していたことを覚えています。次のように入力します:D
スティーブンロビンス

6
たった1つの機能を実行するという機能のコンセプトが気に入っています。10個の機能を実行する関数があり、そのうちの9個を別の関数に移動しても、残りの関数によって呼び出されるのは、本質的に10個のことを実行している残りの関数ではありません。このように関数を分割すると、テストがはるかに簡単になると思います。
mtnpaul 2012年

214

以下は、関数が長すぎることを示す可能性のある赤旗(順不同)のリストです。

  1. 深くネストされた制御構造:たとえば、複雑な条件を持つネストされたifステートメントを含むfor-loopsは3レベルの深さまたは2レベルの深さまで。

  2. 状態を定義するパラメーターが多すぎる状態を定義するパラメーターとは、関数を介した特定の実行パスを保証する関数パラメーターを意味します。これらのタイプのパラメーターを取得しすぎると、実行パスの組み合わせが爆発的に増加します(通常、これは#1と並行して発生します)。

  3. 他の方法と重複するロジック:不十分なコードの再利用は、モノリシックな手続き型コードの大きな原因です。このようなロジックの複製の多くは非常に微妙な場合がありますが、一度リファクタリングすると、最終的な結果ははるかにエレガントなデザインになります。

  4. 過剰なクラス間結合:この適切なカプセル化の欠如により、関数は他のクラスの親密な特性に関係するようになり、その結果、それらが長くなります。

  5. 不必要なオーバーヘッド:明白に深くネストされたクラス、プライベートネストされたクラス変数の余分なゲッターとセッター、および異常に長い関数/変数名を指摘するコメントはすべて、最終的に長さを増加させる関連関数内に構文ノイズを作成する可能性があります。

  6. あなたの大規模な開発者グレードのディスプレイはそれを表示するのに十分な大きさではありません:実際、今日のディスプレイは高さに近い場所にある関数がおそらく長すぎるほど大きいです。しかし、それが大きい場合、これは何かが間違っている喫煙銃です。

  7. あなたはすぐに機能の目的を判断することはできません:あなたは実際に一度さらに、やるあなたは、単一の文の中で、この目的を要約したり途方もない頭痛を持って起こることができない場合には、その目的を決定し、これを手がかりにする必要があります。

結論として、モノリシック機能は広範囲にわたる結果をもたらす可能性があり、多くの場合、主要な設計の欠陥の症状です。読むのが本当に楽しいコードに出会ったときはいつでも、その優雅さがすぐに明らかになります。そして、何を推測しますか:関数はしばしば非常に短いです。


1
いいポスト!非常に確定的
チャックコンウェイ

2
@PedroMorteRoloそのとおりです。標準APIは常に優雅さのモデルとは限りません。さらに、Java APIの多くは、JavaコンパイラーとJVMに関する深い知識をもとに開発されているため、それを説明するパフォーマンス上の考慮事項があります。1ミリ秒を無駄にすることができないコードの重要なセクションは、これらのルールのいくつかを破る必要があるかもしれないと認めますが、それは常に特殊なケースと見なされるべきです。余分な開発時間を前もって費やすことは、将来の(潜在的に障害のある)技術的負債を回避できる初期投資です。
Ryan Delucchi 2013

2
ちなみに、私は長いメソッドは悪い発見的方法がクラスにも適用されるという意見です。私見、長いクラスは悪いです、なぜなら彼らは単一の責任の原則に違反する傾向があるからです。長いクラスとメソッドの両方に対して警告を発するコンパイラーがあると楽しいでしょう...
Pedro Rolo

3
@PedroMorteRolo私は間違いなくこれに同意します。さらに、大規模なクラスの方がミュータブルな状態になる可能性が高く、コードの保守が非常に難しくなります。
Ryan Delucchi 2013

1
ベストアンサー。別の良い手がかりは、次のとおりです。コード内のコメントはどのように見えますか?次のような行で誰かのコードを見つけた回数// fetch Foo's credentials where Bar is "uncomplete"。これはほぼ間違いなくすぐそこにある関数名であり、分離する必要があります。おそらく次のようなものにリファクタリングしたいと考えています: Foo.fetchCredentialWhereBarUncomplete()
Jay Edwards

28

このページの「たった1つのことをする」というマントラには大きな注意が必要だと思います。1つのことを行うと、多くの変数が混同されることがあります。小さな関数が長いパラメーターリストを持つことになる場合は、長い関数を小さな関数の束に分割しないでください。これを行うと、単一の関数が、実際の個々の値のない高度に結合された関数のセットに変わります。


18

関数は1つのことだけを実行する必要があります。関数内で多くの小さなことを行う場合は、それぞれの小さなことを関数にして、長い関数からそれらの関数を呼び出します。

あなたが本当にしたくないことは、あなたの長い関数の10行ごとにコピーして、(あなたの例が示唆するように)短い関数に貼り付けることです。


ええ、コピー&ペーストのパターンでたくさんの小さな関数を作成するのは良い考えではありません。関数は常に1つのことだけを実行する必要があることに同意します

「1つのことを行う」は、粒度に応じて正しい場合と正しくない場合があります。関数が行列を乗算する場合、それは問題ありません。関数が仮想車を構築する場合、それは「1つのこと」ですが、それは非常に大きなことでもあります。複数の機能を使用して、コンポーネントごとに自動車を構築できます。
void.pointer

16

関数は1つのことだけを行うべきだと私は同意しますが、その1つのことはどのレベルであるのでしょうか。

60行が(プログラムの観点から)1つのことを達成していて、それらの60行を構成する部分が他の何にも使用されない場合は、60行で問題ありません。

あなたがそれを独立したコンクリートの断片に分解できない限り、それを分解することには本当の利点はありません。使用するメトリックは機能であり、コード行ではありません。

私は多くのプログラムに取り組んできましたが、作者は1つだけを極端なレベルに引き上げ、結局は誰かが手榴弾を関数/メソッドに持って行って何十もの接続されていない部分に爆破したように見せることだけでした従うのは難しい。

その関数の一部を引き出すときは、不要なオーバーヘッドを追加して大量のデータを渡さないようにするかどうかも検討する必要があります。

重要な点は、その長い機能で再利用性を探し、それらの部品を引き出すことだと思います。あなたが残されているのは、それが10、20、または60行の長さであるかどうかにかかわらず、関数です。


2
+1「使用するメトリックは機能であり、コード行ではありません」
Cody Piersall 2013

もう1つの重要な指標は、ブロックネストのレベル数です。最小限にしてください。多くの場合、関数を小さな部分に分割すると役立ちます。複数の返品など、他にも役立つことがあります。
user2367418

10

60行は大きいですが、関数には長すぎません。エディターの1つの画面に収まる場合は、一度にすべてを表示できます。それは、関数が何をしているかに本当に依存します。

関数を分割する理由:

  • 長すぎる
  • コードを分割し、新しい関数に意味のある名前を使用することで、コードを保守しやすくします
  • 機能がまとまりがない
  • 関数の一部はそれ自体で役立ちます。
  • 関数にわかりやすい名前を付けるのが難しい場合(多すぎる可能性があります)

3
良い点は、私も同意します。関数にDoThisAndThisAndAlsoThisという名前を付けなければならない場合も、多分多すぎるでしょう。おかげで:)

2
あなたはこの仲間と一緒にちょうど狂っています。60行は常に多すぎます。私が10行で近づいている場合は、おそらく限界に近いと思います。
willcodejavaforfood 2009年

しかし、他の機能は、まだこれらの関数を呼び出すと、本質的に非常に同じであるDoThisAndThisAndAlsoThis機能が、あなたはまだ何とか名前を付けるために持っていることを抽象化の多くの
ティモHuovinen

6

私の個人的なヒューリスティックは、スクロールせずにすべてを見ることができない場合は長すぎるということです。


4
...フォントサイズを5に設定していますか?
EricSchaefer、2009年

5

サイズはおおよその画面サイズです(大きなピボットワイドスクリーンを入手して回転させてください)... :-)

冗談はさておき、関数ごとに1つの論理的なこと。

そして、ポジティブなことは、1つのことを行う小さな論理関数を使用すると、ユニットテストが非常に簡単になることです。多くのことを行う大きな機能は検証が困難です!

/ヨハン


単体テストについての良い点:)

5

経験則:関数に、何かを行うコードブロックが含まれている場合、それがコードの他の部分からある程度切り離されている場合は、別の関数に入れます。例:

function build_address_list_for_zip($zip) {

    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

より良い:

function fetch_addresses_for_zip($zip) {
    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }
    return $addresses;
}

function build_address_list_for_zip($zip) {

    $addresses = fetch_addresses_for_zip($zip);

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

このアプローチには2つの利点があります。

  1. 特定の郵便番号の住所を取得する必要がある場合はいつでも、すぐに利用できる関数を使用できます。

  2. 関数をbuild_address_list_for_zip()再度読み取る必要がある場合は、最初のコードブロックが何を行うかがわかります(特定の郵便番号のアドレスをフェッチします。少なくとも、関数名から取得できるものです)。クエリコードをインラインのままにしていた場合は、最初にそのコードを分析する必要があります。

[その一方で(拷問下でも私がこれを言ったことは否定します):PHPの最適化についてよく読んだ場合、関数の呼び出しが非常に少ないため、関数の数を可能な限り少なく保つというアイデアを得ることができます。 PHPでは非常に高価です。ベンチマークをしたことがないので、それについてはわかりません。その場合、アプリケーションが「パフォーマンスに敏感」である場合は、質問への回答のいずれにも従わない方がよいでしょう。;-)]


素晴らしい例に感謝します:)私はphpの関数ベンチマークを

5

McCabeのサイクロマティックを見てみましょう。ここでは、コードをグラフに分割しています。 」

コードに関数/メソッドがないと想像してください。グラフの形のコードの巨大なスプロールの1つにすぎません。

この無秩序な広がりをメソッドに分解したいとします。その場合、各メソッドに特定の数のブロックが存在することを考慮してください。各メソッドの1つのブロックのみが他のすべてのメソッドに表示されます:最初のブロック(メソッドにジャンプできるのは1点のみです:最初のブロック)。各メソッド内の他のすべてのブロックは、そのメソッド内に隠された情報になりますが、メソッド内の各ブロックは、そのメソッド内の他のブロックにジャンプする可能性があります。

メソッドごとのブロック数の観点からメソッドのサイズを決定するには、1つの質問として、すべてのブロック間の依存関係(MPE)の最大数を最小限に抑えるためにいくつのメソッドを使用する必要がありますか?

その答えは方程式で与えられます。rがシステムのMPEを最小化するメソッドの数であり、nがシステム内のブロックの数である場合、方程式は次のとおりです。r = sqrt(n)

また、これによりメソッドごとのブロック数がsqrt(n)になることも示されています。


4

結局のところ、リファクタリングのためにリファクタリングを行うことになり、コードが最初よりも読みにくくなる可能性があることに注意してください。

私の元同僚は、関数/メソッドに4行のコードのみを含める必要があるという奇妙な規則を持っていました。彼は非常に厳格にこれに固執しようとしました。そのため、彼のメソッド名はしばしば反復的で無意味になり、呼び出しは深くネストされて混乱しました。

ですから、私自身のマントラになります。リファクタリングしているコードのチャンクの適切な関数/メソッド名を考えることができない場合は、気にしないでください。


2

私が通常関数を分解する主な理由は、ビットとその一部が、私が書いている近くの別の関数の要素でもあるため、共通の部分が分解されるためです。また、他のクラスの多くのフィールドまたはプロパティを使用している場合は、関連するチャンクをホールセールから持ち上げて、可能であれば他のクラスに移動できる可能性が高くなります。

先頭にコメントが付いたコードブロックがある場合は、その目的を示す関数名と引数名を付けてコードを関数に引き出し、コメントをコードの根拠として予約することを検討してください。

他の場所で役立つと思われる部分がそこにないのは確かですか?どんな機能ですか?


この関数は、URLに基​​づいて、テンプレートからキャッシュファイルを作成します。たとえば、URL / post / 2009/01/01からのpost_2009_01_01.htmlのような回答をありがとう

2

私の意見では、答えは次のとおりです。関数は、関数自体の名前から期待されるアクションのみを実行する必要があります。考慮すべきもう1つの点は、関数の一部を他の部分で再利用する場合です。この場合、分割すると便利です。


2

私は通常、次のコードブロックを説明するコメントを配置する必要があるため、関数を分割します。以前にコメントに含まれていたものは、新しい関数名に入ります。これは難しいルールではありませんが、(私にとって)良い経験則です。私はコメントが必要なコードよりも自分で話すのが好きです(コメントが通常あることを知っているので)


$ variableが定義された場所に関する多くの質問を排除するために、主に私のためではなく他の人のために私のコードにコメントを付けたいのですが、コードが自明であることも好きです。コメントは嘘をつきますか?

はい、そうではないので、維持されていません。これを書いている時点では正しいかもしれませんが、バグ修正や新機能が導入されると、新しい状況に応じてコメントを強制的に変更する人はいません。メソッド名はコメントよりもはるかに少ない頻度である傾向があるIMHO
Olaf Kock

私はこの答えに出くわしました:stackoverflow.com/questions/406760/…「コード内のほとんどのコメントは実際には悪質な形式のコード複製である」と述べています。また-コメントが長いです。
オラフコック

1

これは部分的には好みの問題ですが、私がこれをどのように判断するかは、一度に(最大で)画面に収まる長さだけおおよその機能を維持しようとすることです。その理由は、全体を一度に見ることができれば、何が起こっているのかを理解しやすいからです。

私がコーディングするとき、それは長い関数を書くことと、他の関数で再利用できるビットを引き出すためにリファクタリングすることの混合です-そして-私が行くにつれて離散的なタスクを行う小さな関数を書くこと。

これに対する正解または不正解があるかどうかはわかりません(たとえば、67行を最大として解決してもかまいませんが、さらに数行追加するのが理にかなっている場合もあります)。


まあ私は画面に私の完全な機能を見ることも好きです:)時にはそれはモノスペース9フォントと黒い背景の大きな解像度を意味します、私はそのようにそれを理解する方が簡単であることに同意します。

1

このトピックについて徹底的な調査が行われています。バグを最小限に抑えたい場合は、コードが長くなりすぎないようにしてください。しかし、短すぎてはいけません。

メソッドが1つの画面に収まるようにする必要はありませんが、ページを下にスクロールすると、メソッドが長すぎます。

詳細については、オブジェクト指向ソフトウェアの最適なクラスサイズを参照してください 。


リンクをお寄せいただきありがとう

1

これまでに500行の関数を記述しましたが、これらはメッセージをデコードして応答するための大きなスイッチステートメントにすぎませんでした。1つのメッセージのコードが1つのif-then-elseよりも複雑になったとき、私はそれを抽出しました。

本質的に、関数は500行でしたが、独立して維持された領域は平均して5行でした。


1

私は通常、コードを書くためにテスト駆動のアプローチを使用します。このアプローチでは、関数のサイズは多くの場合、テストの粒度に関連しています。

テストが十分に集中している場合、テストに合格するための小さな集中関数を作成することになります。

これは逆方向にも機能します。関数は、効果的にテストするために十分に小さい必要があります。そのため、レガシーコードを操作するとき、さまざまな部分をテストするために、大きな関数を分解することがよくあります。

普段は「この機能の責任は何か」と自問自答し、責任を明快で簡潔に記述できない場合は、それを小さな集中テストに変換すると、機能が大きすぎるのではないかと思います。


1

分岐が4つ以上ある場合、これは通常、関数またはメソッドを分解して、分岐ロジックを異なるメソッドにカプセル化する必要があることを意味します。

各forループ、ifステートメントなどは、呼び出し側のメソッドの分岐とは見なされません。

Cobertura for Javaコード(および他の言語用の他のツールがあると確信しています)は、関数ごとにifなどの数を計算し、それを「平均的な循環的複雑度」について合計します。

関数/メソッドにブランチが3つしかない場合、そのメトリックでは3つが得られ、非常に優れています。

このガイドラインに従うこと、つまりユーザー入力を検証することが難しい場合があります。それにもかかわらず、ブランチをさまざまなメソッドに配置することは、開発とメンテナンスだけでなくテストにも役立ちます。ブランチを実行するメソッドへの入力を簡単に分析して、テストケースに追加する必要のあるブランチをカバーするために、どの入力を追加する必要があるかを確認できるためです。カバーされませんでした。

すべてのブランチが単一のメソッド内にある場合、メソッドの開始以降、入力を追跡する必要があり、テストが困難になります。


0

これについてはたくさんの答えがあると思います。

関数内で実行されていた論理的なタスクに基づいて、おそらくそれを分解します。あなたの短編小説が小説に変わっているように見えるなら、私は明確なステップを見つけて抽出することを勧めます。

たとえば、ある種の文字列入力を処理して文字列の結果を返す関数がある場合、文字列を部分に分割するロジック、追加の文字を追加するロジック、およびそれを配置するロジックに基づいて関数を分割することができます。フォーマットされた結果として再びすべて一緒に戻ります。

要するに、コードをクリーンで読みやすくするもの(それが単に関数に適切なコメントを付けたり、分解したりすることを保証することによるものであったり)が最善のアプローチです。


0

あなたが一つのことをしていると仮定すると、長さは以下に依存します:

  • 何をしているの
  • あなたが使っている言語
  • コードで処理する必要がある抽象化のレベル数

60行は長すぎるか、ちょうど良いかもしれません。でも長すぎるかもしれません。


私はPHPでキャッシングを行っています。おそらく60行は多すぎるため、リファクタリングしています...

0

1つのこと(そしてそのことは関数名から明らかであるはずです)ですが、それに関係なく、画面全体のコードにすぎません。そして、フォントサイズを自由に増やしてください。疑わしい場合は、2つ以上の関数にリファクタリングします。


0

ボブおじさんからのツイートの趣旨を少し引き延ばして、2行のコードの間に空白行を入れる必要があると感じたときに、関数が長くなりすぎていることがわかります。コードを分離するために空白行が必要な場合、その時点でその責任とスコープが分離しているという考えです。


0

私の考えでは、長すぎるかどうかを自問する必要がある場合、おそらく長すぎるでしょう。これは、アプリケーションのライフサイクルの後半で役立つ可能性があるため、この領域でより小さな関数を作成するのに役立ちます。

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