8

私は非常に長い関数に関する小さな学術研究プロジェクトを書いています。明らかに、私は悪いプログラミングの例を探しているのではなく、100、200、および 600 行の長さの関数の例を探しています。

ヘブライ大学で修士号取得のために書かれたスクリプトを使用して、Linux カーネル ソースを調査します。このスクリプトは、コードの行数、関数の複雑さ(MCC で測定) などのさまざまなパラメーターを測定します。ところで、コード解析についてのきちんとした研究であり、推奨される読み物です。

関数が非常に長くなければならない正当な理由を考えることができるかどうか興味がありますか? 私は C を調べますが、どの言語からの例や引数も非常に役に立ちます。

4

15 に答える 15

14

switch ステートメントに多くの値がありますか?

于 2009-07-14T19:54:24.133 に答える
11

他のソースから生成されたもの、つまりパーサジェネレータなどの有限状態マシン。人間が消費することを意図していない場合、美的または保守性の懸念は関係ありません。

于 2009-07-14T20:08:04.370 に答える
4

関数は、特に多くの開発者によって変更された場合、時間の経過とともに長くなる可能性があります。

適切な例:私は最近(約1年または2年前)、2001年頃から数千行の関数を含むレガシー画像処理コードをリファクタリングしました。数千行のファイル、数千行の関数も少なくありません。

何年にもわたって、それらを適切にリファクタリングするための努力を実際に行うことなく、非常に多くの機能がそれらに追加されました。

于 2009-07-14T20:06:21.457 に答える
3

サブルーチンに関するMcConnell's Code Completeの章を読んでください。それには、関数に分割する必要がある場合のガイドラインとポインターがあります。これらのルールが適用されないアルゴリズムがある場合、それが長い関数を持つ正当な理由になる可能性があります。

于 2009-07-14T19:56:44.393 に答える
2

生成されたコードは、非常に長い関数を生成できます。

于 2009-07-14T20:10:23.100 に答える
2

私が最近コーディングしたのは、サイズを小さくしてもあまり効果がないか、コードが読みにくくなる可能性があるものだけです。一定の長さを超える関数は何らかの形で本質的に悪いという考えは、単に盲目的な定説です。やみくもに適用されたドグマのように、信者は、特定の場合に何が適用されるかを実際に考える必要がなくなります...

最近の例...

シンプルな name=value 構造を持つ構成ファイルを解析して検証し、各値を見つけたときに変換します。これは、構成オプションごとに 1 つのケースである 1 つの大規模な switch ステートメントです。なんで?5/6 行の些細な関数への多数の呼び出しに分割できたはずです。これにより、クラスに約 20 のプライベート メンバーが追加されます。それらのどれも他の場所で再利用されていません。それをより小さなチャンクに分解しても、それだけの価値があるほどの価値が追加されなかったため、プロトタイプ以来ずっと同じです. 別のオプションが必要な場合は、別のケースを追加してください。

もう 1 つのケースは、同じアプリ内のクライアントとサーバーの通信コード、およびそのクライアントです。読み取り/書き込みの多くの呼び出しが失敗する可能性があります。その場合、私は保釈して false を返します。したがって、この関数は基本的に線形であり、ほぼすべての呼び出しの後に保釈ポイント (失敗した場合は戻る) があります。繰り返しますが、小さくしても何も得られず、実際に小さくする方法もありません。

また、私の関数のほとんどはいくつかの「スクリーンフル」であり、関数全体を一度に見ることができるという理由だけで、より複雑な領域ではそれを 1 つの「スクリーンフル」に保つよう努めていることも付け加えておく必要があります。基本的に直線的で、複雑なループや条件があまりない関数の場合は問題ないため、フローは単純です。最後に、どのコードをリファクタリングするかを決定する際には、費用対効果の理由を適用し、それに応じて優先順位を付けることを好みます。永久に中途半端なプロジェクトを回避するのに役立ちます。

于 2009-07-14T21:11:46.067 に答える
1

スピード:

  • 関数を呼び出すとは、スタックにプッシュしてからジャンプし、スタックに再度格納してから、もう一度ジャンプすることを意味します。関数にパラメーターを使用する場合、通常はさらにいくつかのプッシュがあります。

ループについて考えてみましょう。

for...
   func1

ループ内では、これらすべてのプッシュとジャンプが要因になる可能性があります。

これは、C99でのインライン関数の表示とその前の非公式で大部分が解決されましたが、以前作成された、または互換性を念頭に置いて作成されたコードの中には、その理由で長いものがあった可能性があります。

また、インラインにはフローがあり、一部はインライン関数リンクで説明されています。

編集:

関数の呼び出しによってプログラムが遅くなる例として、次のようにします。

4         static void
5 do_printf()
6 {
7         printf("hi");
8 }
9         int
10 main()
11 {
12         int i=0;
13         for(i=0;i<1000;++i)
14                 do_printf();
15 }

これにより、(GCC 4.2.4)が生成されます。

 .
 . 
 jmp    .L4
 .L5:
call    do_printf
addl    $1, -8(%ebp)
 .L4:
cmpl    $999, -8(%ebp)
jle .L5

 .
 .
do_printf:
pushl   %ebp
movl    %esp, %ebp
subl    $8, %esp
movl    $.LC0, (%esp)
call    printf
leave
ret

に対して:

         int
 main()
 {
         int i=0;
         for(i=0;i<1000;++i)
                 printf("hi");
 }

または反対:

 4         static inline void __attribute__((always_inline)) //This is GCC specific!
 5 do_printf()
 6 {
 7         printf("hi");
 8 }

両方とも生成します(GCC 4.2.4):

jmp .L2
.L3:
movl    $.LC0, (%esp)
call    printf
addl    $1, -8(%ebp)
.L2:
cmpl    $999, -8(%ebp)
jle .L3

どちらが速いですか。

于 2009-07-15T20:38:28.180 に答える
1

私が遭遇する非常に長い関数は C で書かれていないので、これがあなたの研究に当てはまるかどうかを判断する必要があります。私が念頭に置いているのは、次の理由により、数百行の長さのいくつかの PowerBuilder 関数です。

  • それらは 10 年以上前に、当時はコーディング標準を念頭に置いていなかった人々によって書かれました。
  • 開発環境では、関数を作成するのが少し難しくなります。良い言い訳にはなりませんが、これは、適切に作業することを思いとどまらせる小さなことの 1 つです。
  • 関数は時間の経過とともに進化し、コードと複雑さの両方が追加されました。
  • 関数には巨大なループが含まれており、反復ごとに異なる種類のデータを異なる方法で処理する可能性があります。数十個のローカル変数、いくつかのメンバー変数、およびいくつかのグローバル変数を使用すると、それらは非常に複雑になります。
  • 古くて醜いので、あえて小さな部分にリファクタリングする人はいません。非常に多くの特殊なケースが処理されているため、それらを分解すると問題が発生します。

これは、明らかに悪いプログラミング プラクティスが現実に遭遇するもう 1 つの場所です。CS の 1 年生なら誰でも、獣は悪いと言うかもしれませんが、見た目を良くするためにお金を使う人は誰もいないでしょう (少なくとも今のところ、彼らはまだ成果を上げていることを考えると)。

于 2009-07-14T20:39:10.593 に答える
1

私が関係があると思う点の 1 つは、異なる言語やツールでは、関数に関連付けられた語彙スコープが異なるということです。

たとえば、Java では、注釈を使用して警告を抑制することができます。注釈の範囲を制限することが望ましい場合があるため、その目的のために関数を短くします。別の言語では、そのセクションを独自の関数に分割することは完全に恣意的かもしれません。

物議を醸す: JavaScript では、コードを再利用する目的でのみ関数を作成する傾向があります。スニペットが 1 か所でしか実行されない場合、関数参照のスパゲッティに従ってファイルをジャンプするのは面倒です。クロージャーは、より長い [親] 機能を促進し、強化すると思います。JS は解釈された言語であり、実際のコードはネットワーク上で送信されるため、コードの長さを短く保つことは良いことです。一致する宣言と参照を作成しても役に立ちません (これは時期尚早の最適化と見なされる可能性があります)。「関数を短く保つ」という明確な目的のために切り刻むことを決定する前に、関数はJSでかなり長くなる必要があります。

JS では、「クラス」全体が技術的に多くのサブ関数を含む関数である場合がありますが、それを処理するためのツールがあります。

一方、JS では、変数には関数の長さの範囲があるため、これが特定の関数の長さを制限する要因になります。

于 2009-07-14T20:15:31.797 に答える
0

XML解析コードには、多くの場合、1つのセットアップ関数で一連のエスケープ文字処理が含まれています。

于 2009-07-14T20:06:29.850 に答える
0

私が扱う(書いていない)関数は、拡張されて拡張され、関数のリファクタリングに時間を費やす人がいないため、長くなります。全体像を考えずに、関数にロジックを追加し続けるだけです。

カットアンドペーストの開発をたくさん扱っています...

したがって、論文の場合、注目すべき側面の 1 つは、メンテナンス計画/サイクルの悪さなどです。

于 2009-07-14T20:39:25.150 に答える
0

まだ明示的に言及されていないいくつかのアイデア:

  • 反復タスク。たとえば、関数は 190 列のデータベース テーブルを読み取り、それらをフラット ファイルとして出力する必要があります (列を個別に処理する必要があると仮定すると、すべての列に対する単純なループは実行されません)。もちろん、それぞれが 10 列を出力する 19 個の関数を作成することもできますが、それではプログラムが改善されることはありません。
  • Oracle の OCI のような複雑で冗長な API 。一見単純なアクションに大量のコードが必要な場合、意味のある小さな関数に分解するのは困難です。
于 2009-07-15T06:48:15.860 に答える