什么时候function太长?

35线,55线,100线,300线? 当你应该开始分裂? 我问,因为我有一个60行(包括评论)的function,正在考虑打破它。

long_function(){ ... } 

成:

 small_function_1(){...} small_function_2(){...} small_function_3(){...} 

这些函数不会在long_function之外使用,使得更小的函数意味着更多的函数调用等等。

你什么时候将一个function拆分成更小的? 为什么?

  1. 方法应该只做一件合乎逻辑的事情(考虑function)
  2. 你应该能够用一句话来解释这个方法
  3. 它应该适合你的显示器的高度
  4. 避免不必要的开销(评论指出明显…)
  5. 对于小逻辑函数,unit testing更容易
  6. 检查一部分函数是否可以被其他类或方法重用
  7. 避免过度的类间耦合
  8. 避免深度嵌套的控制结构

感谢大家的答案 ,编辑列表并投票正确的答案,我会select一个;)

我正在重新考虑这些想法:)

没有真正的硬性规定。 一般来说,我喜欢我的方法只是“做一件事”。 所以如果是抓取数据,然后用这些数据做一些事情,然后把它写到磁盘上,那么我就把抓取和写入单独的方法,所以我的“主要”方法只包含“做某事”。

虽然“做某事”仍然可以是很多行,所以我不确定一些行是正确的使用指标:)

编辑:这是我上周围绕工作邮寄的一行代码(为了certificate这一点..这不是我养成的习惯:)) – 我当然不会希望在我的方法中有50-60个这些坏男孩:d

 return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList(); 

下面是一个红旗列表(没有特定的顺序),可能表明一个函数太长:

  1. 深度嵌套的控制结构 :例如for循环深度3甚至深度2级嵌套的if语句具有复杂的条件。

  2. 太多的状态定义参数 :通过状态定义参数 ,我的意思是一个函数参数,保证通过函数的特定执行path。 获取太多的这些types的参数,并且你有执行path的组合爆炸(这通常与#1一起发生)。

  3. 在其他方法中重复的逻辑 :糟糕的代码重用是单片程序代码的巨大贡献者。 许多这样的逻辑复制可能是非常微妙的,但是一旦重新考虑,最终的结果可以是更加优雅的devise。

  4. 过度的类间耦合 :这种缺乏适当的封装导致函数关注其他类的亲密特性,因此延长了它们。

  5. 不必要的开销 :评论指出了显而易见的,深层嵌套的类,私有嵌套类variables的多余getter和setter,以及非常长的函数/variables名都可能在相关函数中创build语法噪声,最终增加它们的长度。

  6. 您的大量开发人员级别的显示器不够大,无法显示它 :实际上,今天的显示器足够大,以至于接近其高度的任何function可能太长。 但是,如果它更大 ,这是一个吸烟枪,有什么不对劲。

  7. 你不能立即确定function的目的 :另外,一旦你真的确定了它的目的,如果你不能用一句话总结这个目的,或者碰巧有一个头痛的话,这应该是一个线索。

总之,单一function可能会产生深远的影响,往往是主要devise缺陷的症状。 每当我遇到阅读绝对喜悦的代码时,它的优雅就会立刻显现出来。 并猜测:function通常短。

我觉得这个页面上的“只做一件事”的口头禅是一个巨大的警告。 有时候,做一件事情变的很多。 如果较小的函数结束了很长的参数列表,那么不要将一个较长的函数分解成一堆较小的函数。 这样做只是把一个单一的function变成一组高度耦合的function,而没有真正的个人价值。

一个函数应该只做一件事。 如果你在一个函数中做了很多小的事情,把每个小东西都做一个函数,并从长函数中调用这些函数。

你真正不想做的是将你的长函数的每10行复制并粘贴到短函数中(如你的例子所示)。

我同意一个function应该只做一件事,但在一个层面上是一回事。

如果你的60行完成了一件事(从你的程序的angular度来看),而构成这60行的部分不会被其他东西使用,那么60行是好的。

除非你把它分解成独立的混凝土块,否则破坏它没有什么好处。 要使用的度量是function而不是代码行。

我曾经参与过许多程序,作者们只把一件事情放到了极致,最终做的就是让人们看起来像是有人把一个手榴弹带到了一个函数/方法中,然后把它炸成几十个不相连的片断,很难跟随。

当抽出这个函数时,你也需要考虑是否会增加不必要的开销,并避免传递大量的数据。

我相信关键的一点是要在这个长期function中寻找可重用性,并把这些部分拿出来。 剩下的就是这个function,不pipe它是10,20或者60行。

60行很大,但function不会太长。 如果它在一个编辑器中的一个屏幕上,你可以一次看到它。 这真的取决于function在做什么。

为什么我可能分手一个function:

  • 太长了
  • 它通过分解代码并使用有意义的名称来使代码更易于维护
  • 该function不具有内聚性
  • 函数的一部分本身是有用的。
  • 当很难为function拿出一个有意义的名字(这可能是太多了)

尺寸大约你的屏幕尺寸(所以去获得一个大的枢轴宽屏,把它):-)

开玩笑,每个function一个逻辑的事情。

而且,积极的一点是unit testing对于做一件事的小逻辑函数要容易得多。 做很多事情的大function更难以validation!

/约翰

经验法则:如果一个函数包含代码块来执行某些操作,那么它与代码的其余部分有些分离,请将其放在一个单独的函数中。 例:

 function build_address_list_for_zip($zip) { $query = "SELECT * FROM ADDRESS WHERE zip = $zip"; $results = perform_query($query); $addresses = array(); while ($address = fetch_query_result($results)) { $addresses[] = $address; } // now create a nice looking list of // addresses for the user return $html_content; } 

好多了:

 function fetch_addresses_for_zip($zip) { $query = "SELECT * FROM ADDRESS WHERE zip = $zip"; $results = perform_query($query); $addresses = array(); while ($address = fetch_query_result($results)) { $addresses[] = $address; } return $addresses; } function build_address_list_for_zip($zip) { $addresses = fetch_addresses_for_zip($zip); // now create a nice looking list of // addresses for the user return $html_content; } 

这种方法有两个好处:

  1. 无论何时您需要获取某个邮政编码的地址,您都可以使用随时可用的function。

  2. 当你需要再次读取函数build_address_list_for_zip()时,你会知道第一个代码块将要做什么(它为特定的邮政编码提取地址,至less你可以从函数名得到)。 如果您将查询代码内联,您首先需要分析该代码。

[另一方面(我会否认我曾经告诉过你,即使在遭受酷刑的情况下):如果你阅读了很多有关PHP优化的知识,你可能会想到尽可能less地保留函数的数量,因为函数调用是非常的,在PHP中非常昂贵。 我不知道,因为我从来没有做过任何基准。 如果这种情况下,如果你的应用程序是非常“性能敏感”,你可能会更好的不跟随你的问题的任何答案;-)]

看看McCabe的圈子,他把他的代码分解成一个图,其中“图中的每个节点对应于程序中的一个代码块,其中stream是连续的,并且弧对应于程序中采用的分支。 “

现在想象你的代码没有function/方法; 它只是一个庞大的代码forms的graphics。

你想打破这个蔓延到方法。 考虑一下,当你这样做的时候,每种方法都会有一定数量的块。 每个方法只有一个方块对所有其他方法都是可见的:第一个方块(我们假定你只能在一个点上跳转到第一个方块)。 每个方法中的所有其他块将隐藏在该方法中的信息,但方法内的每个块可能会跳转到该方法内的任何其他块。

要根据每个方法的块数来确定您的方法的大小,您可能会问自己的一个问题是:我应该使用多less个方法来最大限度地减less所有块之间的最大潜在的相关性数量(MPE)?

这个答案是由一个等式给出的。 如果r是使系统的MPE最小化的方法的数量,并且n是系统中的块的数量,则等式是:r = sqrt(n)

并且可以certificate,这给出了每个方法的块数,也是sqrt(n)。

我个人的启发是,如果我不能在没有滚动的情况下看不到整个事物,那就太长了。

我通常打破一个函数的主要原因是因为它的一些部分也是我正在编写的另一个附近函数的成分,所以通用部分会被分解出来。 另外,如果使用其他类别的许多领域或属性,则很有可能会将相关的块大量地提取出来,并且如果可能的话,将其移动到另一个类中。

如果你有一个代码块在顶部有一个注释,可以考虑把它拉出来,用函数和参数名来说明它的用途,并保留注释代码的基本原理。

你确定那里没有其他地方有用吗? 这是什么function?

在我看来,答案是:什么时候做太多事情。 你的函数应该只执行你期望从函数本身的名字所做的动作。 另一个需要考虑的是如果你想在别人的某些部分重用你的function, 在这种情况下,分割它可能是有用的。

我通常需要放置描述下一个代码块的注释来打破function。 以前进入评论现在进入新的function名称。 这不是硬性的规则,但(对我来说)一个不错的经验法则。 我喜欢代码为自己说话比需要评论的代码更好(因为我已经了解到评论通常在说谎)

请记住,为了重新考虑,最终可能会重新考虑因素,可能会使代码比起初来说更难读。

我的一个同事有一个奇怪的规则,一个函数/方法只能包含4行代码! 他试图坚持这样僵硬,他的方法名称往往变得重复和无意义,加上调用深深嵌套和混淆。

所以我自己的口头禅已经成为:如果你不能想象一个体面的函数/方法名称的代码块重新因子,不要打扰。

我通常使用testing驱动的方法来编写代码。 在这种方法中,函数大小通常与testing的粒度有关。

如果你的testing有足够的重点,那么它会导致你写一个小集中的function,使testing通过。

这也适用于其他方向。 函数需要足够小以便有效地进行testing。 所以在处理遗留代码的时候,我经常发现为了testing它们的不同部分,我分解了更多的函数。

我通常会问自己“这个function的责任是什么”,如果我不能把这个责任说得简明扼要,然后把它翻译成一个小的集中testing,我不知道这个function是否太大。

这部分是品味的问题,但我如何确定这一点是我试图保持我的function大致只有一次(最大)将适合我的屏幕上。 原因是,如果你能一次看到整个事情,就很容易理解正在发生的事情。

当我编写代码时,这是一个混合写长function,然后重构拔出可以被其他function重用的位 – 并且 – 编写离散任务的小函数。

我不知道这个答案是正确的或者错误的(例如,你可以用最大的67行来解决问题,但是有时候可能会增加几个)。

在这个话题上已经有了一些深入的研究,如果你想要最less的bug,你的代码不应该太长。 但也不能太短。

我不同意一个方法应该适合你的显示在一个,但如果你向下滚动超过一页,那么这个方法太长了。

有关进一步讨论,请参阅面向对象软件的最佳class级规模 。

我已经写了500行函数,但是这些只是解码和响应消息的大开关语句。 当单个消息的代码比单个if-then-else更复杂时,我将其提取出来。

实质上,虽然function是500行,但是独立维护的区域平均为5行。

我怀疑你会find很多答案。

我可能会根据在函数内执行的逻辑任务来分解它。 如果你觉得你的短篇小说正在变成一本小说,那么我会build议find并提取不同的步骤。

例如,如果你有一个处理某种stringinput并返回一个string结果的函数,那么你可能会根据逻辑把函数分解成几部分,增加额外字符的逻辑和逻辑全部重新一起作为一个格式化的结果。

简而言之,无论是否使代码清晰易读(无论是简单地确保您的函数具有良好的评论或打破它)是最好的方法。

假设你正在做件事,这个长度将取决于:

  • 你在做什么
  • 你正在使用什么语言
  • 你需要在代码中处理多less抽象层次

60行可能太长,或者可能是正确的。 我怀疑这可能太长了。

有一件事(而且这个东西应该从函数名称中显而易见),但是无论如何,只不过是一段代码罢了。 并随时增加您的字体大小。 如果有疑问,将其重构成两个或更多的function。

从Bob叔叔那里推出一个推特的精神,当你觉得需要在两行代码之间加一个空白行时,你会发现一个函数太长了。 这个想法是,如果你需要一个空白的行来分隔代码,那么它的职责和范围就是分离的。

如果它有三个以上的分支,通常这意味着一个函数或方法应该被分解,以不同的方法来封装分支逻辑。 每个for循环,if语句等在调用方法中都不被视为分支。 用于Java代码的Cobertura(我确定有其他语言的其他工具)计算每个函数的函数中if的数量,并将其总和为“平均圈复杂度”。 如果一个函数/方法只有三个分支,那么它就会得到一个三分,这非常好。 有时很难遵循这个准则,即validation用户input,但是将分支放在不同的方法中,不仅可以帮助开发和维护,而且还可以testing,因为可以很容易地分析执行分支的方法的input,以查看input需要添加到testing用例中,以覆盖未覆盖的分支 – 如果所有分支都在一个单独的方法中,那么自方法开始以来input必须被跟踪,这会影响可testing性。

我的想法是,如果我不得不问自己是否太长,那可能太长了。 它有助于在这个领域制作更小的function,因为它可以在应用程序的生命周期中稍后帮助。