关于重构:函数何时太长?

When is a function too long?

35 55线100线,线,线,300?当你要开始打破它分开?我问,因为我有一个60线功能(包括评论)和思维打破它除了。

1
long_function(){ ... }

为:

1
2
3
small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

一个函数是不是要用很长的_以外的更多的功能,更小的均值函数调用函数制作等。

当你突破一个较小的距离函数?为什么?

  • 方法应该做一件事情(想只读逻辑功能)
  • 你应该可以在一个单一的解释的方法
  • 它应该适合你到高度显示
  • 避免不必要的开销(从上一点评论……)
  • 单元测试是更容易为小逻辑函数
  • 该函数检查是否部分可以由其他reused类或方法
  • 避免过度的级间耦合
  • 避免深嵌套控制结构
  • 谢谢大家的回答列表和编辑的票,我会选择正确的答案)是一个;

    我现在的想法与重构:那些在心灵)


    下面是一个红色标志列表(没有特定的顺序),它可能指示函数太长:

  • 深度嵌套的控制结构:例如,对于深度为3级的循环,或者甚至只有深度为2级的循环,使用具有复杂条件的嵌套if语句。

  • 状态定义参数太多:通过状态定义参数,我指的是一个函数参数,它保证通过函数的特定执行路径。获取过多的这些类型的参数,就会出现执行路径组合爆炸(这通常与1一起发生)。

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

  • 类间耦合过多:这种缺乏适当的封装导致函数与其他类的密切特性有关,从而延长了它们。

  • 不必要的开销:指出明显的、深度嵌套的类、私有嵌套类变量的多余getter和setter以及异常长的函数/变量名的注释都会在相关函数中产生语法噪声,最终会增加它们的长度。

  • 您的大型开发者级显示器不够大,无法显示:事实上,今天的显示器足够大,以至于任何接近其高度的功能都可能太长。但是,如果它更大,这是一把冒烟枪,有问题。

  • 你不能立即确定函数的用途:而且,一旦你确定了它的用途,如果你不能用一句话来概括这个用途,或者恰好有一个巨大的头痛,这应该是一个线索。

  • 总之,整体功能可以产生深远的后果,并且通常是主要设计缺陷的症状。每当我遇到一个代码,这是一个绝对的快乐阅读,它的优雅是显而易见的。猜猜看:函数的长度通常很短。


    没有真正的硬性规定。一般来说,我喜欢我的方法只是"做一件事"。因此,如果它在抓取数据,然后对数据进行处理,然后将数据写入磁盘,那么我将把抓取和写入拆分成单独的方法,这样我的"主"方法只包含"做某事"。

    不过,"做点什么"仍然可能是相当多的行,所以我不确定多行是要使用的正确度量标准:)

    编辑:这是我上周在工作中邮寄的一行代码(证明一点)。这不是我的习惯。)我当然不想在我的方法中有50-60个坏男孩。

    1
    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();


    我认为这一页上的"只做一件事"的咒语有一个巨大的警告。有时做一件事会使许多变量发生变化。不要把一个长函数分解成一堆较小的函数,如果较小的函数最终拥有较长的参数列表。这样做只是将一个函数变成一组高度耦合的函数,而没有真正的单个值。


    一个函数应该只做一件事。如果你在一个函数中做了很多小事情,把每个小事情都变成一个函数,然后从long函数调用这些函数。

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


    我同意一个函数应该只做一件事,但是在什么层次上它是一件事。

    如果你的60行代码完成了一件事(从你的程序的角度来看),而组成这60行代码的部分不会被其他任何代码使用,那么60行代码就可以了。

    打破它没有真正的好处,除非你能把它分解成独立的混凝土块。要使用的度量标准是功能性的,而不是代码行。

    我曾在许多程序中工作过,在这些程序中,作者们把唯一的一件事带到了一个极端的水平,结果所做的就是让它看起来像是有人拿了一枚手榴弹到一个函数/方法,然后把它炸成几十个不相连的碎片,这是很难理解的。

    在提取该函数的片段时,还需要考虑是否要添加任何不必要的开销,并避免传递大量数据。

    我认为关键是要在这个长函数中寻找可重用性,并将这些部分拉出。剩下的就是函数,不管是10行、20行还是60行。


    60行是大的,但对于函数来说不太长。如果它适合一个编辑器中的一个屏幕,您可以一次看到全部内容。这实际上取决于函数在做什么。

    我为什么要分解一个函数:

    • 太长了
    • 它通过分解代码并为新函数使用有意义的名称来提高代码的可维护性。
    • 功能不连贯
    • 函数的某些部分本身是有用的。
    • 当很难为函数指定一个有意义的名称时(它可能做得太多)


    尺寸大约你的屏幕尺寸(所以去拿一个大的旋转宽屏并转动它)。-)

    别开玩笑了,每个函数都有一个逻辑。

    积极的是,单元测试对于完成1件事情的小逻辑函数来说确实要容易得多。做很多事情的大功能很难验证!

    Johan


    经验法则:如果一个函数包含执行某些操作的代码块,而这些代码块与其他代码有一定的分离,则将其放入一个单独的函数中。例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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;
    }

    好得多:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
    }

    这种方法有两个优点:

  • 每当需要获取某个邮政编码的地址时,都可以使用现成的函数。

  • 当您需要再次读取函数build_address_list_for_zip()时,您知道第一个代码块将要做什么(它获取某个邮政编码的地址,至少这是您可以从函数名中得到的)。如果您将查询代码保留在内联中,那么首先需要分析该代码。

  • [另一方面(我会否认我告诉过你的,即使是在折磨之下):如果你读了很多关于PHP优化的文章,你会想到尽可能少地使用函数,因为函数调用在PHP中非常昂贵。我不知道,因为我从来没有做过基准测试。如果是这样的话,如果您的应用程序非常"性能敏感",那么最好不要遵循问题的任何答案;-)]


    看一眼McCabe的循环图,他把代码分解成一个图,"图中的每个节点对应于程序中的一段代码,其中流是连续的,弧对应于程序中的分支。"

    现在假设您的代码没有函数/方法;它只是一个以图形形式出现的代码的巨大扩展。

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

    为了确定方法在每个方法的块数方面的大小,您可能会问自己一个问题:我应该用多少方法来最小化所有块之间可能存在的最大依赖项数(MPE)?

    这个答案由一个方程式给出。如果r是最小化系统mpe的方法数,n是系统中的块数,则方程式为:r=qRT(n)

    可以证明,这给出了每个方法的块数,也就是sqrt(n)。


    我个人的启发式做法是,如果不滚动就看不到整件事,那就太长了。


    记住,您最终可能只是为了重新分解而重新分解,这可能会使代码比最初更不可读。

    我以前的一个同事有一个奇怪的规则,一个函数/方法只能包含4行代码!他试图如此严格地坚持这一点,以至于他的方法名经常变得重复和毫无意义,而且调用变得非常嵌套和混乱。

    因此,我自己的座右铭是:如果您不能为要分解的代码块找到一个合适的函数/方法名,那么就不用费心了。


    我通常通过需要放置描述下一个代码块的注释来分解函数。以前进入注释的内容现在进入新的函数名。这不是硬性规定,但对我来说是一条很好的经验法则。比起需要注释的代码,我更喜欢自己说代码(正如我了解到的那样,注释通常是谎言)


    我通常将一个函数分解的主要原因是,它的各个部分也是我正在编写的另一个附近函数的组成部分,所以公共部分被分解了。另外,如果它使用了其他类中的大量字段或属性,那么很有可能相关的块可以被大规模地提取出来,如果可能的话,也可以转移到其他类中。

    如果您有一个代码块,上面有一个注释,那么可以考虑将它拉出到一个函数中,函数和参数名说明了它的用途,并保留注释作为代码的基本原理。

    你确定里面没有其他有用的东西吗?它是什么功能?


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

    如果您的测试足够集中,那么它将引导您编写一个小的集中函数来通过测试。

    这也适用于另一个方向。函数必须足够小才能有效地测试。因此,在处理遗留代码时,我经常发现为了测试它们的不同部分,我分解了更大的函数。

    我通常会问自己"这个功能的责任是什么",如果我不能用一个简洁明了的句子来陈述这个责任,然后把它翻译成一个小的集中测试,我想知道这个功能是否太大了。


    在我看来,答案是:当它做了太多的事情。您的函数应该只执行您从函数本身的名称期望的操作。另一个需要考虑的问题是,如果您想在其他地方重用您的函数的某些部分,在这种情况下,拆分它可能很有用。


    这在一定程度上是一个品味问题,但我如何确定这一点,我会尽量保持我的功能在同一时间(最多)只适合我的屏幕。原因是如果你能同时看到整个事情,就更容易理解发生了什么。

    当我编写代码时,它是编写长函数的混合体,然后重构以提取可被其他函数重用的位,以及编写执行离散任务的小函数。

    我不知道这个问题有任何正确或错误的答案(例如,你可以用67行作为你的最大值,但有时可能需要增加一些)。


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

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


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

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

    见最佳类大小面向对象的软件供进一步讨论。


    一件事(从函数名中应该很明显),但不管怎样,不超过一屏代码。并且可以随意增加字体大小。如果有疑问,请将其重构为两个或多个函数。


    我的想法是,如果我不得不问自己它是否太长,它可能太长了。在这方面,它有助于生成较小的函数,因为它可以在应用程序的生命周期的后期提供帮助。


    我想你会找到很多答案的。

    我可能会根据函数中正在执行的逻辑任务将其分解。如果你觉得你的短篇小说正在变成一部小说,我建议你找到并提炼出不同的步骤。

    例如,如果您有一个函数可以处理某种类型的字符串输入并返回字符串结果,那么您可以根据将字符串拆分为多个部分的逻辑、添加额外字符的逻辑以及将其作为格式化结果重新组合在一起的逻辑,将该函数拆分。

    简而言之,无论是什么让代码变得清晰易读(无论是通过简单地确保函数具有良好的注释还是将其分解)都是最好的方法。


    一段时间前,Bob叔叔的推文精神得到了扩展,当你觉得有必要在两行代码之间加一个空白行时,你就会知道一个函数变得太长了。其思想是,如果您需要一个空白行来分隔代码,那么它的职责和范围在这一点上是分离的。


    如果它有三个以上的分支,通常这意味着应该将一个函数或方法分离开来,以便用不同的方法封装分支逻辑。

    每个for循环、if语句等都不被视为调用方法中的分支。

    Java代码的COBURTURA(我肯定还有其他语言的工具)在每个函数的函数中计算IF等的数目,并将其求和为"平均圈复杂度"。

    如果一个函数/方法只有三个分支,那么它将在该度量上得到三个分支,这非常好。

    有时很难遵循这个准则,即验证用户输入。然而,将分支放到不同的方法中不仅有助于开发和维护,而且还有助于测试,因为执行分支的方法的输入可以很容易地进行分析,以查看需要向测试用例添加哪些输入,以便覆盖未覆盖的分支。

    如果所有的分支都在一个方法中,那么自该方法开始以来,必须跟踪输入,这会妨碍可测试性。


    假设您正在做一件事,则长度取决于:

    • 你在做什么
    • 你用什么语言
    • 在代码中需要处理多少个抽象级别

    60行可能太长,或者正好。不过,我怀疑这可能太长了。