内部構造がある場合、長い関数は受け入れられますか?


23

ネストされた関数(PythonやDなど)をサポートする言語で複雑なアルゴリズムを扱う場合、私はしばしば(アルゴリズムが複雑なため)巨大な関数を記述しますが、ネストされた関数を使用して複雑なコードを構成することでこれを緩和します。ネストされた関数を使用して内部的に適切に構造化されていても、巨大な(100行以上の)関数は依然として悪と見なされますか?

編集:PythonまたはDに精通していない人のために、これらの言語のネストされた関数は、外部関数スコープへのアクセスも許可します。Dでは、このアクセスにより、外部スコープ内の変数の突然変異が許可されます。Pythonでは、読み取りのみが許可されます。Dでは、宣言することにより、ネストされた関数の外部スコープへのアクセスを明示的に無効にできますstatic


5
私は定期的に400以上のライン関数/メソッドを書いています。その500件のcase switchステートメントをどこかに置いてください:)
Callum Rogers

7
@Callum Rogers:Pythonでは、500件のcase switchステートメントを使用する代わりに、ルックアップ辞書/マッピングを使用します。私は、500件程度のスイッチケースステートメントよりも優れていると思います。まだ500行の辞書定義が必要です(あるいは、Pythonではリフレクションを使用してそれらを動的に列挙できます)が、辞書定義はデータであり、コードではありません。また、大規模なデータ定義については、大規模な関数定義よりもはるかに少ない不安があります。さらに、データはコードよりも堅牢です。
ライライアン

たくさんのLOCを持つ関数を見るたびにうんざりして、モジュール化の目的は何なのかと思います。小さな関数でロジックを追跡してデバッグするのが簡単です。
アディティアP

古くて使い慣れたPascal言語も外部スコープへのアクセスを許可し、GNU Cのネストされた関数拡張も許可します(これらの言語は下向きfunargsのみを許可します:つまり、スコープを持つ関数である引数は、渡され、返されません。これには完全な字句解析のサポートが必要です)。
カズ

長くなりがちな関数の良い例は、リアクティブプログラミングを行うときに書くコンビネーターです。彼らは簡単に30行にヒットしますが、あなたが閉鎖を失うため、バラバラになるとサイズが2倍になります。
クレイグギドニー14

回答:


21

常にルールを覚えておいてください、関数は一つのことをして、それをうまくします!できる場合は、ネストされた関数を避けてください。

読みやすさとテストの妨げになります。


9
高レベルの抽象化で見たときに「1つのこと」を行う関数は、低レベルで見たときに「多くのこと」を行う可能性があるため、私は常にこのルールを嫌っていました。誤って適用された場合、「一つのこと」ルールは過度にきめの細かいAPIにつながる可能性があります。
dsimcha

1
@dsimcha:どのルールを誤用して問題を引き起こすことはできませんか?誰かが「ルール」/プラティチュードをそれらが有用なポイントを超えて適用しているとき、別のものを引き出すだけです:すべての一般化は偽です。

2
@Roger:正当な苦情。ただし、IMHOルールは、過度にきめ細かく、過剰に設計されたAPIを正当化するために、実際にはしばしば誤って適用されます。これには、APIが単純で一般的なユースケースで使用するのが難しくなり、冗長になるという具体的なコストがかかります。
dsimcha

2
「はい、ルールは誤って適用されていますが、ルールはあまりにも誤って適用されています!」:P

1
私の機能は1つのことを行い、それは非常にうまくいきます。つまり、27画面を占有します。:)
カズ

12

短い関数は長い関数よりもエラーが発生しやすいと主張する人もいます

Card and Glass(1990)は、設計の複雑さには実際には2つの側面が関係していることを指摘しています。各コンポーネント内の複雑さと、コンポーネント間の関係の複雑さです。

個人的には、よくコメントされた直線のコードは、他の場所で使用されない複数の関数に分割される場合よりも(特に最初に書いた人ではない場合)追跡しやすいことがわかりました。しかし、それは本当に状況に依存します。

主なポイントは、コードのブロックを分割するときに、ある種の複雑さを別のものと交換することだと思います。おそらく中央のどこかにスイートスポットがあります。


「クリーンコード」を読みましたか?これについて詳しく説明します。amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/...
マーティンWICKMAN

9

理想的には、スクロールすることなく機能全体を表示できるようにする必要があります。時々、これは不可能です。しかし、もしあなたがそれを断片に分割できるなら、それはコードを読むことをずっと簡単にします。

Page Up / Downを押すか、コードの別のセクションに移動するとすぐに、前のページから7 +/- 2つのことしか覚えていないことを知っています。残念ながら、これらの場所の一部は新しいコードを読み取るときに使用されます。

私は、コンピューターのレジスター(RISCではなく、CISC)のような短期記憶について常に考えています。同じページに関数全体がある場合は、キャッシュに移動して、プログラムの別のセクションから必要な情報を取得できます。関数全体がページに収まらない場合は、すべての操作後にメモリをディスクに常にプッシュすることと同じです。


3
マトリックスプリンターで印刷する必要があります-一度に10メートルのコードをすべて参照してください:)

または、より高い解像度のモニターを入手してください
frogstarr78

そして、垂直方向で使用します。
カルマリオス

4

通常の外部関数ではなく、ネストされた関数を使用する理由

外部関数が以前は大きな関数でしか使用されていなかったとしても、混乱全体を読みやすくします。

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}

2
考えられる2つの理由:名前空間の乱雑さと、「字句的近接」と呼ぶもの。これらは、あなたの言語に応じて、良い理由か悪い理由かもしれません。
フランクシェラー

ただし、ネストされた関数は、下向きfunargsおよび完全に拡張された字句クロージャーをサポートする必要があるため、効率が低下する可能性があります。言語の最適化が不十分な場合は、子関数に渡す動的環境を作成するために余分な作業を行う場合があります。
カズ

3

現時点では、本を目の前に持っていません(引用)が、Code Completeによると、関数の長さの「スイートスポット」は、彼の研究によれば、約25-50行のコードでした。

長い機能を使用しても問題ない場合があります。

  • 関数の循環的複雑度が低い場合。巨大なifステートメントと、そのifのelseステートメントが同時に画面に表示されていない関数を見る必要がある場合、仲間の開発者は少しイライラするかもしれません。

長い機能を使用しても問題ない場合:

  • 深くネストされた条件を持つ関数があります。仲間のコードリーダーに好意的で、関数を分割して読みやすさを向上させます。関数は、「これは1つのことを行うコードのブロックである」ということを読者に示します。また、関数の長さが多すぎて別のクラスに分解する必要があることを示しているかどうかも自問してください。

一番下の行は、保守性がリストの最優先事項の1つであるべきだということです。別の開発者がコードを見て、5秒以内にコードの実行内容の「要点」を取得できない場合、urコードは実行内容を伝えるのに十分な「メタデータ」を提供しません。他の開発者は、100行以上のコードを読む代わりに、選択したIDEのオブジェクトブラウザを見るだけで、クラスが何をしているかを知ることができます。

より小さな関数には、次の利点があります。

  • 移植性:機能を移動するのがはるかに簡単です(クラス内で別の機能にリファクタリングする場合)
  • デバッグ:スタックトレースを見ると、100行ではなく25行のコードで関数を見ていると、エラーを特定するのがはるかに速くなります。
  • 可読性-関数の名前は、コードブロック全体が何をしているのかを示します。チームの開発者は、そのブロックを使用していない場合、そのブロックを読みたくないかもしれません。さらに、ほとんどの最新のIDEの別の開発者は、オブジェクトブラウザーで関数名を読み取ることで、クラスの動作をよりよく理解できます。
  • ナビゲーション-ほとんどのIDEでは、関数の名前を検索できます。また、ほとんどの最新のIDEには、関数のソースを別のウィンドウで表示する機能があります。これにより、他の開発者はスクロールする代わりに、2つの画面(マルチモニターの場合)で長い関数を見ることができます。

リストが続きます.....


2

答えは依存しますが、おそらくそれをクラスに変えるべきです。


あなたがOOPLで書いていない限り、その場合は多分そうではありません:)
フランク・シェラー

dsimchaは、DとPythonを使用していると述べました。
frogstarr78

語彙の閉鎖のようなことを聞​​いたことがない人にとって、クラスはあまりにも松葉杖です。
カズ

2

私はほとんどのネストされた関数が好きではありません。ラムダはそのカテゴリに分類されますが、通常30〜40文字を超えない限りフラグを設定しません。

それは、内部セマンティック再帰性の高いローカルに密な関数となることを基本的な理由はある意味、私は周りに私の脳をラップすることは難しいということ、そしてそれがコード空間を乱雑にしないヘルパー関数にいくつかのものを押し出すだけで簡単です。

私は、関数がその機能を果たすべきだと考えています。他のことを行うことは、他の機能が行うことです。そのため、200行の関数Doing Its Thingがあり、すべてが流れる場合、それはA-OKです。


外部関数に非常に多くのコンテキストを渡さなければならないことがあり(ローカル関数として簡単にアクセスできる場合があります)、関数の実行方法よりも関数の呼び出し方が複雑になります。
カズ

1

受け入れられますか?それは本当にあなただけが答えることができる質問です。関数は必要なものを達成しますか?保守可能ですか?それはあなたのチームの他のメンバーにとって「受け入れられる」ものですか?もしそうなら、それは本当に重要なことです。

編集:ネストされた関数に関するものを見ませんでした。個人的には、私はそれらを使用しません。代わりに通常の関数を使用します。


1

長い「直線」関数は、特定のシーケンスで常に発生するステップの長いシーケンスを指定する明確な方法です。

ただし、他の人が述べたように、この形式は、全体的な流れがあまり明確ではない局所的な複雑さを持っている傾向があります。そのローカルな複雑さをネストされた関数(つまり、長い関数内のどこか、おそらく上部または下部で定義される)に配置すると、メインラインフローの明瞭さが復元されます。

2番目の重要な考慮事項は、長い関数のローカルストレッチでのみ使用することを目的とした変数のスコープを制御することです。この種の間違いはコンパイルエラーやランタイムエラーとして表示されないため、コードのあるセクションで導入された変数が他の場所で意図せずに参照されることを避けるために注意が必要です(たとえば、編集サイクルの後)。

一部の言語では、この問題は簡単に回避できます。コードのローカルストレッチは、「{...}」などの独自のブロックでラップできます。このブロック内では、新しく導入された変数はそのブロックにのみ表示されます。Pythonなどの一部の言語にはこの機能がありません。この場合、ローカル関数はより小さなスコープ領域を適用するのに役立ちます。


1

いいえ、複数ページの機能は望ましくないため、コードレビューに合格するべきではありません。 私も長い関数を書いていましたが、マーティン・ファウラーのリファクタリングを読んだ後、やめました。長い関数は、正しく記述したり、理解したり、テストしたりするのが困難です。50行の関数でさえ、小さな関数のセットに分割された場合に、より簡単に理解およびテストされない関数を見たことはありません。複数ページの関数では、ほぼ確実にクラス全体を除外する必要があります。もっと具体的にするのは難しいです。長い関数の1つをCode Reviewに投稿して、誰か(おそらく私)がそれを改善する方法を示すことができるかもしれません。


0

私がPythonでプログラミングしているとき、関数を書いた後に戻って「PythonのZen」に準拠しているかどうかを自問します(Pythonインタプリタに「import this」と入力します):

いよりも美しいほうがいい。
明示的は暗黙的よりも優れています。
単純なものは複雑なものよりも優れています。
複雑は複雑よりも優れています。
ネストはフラットよりも優れています。
スパースは、デンスよりも優れています。
読みやすさが重要です。
特別なケースは、規則を破るほど特別ではありません。
実用性は純度よりも優れていますが。
エラーが黙って渡されることはありません。
明示的に沈黙しない限り。
あいまいさに直面して、推測する誘惑を拒否します。
明白な方法が1つあり、できれば1つだけである必要があります。
オランダ人でない限り、その方法は最初は明らかではないかもしれませんが。
今は決してないよりはましです。
多くの場合、よりも良いことはありませんが今。
実装の説明が難しい場合、それは悪い考えです。
実装の説明が簡単な場合は、良いアイデアかもしれません。
名前空間は素晴らしいアイデアの1つです-それらをもっとやってみましょう!


1
明示的が暗黙的よりも優れている場合、機械語でコーディングする必要があります。アセンブラーでもありません。分岐遅延スロットを自動的に命令で埋め、相対的な分岐オフセットを計算し、グローバル間のシンボリック参照を解決するなど、厄介な暗黙のことを行います。男、これらの愚かなPythonユーザーと彼らの小さな宗教...
カズ

0

それらを別のモジュールに入れます。

ソリューションが必要以上に肥大化していないと仮定すると、選択肢が多すぎません。あなたはすでに異なるサブ関数に関数を分割しているので、問題はそれをどこに置くべきかです:

  1. それらを「パブリック」関数が1つだけあるモジュールに入れます。
  2. それらを「パブリック」(静的)関数が1つだけのクラスに入れます。
  3. 関数にネストします(説明したとおり)。

現在、2番目と3番目の両方の選択肢はある意味ネストされていますが、2番目の選択肢は一部のプログラマーにとっては悪くないと思われます。2番目の選択肢を除外しない場合、3番目の選択肢を除外する理由はあまりありません。

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