12

私は休日の週末に少しCを学び、Cで書かれた他のプログラムを見始めました。それが良い例になると思い、GNUNetcatを見ることになりました。

600行のmain()機能を見て少しショックを受けました。これは正常ですか?それが正常である場合、これは良いCコーディング慣行と見なされますか?

4

12 に答える 12

23

男性の足の長さを尋ねられたアメリカの大統領(リンカーン?)の引用があります。「彼の体から地面に達するのに十分な長さ」と彼は言った。

トピックに戻る:

「CleanCode」のような本の著者は、すべての関数が1つのことだけを実行することを宣伝しています(ここでは私が大幅に簡略化しています)。したがって、理論的にmain()は、初期化関数を呼び出してから、アプリケーションの作業を調整する別の関数を呼び出す必要があります。

実際には、多くのプログラマーは多くの小さな機能を苛立たせていると感じています。おそらくより有用なメトリックは、関数が見やすく、考えやすくするためだけに、通常は1つの画面に収まる必要があるということです。

プログラムが複雑で、その機能のほとんどがmain()組み込まれている場合、誰かが問題を解決するための適切な仕事をしていません。基本的に、管理性、理解性、読みやすさを追求する必要があります。通常、main()が巨大になる理由はありません。

于 2009-12-28T00:10:28.220 に答える
7

特定の種類のアプリケーションでは、main()に数百行の初期化があり、その後に約20行のトップレベルループが続くことがよくあります。

関数を2回呼び出す必要があるまで、関数を分割しないのが私の習慣です。これにより、300行の関数を作成する場合がありますが、同じブロックが2回発生するのを確認するとすぐに、そのブロックを分割します。

mainに関しては、初期化ルーチンは1回であることが多いため、600行が不合理に聞こえることはありません。

于 2009-12-28T00:32:52.470 に答える
4

言語に関係なく、サブルーチンメソッドをコードの1ページに表示されるものに大まかに制限し、可能な限りサブルーチンに機能を抽出しようとします。

600行は、どの実装でもかなり長く聞こえます。おそらく、いくつかの最も重要な理由があります。議論と明快さを渡す(私はあなたが投稿した例を見ていません)が、それは通常行われていることの遠端にあるように聞こえます、そしてこのタスクを細分化することは可能であるはずです。

私はそれが何年にもわたって機能の継続的な漸進的な追加によって開発されたと思います、そして誰もこれを止めて、より読みやすく/維持しやすいようにリファクタリングしました。このための単体テストがない場合(そして私の経験では、main()何らかの理由でメソッドが記述されたテストを取得することはあまりありません)、それをリファクタリングすることには理解できる抵抗があります。

于 2009-12-28T00:09:31.933 に答える
4

制限はエディタウィンドウです...

ええ、それはひどいですが、私はもっと悪く見えました。サブルーチンがまったくない、大規模な数千行のFortranプログラムを見てきました。

答えは次のとおりです。エディターウィンドウに収まり、循環的複雑度が低くなければなりません。

メインプログラムが単なる一連の関数呼び出しまたは計算である場合、それは必要なだけ長くなる可能性があり、エディターウィンドウの制約から免除される可能性があると思います。それでも、意味のある個別のメソッドを抽出する自然な方法がなかったことに少し驚かされます。

ただし、テストと分岐、およびreturningとbreakingとcontinue-ingの場合は、個別にテストされた機能コンポーネントに分割する必要があります。

于 2009-12-28T06:18:41.187 に答える
3

うまくいけば、彼らはリファクタリングを計画しています。これは非常にラフに見えます。

  443   while (optind < argc) {
  444     const char *get_argv = argv[optind++];
  445     char *q, *parse = strdup(get_argv);
  446     int port_lo = 0, port_hi = 65535;
  447     nc_port_t port_tmp;
  448 
  449     if (!(q = strchr(parse, '-')))    /* simple number? */
  450       q = strchr(parse, ':');     /* try with the other separator */
  451 
  452     if (!q) {
  453       if (netcat_getport(&port_tmp, parse, 0))
  454   netcat_ports_insert(old_flag, port_tmp.num, port_tmp.num);
  455       else
  456   goto got_err;
  457     }
  458     else {        /* could be in the forms: N1-N2, -N2, N1- */
  459       *q++ = 0;
  460       if (*parse) {
  461   if (netcat_getport(&port_tmp, parse, 0))
  462     port_lo = port_tmp.num;
  463   else
  464     goto got_err;
  465       }
  466       if (*q) {
  467   if (netcat_getport(&port_tmp, q, 0))
  468     port_hi = port_tmp.num;
  469   else
  470     goto got_err;
  471       }
  472       if (!*parse && !*q)     /* don't accept the form '-' */
  473   goto got_err;
  474 
  475       netcat_ports_insert(old_flag, port_lo, port_hi);
  476     }
  477 
  478     free(parse);
  479     continue;
  480 
  481  got_err:
  482     free(parse);
  483     ncprint(NCPRINT_ERROR, _("Invalid port specification: %s"), get_argv);
  484     exit(EXIT_FAILURE);
  485   }
于 2009-12-28T00:24:32.403 に答える
3

600ラインのメインはちょっとした警告サインです。しかし、あなたがそれを見て、これを行う以外にそれをより小さな断片に分割する方法を見ることができない場合。

void the_first_part_of_main(args...);
void the_second_part_of_main(args...);
...

main()
{
   the_first_part_of_main();
   the_second_part_of_main();
   ...
}

次に、それを放っておく必要があります。

于 2009-12-28T00:45:09.880 に答える
2

一部の標準では、600行の関数を使用することは悪い考えですが、mainを他の関数とは異なる方法で処理する必要がある理由はありません。

このような状況が発生していると私が考えることができる唯一の理由は、プログラムが急速に開発され、それが成長するにつれて、誰もそれをより論理的な単位に分割することを気にしないということです。

于 2009-12-28T00:11:42.197 に答える
1

できるだけ短くします。通常、名前を割り当てることができる操作があるときはいつでも、そのための新しいメソッドを作成します。

于 2009-12-28T00:12:18.170 に答える
1

私はあなたのルーチンが効果的で確実にそして自動的にテストされるために必要なだけ長く/短くあるべきだと思います。600ステートメントのルーチンには複数のパスが含まれている可能性があり、ルーチンの組み合わせは非常に急速に大きくなる可能性があります。関数を読みやすいものに分解しようとしています。機能は「機能的」または「物語的」のいずれかです。ユニットテストを含めてずっと。

于 2009-12-28T00:47:21.510 に答える
1

私の個人的なコーディングスタイルは、コマンドライン引数の解析と、プログラムに必要な高額な初期化のためにmain関数のみを使用することです。

于 2009-12-28T01:05:53.287 に答える
1

私が見た600行の関数のほぼすべてもばかげて書かれていました。これはそうである必要はありません。

ただし、これらの場合、プログラマーがズームアウトしたビューを表示し、セクションに意味のある名前を付けるのは失敗でした-高レベル(Initialize()など)と低レベル(一般的な3をとる何か)の両方-線のパターンと、パラメータを使用して1つの名前で非表示にします)。

極端な愚かさの場合、彼らはそれが必要とされていないときに関数呼び出しのパフォーマンスを最適化していました。

于 2009-12-28T12:34:03.167 に答える
0

main()、他の関数と同様に、必要なだけ大きくする必要があります。「必要に応じて」は、何をする必要があるかによって大きく異なります。そうは言っても、ほとんどの場合、数百行を超える必要はありません。600行は少し多めですが、その一部は別の関数にリファクタリングできる/おそらくリファクタリングする必要があります。

極端な例として、私が所属していた1つのチームは、3Dディスプレイを駆動するためのコードを高速化するという任務を負っていました。このコードは元々、昔ながらのFORTRANを使用してプログラミングを学んだワイヤーヘッドによって書かれました。5,000main()行を超えるコードがあり、ランダムなビットがあちこちで編集されていました。コードを関数に分割する代わりに、彼は単にvia内のサブルーチンに分岐します(13から15のgotoの間のどこかで、一見ランダムに両方向に分岐します)。最初のステップとして、レベル1の最適化をオンにしました。コンパイラはすぐに利用可能なすべてのメモリとスワップスペースを飲み込み、カーネルをパニックにしました。コードが非常に壊れやすいため、作成できませんでし#includemain()goto何かを壊すことなく変化します。最後に、2つの選択肢があることをお客様に伝えました。システム全体を最初から書き直すか、より高速なハードウェアを購入するかです。

彼らはより速いハードウェアを購入しました。

于 2009-12-28T14:22:12.207 に答える