主要()应该有多大?

时间:2022-09-06 16:24:04

I'm learning a little C over the holiday weekend, and I started to look at other programs written in C. I ended up looking at GNU Netcat, thinking it would be a good example.

我在假期周末学习了一点C,我开始研究用C语言编写的其他程序。我最后看了一下GNU Netcat,认为这将是一个很好的例子。

I was a bit shocked to see a 600 line main() function. Is this normal? If it is normal is this considered good C coding practices?

看到600行main()函数我感到有些震惊。这是正常的吗?如果这是正常的,这被认为是良好的C编码实践吗?

12 个解决方案

#1


22  

There's a quote of an American President (Lincoln?) who was asked how long a man's legs should be. "Long enough to reach from his body to the ground," he said.

引用了一位美国总统(林肯?),他被问到男人的腿应该有多长。 “足够长,可以从他的身体到达地面,”他说。

Getting back on topic:

回到主题:

Authors of books like "Clean Code" advertise that every function do only one thing (that's grossly simplified by me here), so in theory your main() should maybe call an initialization function and then another function orchestrating the work of the application, and that's all.

像“清洁代码”这样的书的作者宣称每个函数只做一件事(我在这里大大简化了),所以理论上你的main()应该调用一个初始化函数,然后调用应用程序的工作的另一个函数,以及就这样。

In practice, many programmers find a lot of tiny functions irritating. A perhaps more useful metric is that a function should usually fit on one screen, if only to make it easier to see and think about.

在实践中,许多程序员发现许多微小的功能令人恼火。一个可能更有用的指标是函数通常应该放在一个屏幕上,如果只是为了让它更容易看到和思考。

If a program is complex and most of its functionality is in main(), someone hasn't done a decent job of breaking the problem down. Essentially you should strive for manageability, understandability and readability. There's usually no good reason for a main() to be huge.

如果一个程序很复杂,并且它的大部分功能都在main()中,那么有人就没有做好打破问题的工作。从本质上讲,您应该努力实现可管理性,可理解性和可读性。主()通常没有充分的理由是巨大的。

#2


7  

I often find on certain kinds of applications that main() has hundreds of lines of initialization followed by about 20 lines of top-level loop.

我经常在某些类型的应用程序中发现main()有数百行初始化,然后是大约20行*循环。

It's my habit not to break functions out until I need to call them twice. This sometimes leads to me writing a 300 line function, but as soon as I see the same block occur twice I break that block out.

在我需要打电话给他们两次之前,我的习惯是不要破坏功能。这有时会导致我编写一个300行函数,但只要我看到同一个块出现两次,我就会打破这个块。

As for main, initialization routines are often once so 600 lines does not sound unreasonable.

至于主要的,初始化例程通常是一次,因此600行听起来不合理。

#3


4  

Regardless of the language, I would try and restrict a subroutine method to roughly what's visible in one page of code, and extract functionality to subroutines wherever possible.

无论语言如何,我都会尝试将子程序方法限制在一页代码中大致可见的内容,并尽可能将功能提取到子程序中。

600 lines sounds quite long for any implementation. Perhaps there's some overriding reason wrt. passing arguments around and clarity (I've not looked at the example you've posted) but it sounds to be at the far end of what's normally practised, and it should be possible to subdivide this task.

对于任何实现,600行听起来都很长。也许有一些压倒一切的理由。传递参数和清晰度(我没有看过你发布的例子),但它听起来是通常实践的远端,应该可以细分这个任务。

I suspect it's been developed by continual incremental addition of functionality over the years, and nobody has stopped and refactored this to be more readable/maintainable. If there are no unit tests for this (and in my experience main() methods don't often get written tests - for whatever reasons) then there's going to be an understandable reluctance to refactor it.

我怀疑它是通过多年来不断增加功能而开发的,并且没有人停止并重构它以使其更具可读性/可维护性。如果没有针对此的单元测试(并且根据我的经验,main()方法通常不会进行书面测试 - 无论出于何种原因)那么可以理解为不可能重构它。

#4


4  

The limit is the editor window...

Heh, that's horrible but I've seen worse. I've seen large, multi-thousand line fortran programs with no subroutines at all.

嘿,那太可怕了,但我看到的情况更糟。我已经看到了大型的,数千行的Fortran程序,根本没有子程序。

I believe the answer is: it should fit in an editor window and it should have low cyclomatic complexity.

我相信答案是:它应该适合编辑器窗口,它应该具有较低的圈复杂度。

If a main program is just a series of function calls or computations, then I suppose it could be as long as necessary and it could have an exemption from the editor window constraint. Even then, I would be a bit surprised that there was not a natural way to extract meaningful discrete methods.

如果一个主程序只是一系列函数调用或计算,那么我想它可能只要有必要就可以免于编辑器窗口约束。即使这样,我也会感到有些惊讶,因为没有一种自然的方法来提取有意义的离散方法。

But if it is testing and branching and returning and breaking and continue-ing then it needs to be broken up into individual and individually tested functional components.

但是,如果它正在测试和分支并返回和断开并继续,则需要将其分解为单独且经过单独测试的功能组件。

#5


3  

Hopefully they are planning on refactoring. This looks very rough.

希望他们计划重构。这看起来很粗糙。

  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   }

#6


3  

A 600 line main is a bit of a warning sign. But if you look at it and can't see any way of breaking it up into smaller pieces other than to do this.

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();
   ...
}

Then you should leave it alone.

那你应该不管它。

#7


2  

By some standards a 600 line function of any sort is a bad idea, but there is no reason main should be treated any differently to any other function.

根据某种标准,任何类型的600线函数都是一个坏主意,但没有理由将main与任何其他函数区别对待。

The only reason I can think of such a situation arising is where a program is developed rapidly, and as it grows no one ever bothers to split it up into more logical units.

我能想到出现这种情况的唯一原因是一个程序快速发展,并且随着它的发展,没有人会把它分成更多的逻辑单元。

#8


1  

As short as possible. Usually, whenever there's an operation I can assign a name to, I create a new method for it.

尽可能短。通常,只要有操作我可以为其指定名称,我就为它创建一个新方法。

#9


1  

I would say your routines should be as long/short as necessary to be effective and reliably and automatically tested. A 600-statement routine likely has multiple paths through it and the combinations of routines might get very large very quickly. I try to break down functions into someting that make it easily readable. Functions are either "functional" or "narrative." All the while including unit tests.

我会说你的惯例应该是必要的长/短,以便有效,可靠和自动测试。 600语句例程可能有多条路径,并且例程的组合可能会非常快地变得非常大。我尝试将功能细分为易于阅读的功能。功能要么是“功能性的”,要么是“叙事性的”。一直包括单元测试。

#10


1  

My personal coding style is to try and only use the the main function for command-line argument parsing, and any big-ticket initialization that the program needs.

我的个人编码风格是尝试仅使用main函数进行命令行参数解析,以及程序需要的任何大票初始化。

#11


1  

Nearly all 600-line functions I have seen were also stupidly written. This doesn't have to be so.

我见过的几乎所有600行函数也都是愚蠢的。这不一定是这样。

However, in these cases it was just a failure of a programmer to present some zoomed-out view, and give meaningful names to sections - both high-level (like, Initialize()) and low-level (something that takes a common 3-line pattern and hides it under one name, with parameters).

但是,在这些情况下,只是程序员无法呈现一些缩小的视图,并为部分提供有意义的名称 - 高级(如,Initialize())和低级(需要共同的3) -line pattern并将其隐藏在一个名称下,带参数)。

In cases of extreme stupidity, they were optimizing function call performance when it was not required.

在极端愚蠢的情况下,他们在不需要时优化函数调用性能。

#12


0  

main(), like any function, should be exactly as big as it needs to be. "As it needs to be" will vary a lot depending on what it needs to do. Having said that, it shouldn't have to be more than a couple of hundred lines in most cases. 600 lines is a bit on the hefty side, and some of that could / should probably be refactored into separate functions.

像任何函数一样,main()应该与它需要的一样大。 “因为它需要”将根据它需要做的事情而变化很大。话虽如此,在大多数情况下,它不应该超过几百行。 600行有点偏重,其中一些可能/应该被重构为单独的函数。

For an extreme example, one team I was on was tasked with speeding up some code to drive a 3d display. The code was originally written by a wirehead who was obviously taught himself programming using old-school FORTRAN; main() was over five thousand lines of code, with random bits #includeed here and there. Instead of breaking code out into functions, he'd simply branch to a subroutine within main() via goto (somewhere between 13 and 15 gotos, branching both directions seemingly at random). As a first step we simply turned on level 1 optimization; the compiler promptly swallowed up all available memory and swap space and panicked the kernel. The code so brittle that we couldn't make any changes without breaking something. We finally told the customer they had two choices: allow us to rewrite the entire system from scratch or buy faster hardware.

举一个极端的例子,我所在的一个团队的任务是加速一些代码来驱动3D显示器。该代码最初是由一个使用老式FORTRAN自学编程的线头编写的; main()超过五千行代码,其中随机位#includeed。他不是将代码分解为函数,而是简单地通过goto(在13到15个gotos之间,在看似随机分支两个方向的分支)中分支到main()中的子例程。作为第一步,我们简单地启用了1级优化;编译器迅速吞下所有可用的内存和交换空间并恐慌内核。代码如此脆弱,以至于我们无法做出任何改变而不会破坏某些东西。我们最终告诉客户他们有两个选择:允许我们从头开始重写整个系统或购买更快的硬件。

They bought faster hardware.

他们买了更快的硬件。

#1


22  

There's a quote of an American President (Lincoln?) who was asked how long a man's legs should be. "Long enough to reach from his body to the ground," he said.

引用了一位美国总统(林肯?),他被问到男人的腿应该有多长。 “足够长,可以从他的身体到达地面,”他说。

Getting back on topic:

回到主题:

Authors of books like "Clean Code" advertise that every function do only one thing (that's grossly simplified by me here), so in theory your main() should maybe call an initialization function and then another function orchestrating the work of the application, and that's all.

像“清洁代码”这样的书的作者宣称每个函数只做一件事(我在这里大大简化了),所以理论上你的main()应该调用一个初始化函数,然后调用应用程序的工作的另一个函数,以及就这样。

In practice, many programmers find a lot of tiny functions irritating. A perhaps more useful metric is that a function should usually fit on one screen, if only to make it easier to see and think about.

在实践中,许多程序员发现许多微小的功能令人恼火。一个可能更有用的指标是函数通常应该放在一个屏幕上,如果只是为了让它更容易看到和思考。

If a program is complex and most of its functionality is in main(), someone hasn't done a decent job of breaking the problem down. Essentially you should strive for manageability, understandability and readability. There's usually no good reason for a main() to be huge.

如果一个程序很复杂,并且它的大部分功能都在main()中,那么有人就没有做好打破问题的工作。从本质上讲,您应该努力实现可管理性,可理解性和可读性。主()通常没有充分的理由是巨大的。

#2


7  

I often find on certain kinds of applications that main() has hundreds of lines of initialization followed by about 20 lines of top-level loop.

我经常在某些类型的应用程序中发现main()有数百行初始化,然后是大约20行*循环。

It's my habit not to break functions out until I need to call them twice. This sometimes leads to me writing a 300 line function, but as soon as I see the same block occur twice I break that block out.

在我需要打电话给他们两次之前,我的习惯是不要破坏功能。这有时会导致我编写一个300行函数,但只要我看到同一个块出现两次,我就会打破这个块。

As for main, initialization routines are often once so 600 lines does not sound unreasonable.

至于主要的,初始化例程通常是一次,因此600行听起来不合理。

#3


4  

Regardless of the language, I would try and restrict a subroutine method to roughly what's visible in one page of code, and extract functionality to subroutines wherever possible.

无论语言如何,我都会尝试将子程序方法限制在一页代码中大致可见的内容,并尽可能将功能提取到子程序中。

600 lines sounds quite long for any implementation. Perhaps there's some overriding reason wrt. passing arguments around and clarity (I've not looked at the example you've posted) but it sounds to be at the far end of what's normally practised, and it should be possible to subdivide this task.

对于任何实现,600行听起来都很长。也许有一些压倒一切的理由。传递参数和清晰度(我没有看过你发布的例子),但它听起来是通常实践的远端,应该可以细分这个任务。

I suspect it's been developed by continual incremental addition of functionality over the years, and nobody has stopped and refactored this to be more readable/maintainable. If there are no unit tests for this (and in my experience main() methods don't often get written tests - for whatever reasons) then there's going to be an understandable reluctance to refactor it.

我怀疑它是通过多年来不断增加功能而开发的,并且没有人停止并重构它以使其更具可读性/可维护性。如果没有针对此的单元测试(并且根据我的经验,main()方法通常不会进行书面测试 - 无论出于何种原因)那么可以理解为不可能重构它。

#4


4  

The limit is the editor window...

Heh, that's horrible but I've seen worse. I've seen large, multi-thousand line fortran programs with no subroutines at all.

嘿,那太可怕了,但我看到的情况更糟。我已经看到了大型的,数千行的Fortran程序,根本没有子程序。

I believe the answer is: it should fit in an editor window and it should have low cyclomatic complexity.

我相信答案是:它应该适合编辑器窗口,它应该具有较低的圈复杂度。

If a main program is just a series of function calls or computations, then I suppose it could be as long as necessary and it could have an exemption from the editor window constraint. Even then, I would be a bit surprised that there was not a natural way to extract meaningful discrete methods.

如果一个主程序只是一系列函数调用或计算,那么我想它可能只要有必要就可以免于编辑器窗口约束。即使这样,我也会感到有些惊讶,因为没有一种自然的方法来提取有意义的离散方法。

But if it is testing and branching and returning and breaking and continue-ing then it needs to be broken up into individual and individually tested functional components.

但是,如果它正在测试和分支并返回和断开并继续,则需要将其分解为单独且经过单独测试的功能组件。

#5


3  

Hopefully they are planning on refactoring. This looks very rough.

希望他们计划重构。这看起来很粗糙。

  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   }

#6


3  

A 600 line main is a bit of a warning sign. But if you look at it and can't see any way of breaking it up into smaller pieces other than to do this.

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();
   ...
}

Then you should leave it alone.

那你应该不管它。

#7


2  

By some standards a 600 line function of any sort is a bad idea, but there is no reason main should be treated any differently to any other function.

根据某种标准,任何类型的600线函数都是一个坏主意,但没有理由将main与任何其他函数区别对待。

The only reason I can think of such a situation arising is where a program is developed rapidly, and as it grows no one ever bothers to split it up into more logical units.

我能想到出现这种情况的唯一原因是一个程序快速发展,并且随着它的发展,没有人会把它分成更多的逻辑单元。

#8


1  

As short as possible. Usually, whenever there's an operation I can assign a name to, I create a new method for it.

尽可能短。通常,只要有操作我可以为其指定名称,我就为它创建一个新方法。

#9


1  

I would say your routines should be as long/short as necessary to be effective and reliably and automatically tested. A 600-statement routine likely has multiple paths through it and the combinations of routines might get very large very quickly. I try to break down functions into someting that make it easily readable. Functions are either "functional" or "narrative." All the while including unit tests.

我会说你的惯例应该是必要的长/短,以便有效,可靠和自动测试。 600语句例程可能有多条路径,并且例程的组合可能会非常快地变得非常大。我尝试将功能细分为易于阅读的功能。功能要么是“功能性的”,要么是“叙事性的”。一直包括单元测试。

#10


1  

My personal coding style is to try and only use the the main function for command-line argument parsing, and any big-ticket initialization that the program needs.

我的个人编码风格是尝试仅使用main函数进行命令行参数解析,以及程序需要的任何大票初始化。

#11


1  

Nearly all 600-line functions I have seen were also stupidly written. This doesn't have to be so.

我见过的几乎所有600行函数也都是愚蠢的。这不一定是这样。

However, in these cases it was just a failure of a programmer to present some zoomed-out view, and give meaningful names to sections - both high-level (like, Initialize()) and low-level (something that takes a common 3-line pattern and hides it under one name, with parameters).

但是,在这些情况下,只是程序员无法呈现一些缩小的视图,并为部分提供有意义的名称 - 高级(如,Initialize())和低级(需要共同的3) -line pattern并将其隐藏在一个名称下,带参数)。

In cases of extreme stupidity, they were optimizing function call performance when it was not required.

在极端愚蠢的情况下,他们在不需要时优化函数调用性能。

#12


0  

main(), like any function, should be exactly as big as it needs to be. "As it needs to be" will vary a lot depending on what it needs to do. Having said that, it shouldn't have to be more than a couple of hundred lines in most cases. 600 lines is a bit on the hefty side, and some of that could / should probably be refactored into separate functions.

像任何函数一样,main()应该与它需要的一样大。 “因为它需要”将根据它需要做的事情而变化很大。话虽如此,在大多数情况下,它不应该超过几百行。 600行有点偏重,其中一些可能/应该被重构为单独的函数。

For an extreme example, one team I was on was tasked with speeding up some code to drive a 3d display. The code was originally written by a wirehead who was obviously taught himself programming using old-school FORTRAN; main() was over five thousand lines of code, with random bits #includeed here and there. Instead of breaking code out into functions, he'd simply branch to a subroutine within main() via goto (somewhere between 13 and 15 gotos, branching both directions seemingly at random). As a first step we simply turned on level 1 optimization; the compiler promptly swallowed up all available memory and swap space and panicked the kernel. The code so brittle that we couldn't make any changes without breaking something. We finally told the customer they had two choices: allow us to rewrite the entire system from scratch or buy faster hardware.

举一个极端的例子,我所在的一个团队的任务是加速一些代码来驱动3D显示器。该代码最初是由一个使用老式FORTRAN自学编程的线头编写的; main()超过五千行代码,其中随机位#includeed。他不是将代码分解为函数,而是简单地通过goto(在13到15个gotos之间,在看似随机分支两个方向的分支)中分支到main()中的子例程。作为第一步,我们简单地启用了1级优化;编译器迅速吞下所有可用的内存和交换空间并恐慌内核。代码如此脆弱,以至于我们无法做出任何改变而不会破坏某些东西。我们最终告诉客户他们有两个选择:允许我们从头开始重写整个系统或购买更快的硬件。

They bought faster hardware.

他们买了更快的硬件。