通过使用私有方法正确地降低了圈复杂度



通过将一些决策点重构为单独的方法来使用私有方法来减少CC,减少了实际方法的CC并简化了阅读,但是并没有减少在测试中获得完整分支覆盖的努力。

这合理吗?你有什么现场工作经验?

如果你还感兴趣的话我写了一篇关于圈复杂度的文章

用不同的方法切割代码不会减少复杂性,但只会在局部帮助获得更好的组织。减少CC的唯一方法就是重构!

如果只提取方法,则需要相同数量的测试。看看你的数据结构,它们可能对你没有帮助?

有时,使您的应用程序代码变得不那么复杂和更易读的结果是您的测试代码变得更复杂和更不易读。然而,这并不是不进行重构的理由。产品代码的可读性比测试更重要。

如果你为了减少CC和提高可读性而使一些方法私有,你可以使用像Mockito这样的框架来测试私有方法本身。

根据我的经验,这是一种非常常见的情况——让你的产品代码做正确的事情会使测试变得更加困难
其他例子包括隐藏接口后面的实现细节,永远不会向外调用者传递ORM实体,使某些功能只能通过web服务调用…一般来说,在生产环境中使用API会限制测试。

是的,在进行了大量的重构之后,重新获得你的覆盖率是一件痛苦的事情。有时,当难以测试的功能没有得到适当的测试时,这会导致整体进度的逆转。所以我基本上同意Fortega的观点,除了最后一句话。不要让你的测试烂了,它们会在你最不想要的时候回来。

我不太明白你的问题。显然,将大型方法中的逻辑和决策点重构为私有方法将降低大型方法的圈复杂度。这就是提取方法的要点——它使大方法更短、更简单,从而更容易理解和更改。

这不是欺骗,它只是使你的程序的结构显式。

,但不减少努力在测试中获得完整的分支覆盖。

这对我来说似乎是不合逻辑的。为什么分解出私有方法会使覆盖率测试更容易?我从没见过有人这么说。你是不是误会了什么?

您不应该重构您的代码,因为您认为改进度量所产生的数字对您的代码是有好处的。而不是去追求数字和花哨的报告(不幸的是,我看到有人这样做),你应该把理解指标以及为什么要使用它放在首位。

圈复杂度是一个简单的数学度量,它只表示在所有分支都被导航的情况下,你的代码可以执行多少条不同的路径。所以,是的,高圈复杂度确实表明您的测试可能会变得更复杂。但是有时候没有更简单的方法来编写具有高CC和大量测试的代码。你只需要知道什么时候这是一种公平的做法,什么时候你的设计出了问题。顺便说一句,通过将代码拆分为方法来减少CC并不能减轻测试任务,因为无论哪种方法都必须覆盖代码。这个数字看起来更"漂亮"。

考虑以下问题:你的任务是设计对按键作出反应的代码,并根据按下的按键执行一些任务。该设备只有固定数量的按钮,这意味着软件首先需要详尽无遗,不需要扩展(以命令模式为例)。

您可以编写一个易于阅读的简单switch语句,每个按下的按钮触发一个方法。这将是一个快速有效地解决这个任务的好方法。但是获得按键的方法的CC将是可怕的。你应该分割代码吗?为什么?我的意思是,它是可读的,完美地服务于它的目的,无论你做什么,你的测试仍然需要解释每个按钮的按下。所以重构除了减少这个数字之外没有任何好处。

我的建议是了解什么时候圈复杂度是一个有意义的度量,什么时候不是。另外,可以尝试一下Micheal在他的博客中提出的重构建议。它有一些可靠的建议。

想象一下,你正在过桥,桥的重量限制为10,000磅,而你正驾驶着一辆载有15,000磅货物的卡车。为了满足这个限制,你把货物分成三个拖车,每个拖车重5000磅,然后把它们拉到你的卡车后面。从技术上讲,它减少了卡车所承载的重量,但桥上的张力保持不变。

将代码从大型方法移到较小的、未经测试的私有方法中也是类似的。它使原来的方法看起来不那么复杂,并且有一些好处。但如果它确实以一种有意义的方式降低了原始方法的复杂性,那么该方法就会变得更容易测试。它不是。

原始方法的任何测试必须仍然测试它调用的私有方法中的所有逻辑,就像那些新方法中的所有代码仍然在原始方法中一样。如果我们之前不能测试它(或者非常困难),那么我们仍然会遇到完全相同的问题。

如果我们将代码提取到私有方法中,作为将它们与原始方法(甚至可能与原始类)隔离开来的垫脚石,将会有所帮助。我们可以将这些方法移动到新类中,然后将这些类注入到原始类中。或者根据语言的不同,我们可以注入方法。

完成后,我们可以

  • 使用被注入依赖项的模拟测试原始方法。mock没有复杂性。它每次都按照指令行事。现在,原始方法的测试不包括那些私有方法中的所有代码。我们只需要验证它与这些依赖项的交互是否符合预期。
  • 测试新的类和方法。它们也更小、更简单,这使得它们更容易测试。

最新更新