迭代方法是否可以降低循环复杂度并提高可支持性?


11

诸如C#,JavaScript之类的现代语言中常见的迭代方法以及Java 8中(希望如此)的迭代方法是否可以减少循环复杂性对代码的易读性和可支持性的影响?

例如,在C#中,我们可能具有以下代码:

List<String> filteredList = new List<String>();

foreach (String s in originalList){
   if (matches(s)){
      filteredList.add(s);
   }
}

这具有2的简单圈数复杂度。

我们可以轻松地将其重写为:

List<String> filteredList = originalList.where(s => matches(s));

其简单的圈复杂度为0。

这实际上会导致更受支持的代码吗?是否有关于该主题的实际研究?


2
您的标题要求降低圈复杂度,但是您的文字假定CC降低并且要求可维护性。这可能是一个有价值的问题,您可以编辑标题以使其更准确吗?
Kilian Foth,

@KilianFoth好吗?
C. Ross,

我将迭代方法与迭代开发方法联系在一起。我认为更好的术语是高阶函数。(顺便说一句,方法没有返回类型/ void,而函数返回了某种东西)。
约翰内斯·鲁道夫

@JohannesRudolph“方法没有返回类型/无效,而函数返回了某种东西” –这是哪种语言?我从没听过;实际上,我所听到的唯一真正的区别是方法与类相关联,而函数与类无关。
threed

Answers:


14

我的猜测是您只是划分/移动了复杂性。它减少了,因为您不计入.where()CC中的实现。

总体CC并没有真正改变,您自己的代码的CC减少了,只是因为它现在已移至框架的代码。

我会说它更易于维护。当它是语言的功能时,请使用它。您使用的不是一个“哦,我知道,这是一个聪明的把戏”,而只是一个简单的内联reduce-like函数。


11

您所要做的只是凸显出圈复杂度作为度量标准的缺陷,而代码的复杂度实际上并没有改变。您在第一个示例中有一个显式分支,并且需要了解在第二个示例中有一个隐式分支。如果您了解语法,并且它使用的基本语法较少,那么第二个则更清晰易懂。


1
有人可能会争辩说,这里自己的代码的循环复杂性降低了,因为where这很可能来自一个框架。我认为,即使在这里,圈复杂度作为度量也是有用的,因为虽然将一个功能分解为多个功能并不会降低系统的整体复杂度(实际上,它通常会稍微增加一点,但通常会增加),但是它确实可以帮助您确定何时系统的一部分变得过于复杂,需要分解。
threed

6

为了客观地回答这个问题,我们需要某种可维护性度量。圈复杂度本身不是可维护性的度量,但它一些指标,主旨来衡量可维护性的一个组成部分。例如,可维护性指数的公式为:

MI = 171 - 5.2 * ln(V) - 0.23 * (G) - 16.2 * ln(LOC)

G所讨论代码的圈复杂度在哪里?因此,通过定义来减少一段代码的循环复杂度确实可以提高代码的可维护性指数和其他类似地使用循环复杂度的度量

很难说您提出的那种改变是否使程序对程序员来说更易于维护。这可能取决于他们对该where方法的熟悉程度(在您的情况下)。


警告:魔术数字!(>_<)
2013年

可维护性指数只有一个小问题。它的三个组成部分是霍尔斯特德体积,环复杂性和代码行。霍尔斯特德体积和圈复杂度都与代码行高度相关。这意味着可以推导出仅依赖于代码行的可维护性指标的替代近似方程,其近似精度与原始精确度相同,而计算难度却大大降低。
John R. Strohm 2013年

@ JohnR.Strohm我认为即使您仅使用LOC作为可维护性指标,您也会得到类似的结果。OP的更改将LOC从4降低到1,而其他降低圈复杂度的更改也同样可能降低LOC。无论哪种方式,实际上都取决于您如何定义和衡量可维护性。
Caleb

1
@Caleb:同意。我要说的是,人们常常选择这些真正复杂的度量标准和度量标准组合,却没有意识到几乎所有的度量标准都已与真实的代码和普通的代码行(LOC)紧密相关。 ,因此没有比LOC更多的预测或描述性价值。
John R. Strohm

3

因为已经证明,循环复杂度(CC)与代码大小紧密相关,所以“如此之多,可以说CC绝对没有自己的解释能力”。您真正要问的是:“诸如在C#,JavaScript和Java 8中(希望)在现代语言中常见的迭代方法,是否会减少代码大小对代码可理解性和可支持性的影响”。

到那时,人们希望答案会很明显。几十年来,众所周知,较短的代码通常更易于理解,维护和支持。


2

如果您在谈论环复杂性的原始统计信息,那么可以肯定。您只是将其从2换成0。如果按数字计算,纯净收益将一直保持不变。

但是,从实用(人类)的角度来看,我认为您实际上已经将复杂性提高了2。其中之一是因为现在其他一些程序员必须掌握或掌握流利语法LINQ的知识才能理解这一点。码。

难度增加的另一个原因是对Lambda表达式的理解。尽管在这种情况下 Lambda相当简单,但是必须进行一些范式转换才能完全理解它们。

这种情况下使用a.where(x => matches(x, arg))并不可怕,说实话,这是让一些同事第一次查看和使用LINQ和Lambda表达式的好方法(我实际上是向一些使用LINQ / Lambdas的以前的同事提供了有关LINQ / Lambdas的教程和其他代码集,效果很好。)但是,使用这些代码确实需要一些知识。

我建议谨慎,因为我已经看到LINQ重构实际上比foreach循环变得更糟的情况。


1

从主观上讲,这取决于开发人员的受众,如果他们了解lambda表达式,那么;

List<String> filteredList = originalList.where(s => matches(s));

更快地了解,也许稍微容易些。我会更担心使用s和matches()。两者都不是自我描述的。

List<String> filteredList = 
    originalList.where(stringToBeTested => matchesNameTest(stringToBeTested));

要么

List<String> filteredList = 
        originalList.where(originalListString => matchesNameTest(originalListString));

为开发人员提供了更有意义的信息,并且更易于分析,而无需深入match()函数来确定正在执行的匹配。

可维护性不仅与理解代码的能力有关,而且主要与人们理解代码的速度和准确性有关。


2
嗨,王牌。这是一个示例,故意缺少语义内容以突出结构。
C. Ross,
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.