そのため、以前見回してみると、長い方法が悪い習慣であるというコメントに気づきました。
長い方法が悪いといつも同意するかどうかはわかりません(他の人からの意見を求めています)。
たとえば、いくつかのDjango=ビューがあり、オブジェクトをビューに送信する前にオブジェクトの処理を少し行っています。長いメソッドは350行のコードです。パラメータを使用して-クエリセットを並べ替え/フィルタリングすると、クエリが返したオブジェクトに対して少しずつ処理が行われます.
したがって、処理は主に条件付き集計であり、データベースでは簡単に実行できない十分に複雑なルールがあるため、メインループの外で宣言されたいくつかの変数がループ中に変更されます。
variable_1 = 0
variable_2 = 0
for object in queryset :
if object.condition_condition_a and variable_2 > 0 :
variable 1+= 1
.....
...
.
more conditions to alter the variables
return queryset, and context
したがって、理論によれば、すべてのコードを小さなメソッドに分解する必要があるため、viewメソッドは最大で1ページの長さになります。
ただし、過去にさまざまなコードベースに取り組んだことがあるので、1つのメソッドから次のメソッドに常にジャンプして、最も外側のメソッドを頭に置いておく必要がある場合、コードが読みにくくなることがあります。
長い形式のメソッドが適切にフォーマットされていると、内部のメソッドに隠れてしまうことがないため、ロジックをより簡単に確認できます。
コードを小さなメソッドに分解することもできますが、多くの場合、2つまたは3つの目的で使用される内部ループがあるため、より複雑なコード、または2つまたは3つしか実行しないメソッド(代わりにタスクごとに内部ループを繰り返すこともできますが、パフォーマンスに影響します)。
それで、長い方法が必ずしも悪いとは限らない場合がありますか?メソッドを1つの場所でのみ使用する場合、メソッドを記述するケースは常にありますか?
更新:1年以上前にこの質問をしたようです。
そこで、ここで(混合)応答の後にコードをリファクタリングし、メソッドに分割しました。これは、Djangoアプリがデータベースから関連するオブジェクトの複雑なセットを取得するため、テスト引数が出ていません(テストケースに関連するオブジェクトを作成するために、おそらく1年の大半を費やしたでしょう。不平を言う前に、「これは昨日必要な」タイプの作業環境を持っている)コードのその部分のバグを修正することは今やや簡単ですが、大規模ではありません。
前 :
#comment 1
bit of (uncomplicated) code 1a
bit of code 2a
#comment 2
bit of code 2a
bit of code 2b
bit of code 2c
#comment 3
bit of code 3
今:
method_call_1
method_call_2
method_call_3
def method_1
bit of (uncomplicated) code 1a
bit of code 2a
def method_2
bit of code 2a
bit of code 2b
bit of code 2c
def method_3
bit of code 3
いいえ、長い方法は必ずしも悪いことではありません。
本Code Completeでは、長いメソッドの方が高速で簡単に記述できることがあり、メンテナンスの問題につながらないことが測定されています。
実際、本当に重要なのは、DRY=を維持し、懸念の分離を尊重することです。時々、計算は書くのに時間がかかりますが、将来的に問題が発生することはありません。
しかし、私の個人的な経験から、ほとんどの長い方法は、関心の分離に欠ける傾向があります。実際、長いメソッドは、コードに問題がある可能性があることを検出する簡単な方法であり、コードのレビューを行う際には特別な注意が必要です。
編集:コメントが作成されたら、興味深い点を回答に追加します。実際には、関数の複雑度メトリック(NPATH、循環的複雑度、またはさらに優れたCRAP)もチェックします。
実際、私は長い関数のそのようなメトリックをチェックするのではなく、自動ツール(Javaなど)のcheckstyleなど)でアラートを含めることをお勧めします。
ここでの焦点のほとんどは、Wordalwaysにあります。はい、絶対的なものは悪く、ソフトウェアエンジニアリングは科学と同じくらい芸術です。しかし、私はあなたが挙げた例では、分割された方が方法が優れていると言わざるを得ません。アップ。これらは、メソッドの分割を正当化するために通常使用する引数です。
読みやすさ:他人についてはわかりませんが、350行のコードをすばやく読み取ることができません。はい、それが私のownコードであり、私がそれについて多くの仮定を行うことができる場合、私はcould非常に迅速にそれをすくい取りますが、それはポイントの外です。そのメソッドが他のメソッドへの10回の呼び出し(それぞれに説明的な名前が付いている)で構成されている場合、そのメソッドがどれほど読みやすいかを検討してください。これを行うことで、コードにレイヤーを導入しました。高レベルのメソッドは、何が起こっているのかについて、読者に短くて甘い概要を提供します。
編集-それを別の観点から見ると、次のように考えてください。その方法を新しいチームメンバーにどのように説明しますか?確かにsome構造を持っているので、「まあ、A、B、Cといった具合に」のように要約できます。他のメソッドを呼び出す短い「概要」メソッドがあると、この構造が明確になります。利益のない350行のコードを見つけることは非常にまれです。人間の脳は、何百ものアイテムのリストを処理することを目的としていません。それらをグループ化します。
再利用性:長いメソッドは凝集度が低くなる傾向があります-それらはしばしば複数のことを行います。凝集度が低いことが再利用の敵です。複数のタスクを1つのメソッドに組み合わせると、本来よりも少ない場所で再利用されることになります。
テスト容易性と結束性:上記のコメントで 循環的複雑度 について述べました-これは、メソッドがどれほど複雑かを示す非常に優れた指標です。入力に応じて、コードを通る一意のパス数の下限を表します(編集:MichaelTのコメントに従って修正)。また、メソッドを適切にテストするには、少なくとも循環的複雑度と同じ数のテストケースが必要になることを意味します。残念ながら、実際には互いに依存していないコードをまとめると、依存関係がないことを確認する方法はなく、複雑さが増す傾向があります。この指標は、実行しようとしているさまざまなことの数を示すものと考えることができます。高すぎる場合は、分割して征服する時です。
リファクタリングと構造:長いメソッドは、多くの場合、一部の構造がコードに欠けていることを示しています。多くの場合、開発者は、そのメソッドのさまざまな部分の共通点、およびそれらの間に線を引くことができる場所を理解できませんでした。長いメソッドが問題であることに気づき、それを小さなメソッドに分割することは、全体のより良い構造を実際に識別するための長い道のりの最初のステップです。おそらく、1つまたは2つのクラスを作成する必要があります。結局、必ずしも複雑になるとは限りません。
また、この場合、長いメソッドを持つことの言い訳は、「メインループの外で宣言されたいくつかの変数は、ループ中に変更される」だと思います。私はPythonの専門家ではありませんが、この問題がなんらかの形で参照渡しすることで簡単に修正できると確信しています。
長い方法は常に悪いことですが、他の方法よりも優れている場合があります。
長いメソッドは code smell です。彼らは通常、何かが間違っていることを示しますが、それは厳格な規則ではありません。通常、それらが正当化されるケースには、多くの州およびかなり複雑なビジネスルールが含まれます(ご存じのとおり)。
あなたの他の質問に関しては、たとえそれらが一度だけ呼び出されたとしても、ロジックのチャンクを別々のメソッドに分離することはしばしば役に立ちます。これにより、高レベルのロジックが見やすくなり、例外処理が少しすっきりします。処理状態を表すために20個のパラメーターを渡す必要がない限り、
長い方法は必ずしも悪いことではありません。これらは通常、問題がある可能性があることを示しています。
私が取り組んでいるシステムには、1万行を超える半ダースほどのメソッドがあります。 1つは現在54,830行です。そして、それは大丈夫です。
これらの途方もなく長い関数は非常に単純で、自動生成されます。その大きな54,830行の長いモンスターには、1962年1月1日から2012年1月10日(最後のリリース)までの毎日の極地運動データが含まれています。また、自動生成されたファイルをユーザーが更新できる手順もリリースしています。そのソースファイルには http://data.iers.org/products/214/14443/orig/eopc04_08_IAU2000.62-now からの極座標モーションデータが含まれており、C++に自動変換されます。
安全なインストールでは、そのWebサイトをその場で読むことはできません。外界とのつながりはありません。 Webサイトをローカルコピーとしてダウンロードし、C++で解析することもできません。解析はslowであり、これは高速でなければなりません。ダウンロード、C++への自動翻訳、コンパイル:高速なものを手に入れました。 (最適化してコンパイルしないでください。最適化コンパイラーが50,000行の非常に単純な直線コードをコンパイルするのにどれだけ時間がかかるかは驚くべきことです。私のコンピューターで最適化された1つのファイルをコンパイルするのに30分以上かかります。そして、最適化はまったく何もしません。 。最適化するものは何もありません。これは、1つの代入ステートメントを次々と実行する単純な直線コードです。
長いメソッドを分割する良い方法と悪い方法があるとだけ言ってみましょう。 「頭の中で最も外側のメソッドを維持する」ことは、最適な方法で分割していないこと、またはサブメソッドの名前が不適切であることを示しています。理論的には、長い方法の方が良い場合があります。実際には、それは非常にまれです。短いメソッドを読みやすくする方法がわからない場合は、誰かにコードをレビューしてもらい、メソッドの短縮に関するアイデアを具体的に尋ねてください。
想定されるパフォーマンスヒットを引き起こす複数のループについては、測定せずにそれを知る方法はありません。必要なすべてのものがキャッシュ内にとどまることができる場合、複数の小さなループが大幅に高速になる可能性があります。パフォーマンスに影響があっても、通常は読みやすさを優先して無視できます。
多くの場合、長いメソッドはreadの方が難しいにもかかわらず、writeの方が簡単です。だから誰も気に入らなくても増殖するのです。チェックインする前の最初からリファクタリングまでの計画に問題はありません。
長いメソッドは、計算とスペース効率が向上し、ロジックを確認しやすく、デバッグが容易になります。ただし、これらのルールは、1人のプログラマのみがそのコードに触れる場合にのみ実際に適用されます。コードがアトミックでない場合、コードを拡張するのは面倒です。基本的に、次の人はゼロから始めて、既知の優れたコードを使用していないので、これをデバッグおよびテストする必要があります。
長い関数を書くのはまったく問題ありません。しかし、本当に必要かどうかは、状況によって異なります。たとえば最良のアルゴリズムのいくつかは、それが適切な場合に最もよく表現されます。一方、オブジェクト指向プログラムのルーチンの大部分は、非常に短いアクセサルーチンになります。テーブル駆動の方法で条件を最適化できる場合は、スイッチケースが長くなる長い処理ルーチンの一部。
ルーチンの長さについては、Code Complete 2で優れた短い議論があります。
理論的に最適な最大長497は、多くの場合、1〜2ページのプログラムリスト、66〜132行で表されます。最近のプログラムでは、非常に短いルーチンのボリュームがいくつかの長いルーチンと混在している傾向があります。
何十年にもわたる証拠によれば、そのような長さのルーチンは、短いルーチンよりもエラーを起こしにくいということです。ネストの深さ、変数の数、およびその他の複雑さに関する考慮事項などの問題が、長さを課すのではなく、ルーチンの長さを指定することを許可します。
約200行を超えるルーチンを記述したい場合は注意してください。コストの低下、エラー率の低下、またはその両方が200行を超えるサイズで区別されていることを報告した研究はありません。200行のコードを渡すと、理解しやすさの上限にぶつかることになります。 536の制限自体。
我々が呼ぶものがある Functional Decomposition 長いメソッドを可能な限り小さなものに分割することを意味する。あなたの方法がソート/フィルタリングを含むと述べたように、あなたはこれらのタスクのために別々の方法または機能を持っている方が良いです。
正確には、メソッドは1つのタスクのみを実行することに集中する必要があります。
そして、何らかの理由で別のメソッドdorを呼び出す必要がある場合は、そうしないでください。そうでない場合は、すでに記述しているメソッドを続行してください。また、読みやすさを考慮して、コメントを追加できます。従来、プログラマーはメソッドの説明に複数行コメント(C、C++、C#、Javaでは/ ** /)を使用し、単一行コメント(C、C++、C#、Javaでは//)を使用しています。コードを読みやすくするための優れたドキュメントツールもあります(例 JavaDoc )。 .Net開発者であれば、 XMLベースのコメント を調べることもできます。
ループはプログラムのパフォーマンスに影響を与え、適切に使用しないとアプリケーションのオーバーヘッドを引き起こす可能性があります。考えは、ネストされたループをできるだけ使用しないようにアルゴリズムを設計することです。
私はあなたが与えた例に取り組みたいと思いました:
たとえば、いくつかのDjango=ビューがあり、オブジェクトをビューに送信する前にオブジェクトの処理を少し行っています。長いメソッドは350行のコードです。パラメータを使用して-クエリセットを並べ替え/フィルタリングすると、クエリが返したオブジェクトに対して少しずつ処理が行われます.
私の会社では、私たちの最大のプロジェクトはDjango=に基づいて構築されており、長いビュー関数も持っています(多くは350行を超えています)。彼らは私たちを傷つけています。
これらのビュー関数は、モデル、ヘルパークラス、またはヘルパー関数に抽出する必要がある、大まかに関連する多くの作業を行っています。また、さまざまなことを行うためにビューを再利用することになり、代わりに、よりまとまりのあるビューに分割する必要があります。
あなたの見解には同様の特徴があると思います。私の場合、問題が発生することがわかっているので、変更に取り組んでいます。それが問題の原因であることに同意しない場合は、修正する必要はありません。
それはほとんど常に間違っているという別の投票。しかし、それが適切な答えである2つの基本的なケースを見つけます。
1)基本的には他のメソッドの束を呼び出すだけで、実際には機能しないメソッド。達成するために50のステップを必要とするプロセスがあり、50の呼び出しを持つメソッドを取得します。通常、それを分解しようとしても何も得られません。
2)コーディネーター。 OOPデザインはそのようなメソッドのほとんどを取り除きましたが、データの入力ソースはその性質上単なるデータであり、したがってOOPの原則に従うことはできません。データを処理するコードにある種のディスパッチャールーチンがあるのはまったく珍しいことです。
また、自動生成されたものを処理するときは、質問を考慮すべきではないとも言えます。自動生成されたコードが何をするのかを理解しようとする人はいません。人間が理解しやすいかどうかは重要ではありません。
誰かがすでにこれについて言及しているかどうかはわかりませんが、長いメソッドが悪い理由の1つは、通常、いくつかの異なるレベルの抽象化が含まれるためです。ループ変数とあらゆる種類のことが起こっています。架空の関数を考えてみましょう:
function nextSlide() {
var content = getNextSlideContent();
hideCurrentSlide();
var newSlide = createSlide(content);
setNextSlide(newSlide);
showNextSlide();
}
その関数ですべてのアニメーション、計算、データアクセスなどを行っていたら、それは混乱していたでしょう。 nextSlide()関数は、一貫性のある抽象化レイヤー(スライド状態システム)を維持し、他のものを無視します。これにより、コードが読みやすくなります。
常に小さなメソッドに足を踏み入れてそれらの機能を確認する必要がある場合は、関数を分割する練習は失敗しました。読んでいるコードが子メソッドで明白なことをしていないからといって、子メソッドが悪い考えであるとは限りません。
メソッドを作成するとき、私は通常、一種の分割統治戦略として、それらを小さなメソッドに分割することになります。のような方法
if (hasMoreRecords()) { ... }
確かにより読みやすいです
if (file.isOpen() && i < recordLimit && currentRecord != null) { ... }
正しい?
絶対的な発言が悪いことにも同意します。また、通常、長い方法は悪いことにも同意します。
実話。私はかつて2000行を超える長さのメソッドに遭遇しました。メソッドには、それらの領域で何が行われているかを説明する領域がありました。リージョンを読んだ後、自動抽出メソッドを実行し、リージョン名に従って名前を付けることにしました。私が完了するまでに、メソッドはそれぞれ約50行の40メソッド呼び出しにすぎず、すべて同じように動作しました。
大きすぎるのは主観的です。時々、メソッドは現在のメソッドよりも細かく分解できないことがあります。本を書くようなものです。ほとんどの人は、長い段落は通常分割する必要があることに同意します。ただし、アイデアが1つしかなく、それを分割すると、その段落の長さよりも混乱が生じる場合があります。
真実は、それは依存します。前述のように、コードが問題を分離せず、1つのメソッドですべてを実行しようとすると、問題になります。コードを複数のモジュールに分離すると、コードの読み取りや、複数のプログラマによるコードの書き込みが容易になります。まず、ソースファイルごとに1つのモジュール(クラス)を使用することをお勧めします。
第二に、それが関数/手順になると:
void setDataValueAndCheckForRange(Data *data) {/*code*/}
「データ」のみの範囲をチェックする場合は、良い方法です。同じ範囲が複数の関数に当てはまる場合、これは悪い方法です(悪いコードの例):
void setDataValueAndCheckForRange(Data *data){ /*code */}
void addDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}
void subDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}
void mulDataValuesAndCheckForRange(Data *result, Data *d1, Data *d2){ /*code*/}
これは、次のようにリファクタリングする必要があります。
bool isWithinRange(Data *d){ /*code*/ }
void setDataValue(Data *d) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/}
void addDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/}
void subDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/}
void mulDataValue(Data *d, Data *d1, Data *d2) {/*code*/ if(isWithinRange(d)){/*continue*/}else{/*warn/abort*/}
コードをできるだけ再利用します。そして、それは、プログラムの各関数が単純(必ずしも簡単ではない)で十分な場合に可能です。
QUOTE:MOUNTAINは、地球の小さな粒子で構成されています。海は小さな水滴でできています...(-Sivananda)
長いメソッドは、命令、副作用、および可変性を好む命令型言語では「悪い」傾向があります。これらの機能が複雑さを増し、バグを増やすからです。
式、純粋さ、および不変性を支持する関数型プログラミング言語では、心配する理由はあまりありません。
関数型言語と命令型言語の両方で、再利用可能なコードのチャンクを共通のトップレベルルーチンに分解するのが常に最善ですが、ネストされた関数などの字句スコープをサポートする関数型言語では、上位のサブルーチンを非表示にする方がカプセル化が実際に優れています。レベル関数(メソッド)を他のトップレベル関数に分解するよりも。
メソッドのポイントは、コードの逆流を減らすのを助けることです。メソッドには、そのメソッドが担当する特定の機能が必要です。多数の場所でコードの再ハッシュを行うと、ソフトウェアが対処するように設計された仕様が変更された場合に、コードが予期しない結果になるリスクがあります。
メソッドが350行ある場合、特殊なタスクを実行するためにそのような大量のコードが必要になることはまれであるため、実行しているタスクの多くが他の場所に複製されることを示唆しています。
悪い方法であるのは実際には長い方法ではなく、それが悪いようにそれらを残すことです。
つまり、サンプルをリファクタリングする実際の動作は次のとおりです。
varaible_1 = 0
variable_2 = 0
for object in queryset :
if object.condition_condition_a and variable_2 > 0 :
variable 1+= 1
.....
...
.
more conditions to alter the variables
return queryset, and context
に
Status status = new Status();
status.variable1 = 0;
status.variable2 = 0;
for object in queryset :
if object.condition_condition_a and status.variable2 > 0 :
status.variable1 += 1
.....
...
.
more conditions to alter the variables (status)
return queryset, and context
そして次に
class Status {
variable1 = 0;
variable2 = 0;
void update(object) {
if object.condition_condition_a and variable2 > 0 {
variable1 += 1
}
}
};
Status status = new Status();
for object in queryset :
status.update(object);
.....
...
.
more conditions to alter the variables (status)
return queryset, and context
これで、はるかに短い方法だけでなく、はるかに使いやすく、理解しやすい方法に向かっています。
この方法が非常に長いという事実は確認する必要があると思いますが、インスタントアンチパターンではありません。巨大なメソッドで探すべき大きなことは、ネストがたくさんあることです。あなたが持っている場合
foreach(var x in y)
{
//do ALL the things
//....
}
ループの本体が極端にローカライズされていない(つまり、4つのパラメーター未満で送信できる)場合は、おそらく次のように変換する方が良いでしょう。
foreach(var x in y)
{
DoAllTheThings(x);
}
...
void DoAllTheThings(object x)
{
//do ALL the things
//....
}
これにより、関数の長さが大幅に短縮されます。また、関数内で重複するコードを探して、それを別の関数に移動してください
最後に、いくつかのメソッドは長くて複雑であり、あなたができることは何もありません。いくつかの問題は、簡単にコード化できないソリューションを必要としています。たとえば、非常に複雑な文法に対して構文解析を行うと、メソッドを本当に長くすることができます。