我在假期周末学习了一点C,我开始研究用C编写的其他程序。我最后看了GNU Netcat,认为这将是一个很好的例子。
看到600 line main()
function我感到有点震惊。这是正常的吗?如果这是正常的,这被认为是良好的C编码实践吗?
答案 0 :(得分:22)
引用了一位美国总统(林肯?),他被问到一个男人的腿应该多久。 “足够长,可以从他的身体到达地面,”他说。
回到主题:
像“清洁代码”这样的书籍的作者宣称每个函数只做一件事(我在这里大大简化了),所以理论上你的main()
应该调用初始化函数然后另一个编写工作的函数应用程序,这就是全部。
在实践中,许多程序员发现许多微小的功能令人恼火。一个可能更有用的指标是,一个函数通常应该放在一个屏幕上,如果只是为了让它更容易看到和思考。
如果一个程序很复杂并且其大多数功能都在main()
中,那么有人就没有做好打破问题的工作。从本质上讲,您应该努力实现可管理性,可理解性和可读性。通常没有充分的理由让main()变得庞大。
答案 1 :(得分:7)
我常常在某些类型的应用程序中发现main()有数百行初始化,然后是大约20行顶级循环。
我的习惯是在我需要两次打电话之前不要打破功能。这有时会导致我编写一个300行函数,但只要我看到同一个块出现两次,我就会打破这个块。
至于main,初始化例程通常是一次,因此600行听起来不合理。
答案 2 :(得分:4)
无论语言如何,我都会尝试将子程序方法限制在一页代码中大致可见的内容,并尽可能将功能提取到子程序中。
对于任何实现,600行听起来都很长。也许有一些压倒一切的理由。传递参数和清晰度(我没有看过你发布的例子)但听起来似乎是通常实践的远端,应该可以细分这个任务。
我怀疑它是多年来通过不断增加功能而开发的,并且没有人停止并重构它以使其更具可读性/可维护性。如果没有针对此的单元测试(并且根据我的经验main()
方法通常不会进行书面测试 - 无论出于何种原因)那么可以理解为不可能重构它。
答案 3 :(得分:4)
我相信答案是:它应该适合编辑器窗口,它应该低cyclomatic complexity。
如果一个主程序只是一系列函数调用或计算,那么我想它可能只要有必要就可以免除编辑器窗口约束。即使这样,我也会感到有些惊讶,因为没有一种自然的方法来提取有意义的离散方法。
但如果它正在测试和分支以及return
和break
和continue
- 那么它需要被分解为单独和单独测试的功能组件。
答案 4 :(得分: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 }
答案 5 :(得分: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();
...
}
然后你应该不管它。
答案 6 :(得分:2)
根据某些标准,任何类型的600行函数都是一个坏主意,但没有理由将main与任何其他函数区别对待。
我能想到出现这种情况的唯一原因是程序快速发展,随着它的发展,没有人会把它分成更合理的单位。
答案 7 :(得分:1)
尽可能短。通常,只要有操作我可以为其指定名称,我就为它创建一个新方法。
答案 8 :(得分:1)
我会说你的惯例应该是必要的长/短,以便有效,可靠和自动测试。 600语句例程可能有多条路径,并且例程的组合可能会非常快地变得非常大。我尝试将功能细分为易于阅读的功能。功能是“功能性”或“叙述性”。一直包括单元测试。
答案 9 :(得分:1)
我的个人编码风格是尝试仅使用main函数进行命令行参数解析,以及程序需要的任何大票初始化。
答案 10 :(得分:1)
我见过的几乎所有600行函数也都是愚蠢的。这不一定是这样。
然而,在这些情况下,只是程序员无法呈现一些缩小视图,并为部分提供有意义的名称 - 高级(如,Initialize())和低级别(采用常见的3行模式并将其隐藏在一个名称下,带有参数)。
在极端愚蠢的情况下,他们在不需要时优化函数调用性能。
答案 11 :(得分:0)
main()
应该与它需要的一样大。 “因为它需要”将根据它需要做的事情而变化很大。话虽如此,在大多数情况下, 不应超过几百行。 600行有点偏重,其中一些可能/应该被重构为单独的函数。
对于一个极端的例子,我所在的一个团队的任务是加速一些代码来驱动3D显示。该代码最初是由一个使用老式FORTRAN自学编程的线头编写的; main()
超过五行千行代码,其中随机位#include
被编辑。他不是将代码分解为函数,而是简单地通过main()
分支到goto
内的子程序(13到15之间的某个地方,似乎随机地分支两个方向)。作为第一步,我们简单地启用了1级优化;编译器迅速吞下所有可用的内存和交换空间并恐慌内核。代码非常脆弱,我们无法在不破坏某些内容的情况下进行任何更改。我们最终告诉客户他们有两个选择:允许我们从头开始重写整个系统或购买更快的硬件。
他们买了更快的硬件。