为什么圈复杂度对于单一方法如此重要?


10

从最近开始,我就在SonarLint for Eclipse中使用它,这对我有很大帮助。但是,它向我提出了关于圈复杂度的问题。

SonarLint认为可接受的CC为10,在某些情况下我超出了CC,大约为5或6个单位。这些部分与映射器相关,其中值依赖于不同的变量,例如:

  • 字段A依赖于字符串sA;
  • 字段B依赖于String sB;
  • 字段C依赖于String sC;
  • 等...

我别无选择,只能if为每个领域放一个。(幸运的是)这不是我的选择,而是我自己无法更改的已经存在的复杂系统。


我的问题的核心是:为什么单一方法中 CC不能过高如此重要?如果您将某些条件转移到一个或多个子方法中以降低复杂性,那么它并不能降低整体功能的成本,只是将问题转移到其他地方,我猜?

(对不起的小错误,如有)。


编辑

我的问题不涉及全局圈复杂性,而仅涉及单个方法的复杂性和方法的拆分(抱歉,我很难解释我的意思)。我在问为什么如果仍然属于“超级方法”,为什么可以将您的条件拆分为较小的方法,而超级方法只能执行每个子方法,从而增加了算法的复杂性。

但是,第二个链接(关于反模式)很有帮助。




^^^“箭头头”问题可能是一个更好的复制品,因为它解释了如何改进代码,但我之所以选择第一个是因为详细的解释回答了您关于圈复杂度问题的一部分
gnat

将方法分成较小的部分不会减少执行的代码总量,但可以使正在执行的单个任务清晰明了。与将它们全部缠绕成一个整体相比,它们每个都可以更容易地被单独理解。至少它从较大的范围中删除了许多一次性使用的中间变量。
2013年

1
对于您所描述的特定情况,我会在您的主要方法中执行一些操作,例如“ A = extractAFrom(sA);”。对于每个领域。您可能会想出更好的名称,因为您知道实际的字段及其用途。
Tin Man

Answers:


32

这里的核心是:“大脑能力”。

您会看到,代码的主要功能之一是...被读取。代码可以很容易阅读和理解;还是很难。

并具有 CC只是意味着一个方法中有很多“级别”的。这意味着:作为人类读者,您将很难理解该方法。

当您阅读源代码时,您的大脑会自动尝试将事物置于视野中:换句话说,它将尝试创建某种形式的“上下文”。

而且,当您有一个仅包含几行且CC极低的小方法(具有好名)时;那么您的大脑就可以轻松接受这个“障碍”。你读了,你理解了;完成。

另一方面,如果您的代码具有较高的CC,则您的大脑将花费更多的“循环”来推断正在发生的事情。

另一种说法是:相对于简单的复杂事物网络,您应该始终倾向于使用复杂的简单事物网络。因为您的大脑更擅长理解小事物。


9
因此,基本上,这与技术问题无关,而是与有关?这听起来确实很聪明,我不知道我以前怎么没想到过。谢谢 !
Yassine Badache '16

4
如果是这样,我全力推荐您购买Robert Martin的“ Clean Code”(您可能会在网上免费找到pdf)。您会发现,创建可读代码是优秀程序员最重要但经常被忽略的优点之一。
GhostCat向Monica C致敬。2016年

@YassineBadache CC还使您很难测试每个角落和裂缝(全面报道)。
图兰斯·科尔多瓦

这也是Dijkstra的goto纸的症结所在。
塞斯·巴丁

以我的经验,错误几乎总是存在于CC较高的方法中,而当错误处于CC较低的方法中时,它们通常是完全显而易见的,它们无法隐藏代码的第一轮运行。此外,我发现我几乎不需要修改低CC方法-减轻了大脑的负担。
罗兰·佩希特尔

6

像其他所有关于代码气味的经验法则一样,CC是一种启发式方法。这不是告诉您绝对真实的故障安全标准。如果确实如此,那么要做的合理的事情就是简单地使这种方法在语言中是非法的,并迫使人们以另一种方式实现自己的目标。

但这不是指标的工作方式。大多数情况下,它们的功能是提醒人们一些他们知道的事情。在您的情况下,您知道逻辑是复杂的,替代解决方案会使它更加复杂。因此,当主要的目的是向知道存在问题的人发出警告时,试图满足原始的经验法则是没有意义的。


2
如果OP声明“ FieldA依赖于String sA”,则我不认为将其移至CalculateFieldA(String sA)会使代码更复杂。
Taemyr '16

2

简而言之:这全部与代码的可读性和可维护性有关。

如果您有一个长且复杂的方法,其中包含许多(嵌套)if,则很难说出它的实际作用。如果提取一些私有方法并以有意义的方式命名它们,那么它会容易得多。


虽然这是一个很好的答案,但有点短。如果您可以提供您所说的话的示例,并且对OP提出的问题更具体:“为什么单一方法中CC值不要太高,为什么如此重要呢?”会很好。
马查多


0

CC只是一种启发式方法,特定分数的“不良”程度取决于许多因素。

就是说,您应该始终将较高的CC视为突出显示可以/应该重构的代码。您说将if语句移至另一种方法隐藏了问题-但是那里是否存在可以抽象而不是复制粘贴n次的模式?如果是长if-else链,可以将其转换为switch语句,还是可以使用多态性,或者其他?如果存在深层嵌套,是否可以合并某些条件子句,或者它表示应该划分为不同类的单独职责?


0

我认为圈复杂性是一个警告。如果您能阅读代码并且理解起来不太复杂,那么我就不用担心太多了,可能还有更重要的事情要担心,总会发生。

减少您提到的CC类型的一种方法是使用多态性,因为您已经用Java标签标记了问题。因此,可以使用命名良好的类来代替字符串的代码路径。这可能会有所帮助,但有时会显得过大,并且会使您的代码更加难以理解。

但是,这可能是很难维护的代码的标志。在阅读该方法时,是否很容易看到每种情况下的代码路径?如果您不太了解代码库并且正在搜索相关但在代码中更进一步的内容,您是否可以跳过此方法?我知道有些人主张将方法分成具有描述性名称的1/2行方法,但有时我认为这比原本要替换的代码更难读。

最终,可维护性是一个棘手的问题,您需要决定自己认为更容易阅读的内容。您完全在考虑这一事实,意味着您处在正确的轨道上。请记住,必须在几年后尝试破译此代码的维护者可能就是您。因此,让他们尽可能地容易。


0

取决于花多少时间(脑部循环)看代码并理解代码在做什么。

  • 考虑一个10行的方法-大概需要几分钟来理解它在做什么。
  • 考虑使用100行方法-大概需要一个小时或更长时间才能理解正在执行的操作。
  • 考虑使用1000行方法-大概需要一天或更长时间才能理解正在执行的操作。

同样,较大的方法更难测试,更难预测可能发生的行为。

圈复杂度是一种度量。在这种情况下,较高的值表示潜在问题。复杂的代码需要花费更长的时间来理解,并且可能没有像不太复杂的方法那样经过全面的测试。因此,重要的是要注意,此代码的哪些部分是复杂的,以进行重构和维护。

要考虑的另一件事是更新复杂的代码。在进行分析时,开发人员可以通过查看代码的复杂性来报告更改代码的风险或多或少。

因此,测量可用于决策目的的复杂性具有很多价值。


1
对不起,Ghost Cat的回答非常相似,应该已刷新页面。
乔恩·雷诺
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.