我应该将特定功能提取到功能中,为什么?


29

我有一个执行3个任务的大型方法,每个任务都可以提取到一个单独的函数中。如果我将为每个任务添加其他功能,那么它会使我的代码变得更好还是更坏,为什么?

显然,它将在主函数中减少代码行的数量,但是将有附加的函数声明,因此我的类将具有附加的方法,我认为这不好,因为这会使类变得更加复杂。

我应该在编写所有代码之前执行此操作,还是在完成所有操作然后提取函数之前将其保留?


19
“直到一切都完成,我才离开”通常与“永远不会完成”同义。
欣快的2012年

2
通常这是正确的,但也请记住YAGNI的相反原理(在这种情况下将不适用,因为您已经需要它)。
2012年


只是想强调一点,不要过多地关注减少代码行。而是尝试从抽象角度进行思考。每个功能只能有一个工作。如果发现函数执行的功能不止一项,那么通常应该重构该方法。如果遵循这些准则,则几乎不可能具有过长的功能。
阿德里安

Answers:


35

这是我经常链接的书,但在这里我再去一遍:罗伯特·C·马丁的清洁代码,第3章,“函数”。

显然,它将在主函数中减少代码行的数量,但是将有附加的函数声明,因此我的类将具有附加的方法,我认为这不好,因为这会使类变得更加复杂。

您喜欢阅读带有+150行的函数,还是调用3 +50行函数的函数?我想我更喜欢第二种选择。

是的,从某种意义上说,它将使您的代码更“可读”。使函数执行仅一件事,它们将更易于维护并为其生成测试用例。

另外,我从上述书籍中学到的一件非常重要的事情:为您的功能选择正确的名称。函数越重要,名称就应该越精确。不必担心名称的长度,如果必须调用FunctionThatDoesThisOneParticularThingOnly,则以这种方式命名。

在执行重构之前,请编写一个或多个测试用例。确保它们起作用。重构完成后,您将能够启动这些测试用例,以确保新代码正常工作。您可以编写其他“较小”的测试,以确保您的新功能可单独很好地执行。

最后,这与我刚刚写的内容并不矛盾,问问自己是否真的需要进行此重构,请查看“ 何时重构?” 的答案。(此外,搜索关于“重构”的问题,还有更多,答案很有趣)

我应该在编写所有代码之前先执行此操作,还是在完成所有操作然后提取函数之前将其保留?

如果代码已经存在并且可以正常工作,而您的下一个发布时间紧迫,请不要触摸它。否则,我认为应该在可能的情况下使小型函数运行,因此,应在有可用时间的时候进行重构,同时确保一切都像以前那样工作(测试用例)。


10
实际上,鲍勃·马丁(Bob Martin)多次表明,他偏爱2到3行的7个功能,而不是15行的一个功能(请参阅此处sites.google.com/site/unclebobconsultingllc/…)。这就是许多甚至经验丰富的开发人员都会抵制的地方。我个人认为,许多“经验丰富的开发人员”很难接受他们仍然可以改进这样的基本功能,例如经过10年以上的编码来构建具有功能的抽象。
布朗

+1只是指一本我认为应该在任何软件公司的书架上都可以买到的书。
Fabio Marcolini 2014年

3
我可能在这里解释一下,但是那本书中几乎每天都在脑海中回荡的一句话是:“每个功能都只能做一件事情,并且做得很好”。此处似乎特别相关,因为OP表示“我的主要职能是做三件事”
wakjah 2015年

你是绝对正确的!
Jalayn'2

取决于三个单独的功能之间有多少交织在一起。与完全重复依赖的三个代码块相比,将一个代码块放在一个地方可能更容易。
user253751'9

13

是的,显然。如果很容易看到并分离单个功能的不同“任务”。

  1. 可读性-具有好名的函数使您无需阅读该代码即可明确地执行代码。
  2. 可重用性-在多个地方使用做一件事情的功能比起执行不需要的功能要容易。
  3. 可测试性-具有一个定义的“功能”的功能较容易测试,而其中一个功能很多

但是,这可能会有问题:

  • 不容易看到如何分离功能。在继续进行分离之前,这可能需要首先重构函数的内部。
  • 该函数具有巨大的内部状态,即被传递。这通常需要某种OOP解决方案。
  • 很难确定应该执行什么功能。对它进行单元测试并重构,直到知道为止。

5

您提出的问题不是编码,约定或编码实践的问题,而是可读性和文本编辑器显示您编写的代码的方式的问题。帖子中也出现了同样的问题:

可以将长函数和方法拆分为较小的函数,即使它们不会被其他任何东西调用也可以吗?

当实现一个大型系统以封装它所组成的不同功能时,将一个功能分为多个子功能是有意义的。无论如何,迟早您会发现自己拥有许多重要功能。如果将它们保留为单个长函数,或者将它们拆分为较小的函数,则其中一些是无法阅读且难以维护的。对于在系统的其他任何地方都不需要执行操作的功能,尤其如此。让我们选择这么长的功能之一,并从更广泛的角度考虑它。

优点:

  • 阅读完该书后,您将对该函数所做的所有操作都有完整的了解(可以将其当作一本书阅读);
  • 如果要调试它,则可以逐步执行它,而无需跳转到任何其他文件/文件的一部分;
  • 您可以自由访问/使用在函数的任何阶段声明的任何变量;
  • 函数实现的算法完全包含在函数中(封装);

相反:

  • 它占用了屏幕的许多页面;
  • 阅读它需要很长时间;
  • 记住所有不同的步骤并不容易。

现在,让我们想象一下将long函数拆分为几个子函数,并以更广阔的前景对其进行研究。

优点:

  • 除了请假功能,每个功能都用文字(子功能的名称)描述完成的不同步骤。
  • 读取每个功能/子功能花费的时间非常短。
  • 显然,每个子功能会影响哪些参数和变量(关注点分离);

相反:

  • 容易想象像“ sin()”这样的函数的作用,但是不容易想象我们的子函数的作用。
  • 该算法现在消失了,它现在分布在may子功能中(无概述);
  • 逐步调试时,很容易忘记您来自的深度级别函数调用(在项目文件中四处跳转);
  • 阅读不同的子功能时,您可以轻松地松开上下文。

两种解决方案都有利有弊。实际的最佳解决方案是让编辑器进行扩展,内联,并在整个深度内扩展每个函数的调用。这将使子功能拆分为唯一的最佳解决方案。


2

对我来说,将代码块提取到函数中有四个原因:

  • 您正在重用它:您只是将代码块复制到剪贴板中。不仅将其粘贴,还可以将其放入函数中,并在两侧将其替换为函数调用。因此,每当需要更改该代码块时,只需更改该单个函数即可,而无需在多个位置更改代码。因此,无论何时复制代码块,都必须创建一个函数。

  • 这是一个回调:这是事件处理程序或库或框架调用的某种用户代码。(我很难想象没有功能。)

  • 您相信它会在当前项目或其他地方被重用:您刚刚编写了一个块,用于计算两个数组的最长公共子序列。即使您的程序仅调用一次此功能,我也相信最终在其他项目中也将需要此功能。

  • 您需要自我记录的代码:因此,与其在一段概述其功能的代码块上写一行注释,不如将整个内容提取到一个函数中,然后将其命名为要写入注释中的内容。尽管我不喜欢这个,但是因为我喜欢写出所用算法的名称,选择该算法的原因等。那么函数名可能太长了...


1

我敢肯定,您已经听到建议,应该尽可能严格地限制变量的范围,我希望您对此表示同意。函数是范围的容器,在较小的函数中,局部变量的范围较小。应当更清楚地了解如何使用它们以及何时使用它们,并且很难以错误的顺序或在初始化之前使用它们。

同样,功能是逻辑流程的容器。只有一种方法,可以清楚地标记出方法,如果函数足够短,则内部流程应该很明显。这具有降低循环复杂性的效果,这是降低缺陷率的可靠方法。


0

旁白:我是为回应达林的问题(现已关闭)而写的,但我仍然认为这可能对某人有所帮助,因此请继续


我认为雾化函数的原因有2倍,正如@jozefg所提到的,取决于所使用的语言。

关注点分离

这样做的主要原因是将不同的代码段分开,因此任何不直接对函数的期望结果/意图作出贡献的代码块都是一个单独的问题,可以将其提取出来。

假设您有一个后台任务也更新了进度条,则进度条更新与长时间运行的任务没有直接关系,因此即使它是使用进度条的唯一代码,也应将其提取。

在JavaScript中说您有一个函数getMyData(),该函数1)从参数构建一条肥皂消息,2)初始化服务引用,3)用肥皂消息调用服务,4)解析结果,5)返回结果。看起来很合理,我已经多次编写了这个确切的函数-但实际上可以将其拆分为3个私有函数,仅包括3和5的代码(如果有的话),因为其他任何代码都不直接负责从服务获取数据。

改进的调试体验

如果您具有完全原子的功能,则堆栈跟踪将成为任务列表,列出所有成功执行的代码,即:

  • 获取我的数据
    • 建立肥皂消息
    • 初始化服务参考
    • 解析的服务响应-错误

会更有趣,然后发现获取数据时出错。但是,某些工具对于调试详细的调用树甚至更为有用,例如Microsoft的Debugger Canvas

我也理解您的担心,因为遵循这种方式编写的代码可能很困难,因为到了最后,您确实需要在单个文件中选择函数的顺序,因为您的调用树将比这复杂得多。 。但是,如果函数命名正确(智能允许我在任何函数中使用3-4个大写字母,请不要让我慢下来),并在文件顶部使用公共接口进行结构化,则您的代码将像伪代码一样读取,到目前为止,这是获得代码库高级理解的最简单方法。

仅供参考-这是“按我说的不做”的事情之一,保持代码原子性是没有意义的,除非您无情地与恕我直言保持一致,我不是。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.