35行、55行、100行、300行?いつそれをバラバラにし始めるべきですか? 60行(コメントを含む)の関数があり、それを分解することを考えていたので、私は尋ねています。
long_function(){ ... }
に:
small_function_1(){...}
small_function_2(){...}
small_function_3(){...}
関数はlong_functionの外部で使用されることはありません。小さな関数を作成すると、より多くの関数呼び出しなどが行われます。
関数をいつ小さな関数に分解しますか?どうして?
回答に感謝します、リストを編集して正しい答えに投票します;)
これらのアイデアを念頭に置いてリファクタリングしています:)
それに対する実際の難しいルールはありません。一般に、私は自分の方法が「1つのことをする」だけのようにしています。したがって、データを取得し、そのデータを使用して何かを実行し、それをディスクに書き込む場合、取得と書き込みを別々のメソッドに分割して、「main」メソッドに「doing」を含めるだけにします。
しかし、「何かをする」ことはまだかなりの行になる可能性があるので、多くの行が使用するのに適切なメトリックであるかどうかはわかりません:)
編集:これは先週仕事の周りに郵送したコードの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();
以下は、関数が長すぎることを示す可能性のある赤旗のリストです(順不同)。
深くネストされた制御構造:例for-loops深さ3レベル、または複雑な条件を持つネストされたif文を含む深さ2レベル。
state-definingparameters:Bystate-defining parameter、私は関数を通る特定の実行パスを保証する関数パラメーターを意味します。これらのタイプのパラメーターが多すぎると、実行パスの組み合わせが急増します(これは通常、#1と並行して発生します)。
他の方法で複製されたロジック:貧弱なコードの再利用は、モノリシック手続き型コードの大きな貢献者です。このようなロジックの複製の多くは非常に微妙ですが、リファクタリングすると、最終結果ははるかにエレガントなデザインになります。
過剰なクラス間結合:この適切なカプセル化の欠如により、関数は他のクラスの親密な特性に関係するようになり、したがってそれらが長くなります。
不要なオーバーヘッド:ネストされたプライベートクラス変数の明白で深くネストされたクラス、余分なゲッターとセッター、および異常に長い関数/変数名を指摘するコメントはすべて、関連関数内で最終的に増加する構文ノイズを作成する可能性がありますそれらの長さ。
開発者向けの巨大なディスプレイは、表示するのに十分な大きさではありません:実際、今日のディスプレイは十分に大きいため、その高さに近い場所にある関数はおそらく長すぎます。しかし、それがlargerの場合、これは何かが間違っているという喫煙銃です。
関数の目的をすぐに決定することはできません:さらに、実際にdoこの目的を一文で要約したり、頭を痛めたりすることがありますが、これが手がかりになるはずです。
結論として、モノリシック機能は広範囲に及ぶ結果をもたらす可能性があり、多くの場合、主要な設計上の欠陥の症状です。読むのが絶対的喜びであるコードに遭遇するたびに、優雅さがすぐに明らかになります。そして、推測してください:関数はしばしばvery長さが短いです。
このページの「1つのことだけを行う」というマントラには大きな警告があると思います。 1つのことを行うと、多くの変数が調整されることがあります。小さい関数が長いパラメータリストを持つことになった場合、長い関数を小さな関数の束に分割しないでください。これを行うと、1つの関数が実際の個々の値を持たない高度に結合された関数のセットに変わります。
関数が行うことは1つだけです。関数で多くの小さなことをしている場合は、それぞれの小さなことを関数にして、それらの関数を長い関数から呼び出します。
あなたが本当にdo n'tしたいのは、長い関数の10行ごとにコピーして、短い関数に貼り付けることです(例が示すように)。
関数は1つのことだけを行う必要があることに同意しますが、その1つのことはどのレベルで行われますか。
60行が1つのことを達成し(プログラムの観点から)、それらの60行を構成する部分が他の何かによって使用されない場合、60行は問題ありません。
それを独立した具体的な断片に分割できない限り、それを分割することには本当の利点はありません。使用するメトリックは機能であり、コード行ではありません。
私は多くのプログラムに取り組んできましたが、作者はたった1つのことを極端なレベルまで進め、最終的には誰かが手/弾を関数/メソッドに持って行って、それを何十もの接続されていない部分に吹き飛ばしたように見せましたフォローするのは難しい。
その関数の一部を引き出すとき、不必要なオーバーヘッドを追加するかどうかを検討し、大量のデータを渡すことを避ける必要もあります。
キーポイントは、その長い機能で再利用性を探し、それらの部品を引き出すことだと思います。残っているのは、長さ10、20、または60行の関数です。
60行は大きいですが、機能には長すぎません。エディターの1つの画面に収まる場合は、一度にすべてを見ることができます。それは本当に機能が何をしているかに依存します。
機能を分割する理由:
おおよその画面サイズのサイズ(だから大きなピボットワイドスクリーンを手に入れて回してください)... :-)
冗談はさておき、関数ごとに1つの論理的なこと。
そして、ポジティブなことは、1つのことを行う小さな論理関数を使用すると、ユニットテストが非常に簡単になることです。多くのことを行う大きな機能は、検証が困難です!
/ヨハン
経験則:関数に何かを行うコードブロックが含まれている場合、それは残りのコードからある程度切り離されている場合は、別の関数に入れます。例:
_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つの利点があります。
特定の郵便番号の住所を取得する必要があるときはいつでも、すぐに使用できる機能を使用できます。
関数build_address_list_for_Zip()
を再度読み取る必要があるときは、最初のコードブロックが何をするかを知っています(特定のZipコードのアドレスを取得します。少なくとも関数名から導出できることです)。クエリコードをインラインのままにした場合、最初にそのコードを分析する必要があります。
[一方で(拷問の下でさえ、私はあなたにこれを言ったことを否定します):PHP最適化について多くを読んだ場合、あなたは関数の数をできるだけ少なく保つ考えを得ることができますPHPでは関数呼び出しが非常に高価なため、可能性があります。ベンチマークを実行したことがないので、それについては知りません。非常に「パフォーマンスに敏感」;-)]
McCabeのサイクロマティックを覗いて、コードをグラフに分割します。「グラフ内の各ノードは、プログラム内のコードブロックに対応し、フローはシーケンシャルで、アークはプログラム内の分岐に対応します。 」
コードに関数/メソッドがないことを想像してください。グラフ形式のコードのたった1つの巨大な広がり。
このスプロールをメソッドに分割したい場合。実行すると、各メソッドに特定の数のブロックが存在することを考慮してください。各メソッドの1つのブロックのみが他のすべてのメソッドに表示されます:最初のブロック(1つのポイントのみでメソッドにジャンプできると仮定しています:最初のブロック)。各メソッド内の他のすべてのブロックは、そのメソッド内に隠された情報ですが、メソッド内の各ブロックは、そのメソッド内の他のブロックにジャンプする可能性があります。
メソッドごとのブロック数の観点からメソッドのサイズを決定するために、あなたが自問するかもしれない質問の1つは、すべてのブロック間の依存関係(MPE)の潜在的な最大数を最小化するためにいくつのメソッドが必要ですか?
その答えは方程式で与えられます。 rがシステムのMPEを最小化するメソッドの数であり、nがシステム内のブロックの数である場合、方程式は次のようになります。r = sqrt(n)
また、これにより、メソッドごとのブロック数がsqrt(n)になることを示すことができます。
私の個人的な経験則では、スクロールせずにすべてを見ることができない場合は長すぎます。
リファクタリングのためだけにリファクタリングを行う可能性があり、コードが最初よりも読みにくくなる可能性があることに留意してください。
私の元同僚は、関数/メソッドには4行のコードのみを含める必要があるという奇妙なルールがありました。彼はこれに厳格に固執しようとしたので、メソッド名はしばしば反復的で無意味になり、さらに呼び出しは深く入れ子になり、混乱しました。
ですから、私自身の信念は次のとおりです。リファクタリングしているコードのチャンクに対して適切な関数/メソッド名が思いつかない場合は、気にしないでください。
私が通常関数を分解する主な理由は、それの断片が、私が書いている近くの別の関数の成分でもあるためです。そのため、共通部分は因数分解されます。また、他のクラスの多くのフィールドまたはプロパティを使用している場合、関連するチャンクを大規模に持ち上げて、可能であれば他のクラスに移動できる可能性が高くなります。
上部にコメントのあるコードブロックがある場合は、その目的を説明する関数名と引数名を使用して、それを関数に引き出し、コードの理論的根拠のためにコメントを予約することを検討してください。
他の場所で役立つと思われる部分はありませんか?どのような機能ですか?
私の意見では、答えは次のとおりです。関数は、関数自体の名前から期待するアクションのみを実行する必要があります。考慮すべきもう1つのことは、関数の一部を他の部分で再利用したい場合です。この場合、分割すると便利です。
私は通常、次のコードブロックを説明するコメントを配置する必要があるため、機能を分割します。以前はコメントに入力されていたものが、新しい関数名に入力されます。これは難しい規則ではありませんが、(私にとっては)いい経験則です。私は、コメントが必要なコードよりも自分自身で話すコードが好きです(通常、コメントは嘘をついていることを学びました)
私は通常、コードを書くためにテスト駆動のアプローチを使用します。このアプローチでは、多くの場合、関数のサイズはテストの粒度に関連しています。
テストが十分に集中している場合、テストに合格するための小さな集中関数を作成するように導きます。
これは逆方向にも機能します。関数は、効果的にテストするために十分小さい必要があります。そのため、レガシーコードを使用する場合、より大きな関数を分解して、それらのさまざまな部分をテストすることがよくあります。
私は通常「この関数の責任は何か」と自問します。明確な簡潔な文で責任を表明できず、それを小さな焦点のテストに変換できない場合、関数が大きすぎるのではないかと思います。
これは部分的には好みの問題ですが、これをどのように判断するかは、一度に(最大で)画面に収まる程度にのみ機能を維持することです。その理由は、一度にすべてを見ることができれば、何が起こっているのかを理解しやすいからです。
私がコーディングするときは、長い関数を作成し、リファクタリングして、他の関数で再利用できるビットを引き出し、さらに、個別のタスクを実行する小さな関数を作成します。
これに対する正解または不正解があることはわかりません(たとえば、最大値として67行に収まる場合がありますが、さらに追加することが理にかなっている場合があります)。
このトピックに関しては徹底的な調査が行われています。バグを最小限に抑えたい場合は、コードを長くしすぎないようにしてください。しかし、短すぎてもいけません。
メソッドが1つのディスプレイに収まる必要があることに同意しませんが、1ページ以上スクロールダウンする場合、メソッドは長すぎます。
詳細については、 オブジェクト指向ソフトウェアの最適なクラスサイズ を参照してください。
以前に500行の関数を作成しましたが、これらはメッセージをデコードして応答するための単なる大きなswitchステートメントでした。単一のメッセージのコードが単一のif-then-elseよりも複雑になったとき、私はそれを抽出しました。
本質的に、関数は500行ですが、独立して維持される領域は平均5行です。
3つ以上のブランチがある場合、一般に、これは関数またはメソッドを分解して、分岐ロジックを異なるメソッドにカプセル化する必要があることを意味します。
そのため、各forループ、ifステートメントなどは、呼び出し元のメソッドのブランチとは見なされません。
Cobertura for Javaコード(および他の言語用の他のツールがあると確信しています)は、各関数の関数内のifなどの数を計算し、「平均循環的複雑度」 「。
関数/メソッドに3つのブランチしかない場合、そのメトリックで3つを取得します。これは非常に良いことです。
このガイドラインに従うこと、つまりユーザー入力を検証することは難しい場合があります。それでも、分岐を異なる方法で配置すると、分岐を実行するメソッドへの入力を簡単に分析して、どの入力をテストケースに追加する必要があるかを確認できるため、開発と保守だけでなくテストにも役立ちます。カバーされませんでした。
すべてのブランチが単一のメソッド内にある場合、メソッドの開始以降、入力を追跡する必要があり、テスト性が妨げられます。
これについて多くの答えが見つかると思います。
おそらく、関数内で実行されていた論理タスクに基づいて分割します。あなたの短編小説が小説に変わっていると思われる場合は、明確なステップを見つけて抽出することをお勧めします。
たとえば、何らかの文字列入力を処理して文字列結果を返す関数がある場合、文字列を部分に分割するロジック、余分な文字を追加するロジック、およびそれを配置するロジックに基づいて関数を分割することができます書式設定された結果としてすべて一緒に戻ります。
要するに、コードをきれいで読みやすくするもの(関数に適切なコメントを付けたり、分割したりすることによる)が最善のアプローチです。
あなたがone事をしていると仮定すると、長さは依存します:
60行は長すぎるか、ちょうどいいかもしれません。私はそれが長すぎるかもしれないと思う。
1つのこと(およびそのことは関数名から明らかです)が、それでも、画面いっぱいのコードにすぎません。フォントサイズを自由に増やしてください。疑わしい場合は、2つ以上の関数にリファクタリングします。
私の考えは、長すぎると自問しなければならない場合、おそらく長すぎるということです。この領域では、アプリケーションのライフサイクルの後半で役立つ可能性があるため、小さな機能を作成するのに役立ちます。
ボブおじさんからのツイートの精神を少し前に延ばすと、2行のコードの間に空白行を入れる必要があると感じたときに関数が長くなりすぎていることがわかります。コードを分離するために空白行が必要な場合、その責任と範囲はその時点で分離されるという考えです。