35线,55线,100线,300线?什么时候应该开始分手?我问,因为我有60行(包括评论)的功能,并且正在考虑将它分开.
long_function(){ ... }
成:
small_function_1(){...} small_function_2(){...} small_function_3(){...}
这些函数不会在long_function之外使用,因为较小的函数意味着更多的函数调用等.
你什么时候将一个函数拆分成更小的函数?为什么?
方法应该只做一个逻辑事情(考虑功能)
你应该能够用一句话来解释这个方法
它应该适合您的显示器的高度
避免不必要的开销(注释明显的评论......)
对于小型逻辑功能,单元测试更容易
检查部分函数是否可以被其他类或方法重用
避免过多的类间耦合
避免深层嵌套的控制结构
谢谢大家的答案,编辑列表并投票给出正确的答案,我会选择那个;)
我现在正在重构这些想法:)
这是一个红色标记列表(没有特定的顺序),可能表明函数太长:
深度嵌套的控制结构:例如,for-loops 3级深,甚至只有2级深,嵌套的if语句具有复杂的条件.
太多的状态定义参数:通过状态定义参数,我的意思是一个函数参数,它保证通过函数的特定执行路径.获取太多这些类型的参数,并且您有执行路径的组合爆炸(这通常与#1一起发生).
在其他方法中重复的逻辑:糟糕的代码重用是整体过程代码的巨大贡献者.很多这样的逻辑重复可能非常微妙,但一旦重新考虑,最终结果可能是一个更优雅的设计.
过多的类间耦合:缺乏适当的封装导致函数关注其他类的紧密特征,从而延长它们.
不必要的开销:注释指出明显的,深度嵌套的类,私有嵌套类变量的多余getter和setter,以及异常长的函数/变量名都可以在相关函数中创建语法噪声,最终会增加它们的长度.
你的大量开发者级别的显示器还不足以显示它:实际上,今天的显示器足够大,以至于任何接近其高度的功能都可能太长.但是,如果它更大,这是一个冒烟的东西是错误的.
你不能立即判断函数的目的:此外,一旦你真正做决定的目的,如果你不能概括这个目的在一个句子或碰巧有一个巨大的头痛,这应该是一个线索.
总之,单片功能可能会产生深远的影响,并且通常是主要设计缺陷的症状.每当我遇到令人非常高兴阅读的代码时,它的优雅立刻就会显现出来.并猜测:功能通常非常短.
没有真正的硬性规则.一般来说,我喜欢我的方法只是"做一件事".因此,如果它正在抓取数据,然后对该数据执行某些操作,然后将其写入磁盘,那么我将拆分并写入单独的方法,因此我的"主要"方法只包含"做某事".
虽然"做某事"可能仍然是相当多的几行,所以我不确定多少行是正确使用的指标:)
编辑:这是我上周邮寄工作的一行代码(为了证明一点......这不是我养成的习惯:)) - 我当然不希望我的方法中有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();
我认为这页上的"只做一件事"的口头禅是一个巨大的警告.有时候做一件事就是玩弄很多变数.如果较小的函数最终具有长参数列表,请不要将长函数分解为一堆较小的函数.这样做只会将单个函数转换为一组高度耦合的函数,而没有真正的单个值.
一个函数应该只做一件事.如果你在函数中做了很多小事,那就把每个小事都做成一个函数,并从long函数中调用这些函数.
你真的不想要做的就是复制和每10行的长期功能分为短功能的粘贴(如你的例子说明).
我同意一个函数应该只做一件事,但在什么级别是一件事.
如果你的60行是完成一件事(从你的程序角度来看),那些组成这60行的作品将不会被其他任何东西使用,那么60行就可以了.
除非你能把它分解成独立的具体部分,否则分解它并没有什么好处.要使用的度量标准是功能而不是代码行.
我曾经参与过许多程序,其中作者将唯一的一件事情带到了一个极端的水平,所有它最终做的就是让它看起来像有人拿一个手榴弹到一个功能/方法并把它吹成几十个未连接的部分难以理解.
在提取该功能的部分时,您还需要考虑是否要添加任何不必要的开销并避免传递大量数据.
我认为关键是要在那个长函数中寻找可重用性并将这些部分拉出来.剩下的就是功能,无论是10行,20行还是60行.
60行很大但功能不太长.如果它适合编辑器中的一个屏幕,您可以一次看到它.这实际上取决于功能的作用.
为什么我可能会破坏一个功能:
太长了
它通过分解代码并为新函数使用有意义的名称,使代码更易于维护
这个功能没有凝聚力
部分功能本身很有用.
当很难为这个函数提出一个有意义的名字时(它可能做得太多了)
我个人的启发式是,如果我没有滚动就看不到整个事情,那就太长了.
尺寸大约你的屏幕尺寸(所以去获得一个大转轴宽屏并转动它)... :-)
除了笑话,每个功能一个合乎逻辑的事情.
而积极的一点是单元测试对于做一件事的小逻辑函数来说真的要容易得多.做很多事情的大功能很难验证!
/约翰
经验法则:如果一个函数包含执行某些操作的代码块,这与代码的其余部分有些分离,请将其置于单独的函数中.例:
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; }
这种方法有两个优点:
每当您需要获取某个邮政编码的地址时,您都可以使用现成的功能.
当你需要build_address_list_for_zip()
再次阅读这个函数时,你知道第一个代码块将要做什么(它获取某个邮政编码的地址,至少这是你可以从函数名称中得到的).如果您将查询代码保留为内联,则首先需要分析该代码.
[另一方面(我会否认我告诉过你,即使是在折磨下):如果你阅读了很多关于PHP优化的内容,你可以想到保持函数数量尽可能小,因为函数调用非常,在PHP中非常昂贵.我不知道,因为我从未做过任何基准测试.如果是这种情况,如果您的应用程序非常"性能敏感",您可能会更好地不遵循您的问题的任何答案;-)]
看一下McCabe的圈形图,在其中他将代码分解成图形,其中"图形中的每个节点对应于程序中的一个代码块,其中流程是顺序的,弧线对应于程序中的分支. "
现在想象你的代码没有函数/方法; 它只是一个巨大的图形形式的代码蔓延.
你想将这种蔓延分解为方法.考虑一下,当你这样做时,每种方法中都会有一定数量的块.所有其他方法只能看到每个方法的一个块:第一个块(我们假设您只能在一个点跳转到一个方法:第一个块).每个方法中的所有其他块将是该方法中隐藏的信息,但方法中的每个块都可能跳转到该方法中的任何其他块.
要根据每个方法的块数确定方法的大小,您可能会问自己的一个问题是:我应该使用多少方法来最小化所有块之间的最大潜在依赖关系数(MPE)?
这个答案由一个等式给出.如果r是最小化系统MPE的方法的数量,并且n是系统中的块数,则等式为:r = sqrt(n)
并且可以证明,这给出了每个方法的块数,也就是sqrt(n).