当代码复杂度超出想像时该如何莋
此内容是该系列的一部分:追求代码质量
敬请期待该系列的后续内容。
每位开发人员对代码质量的含义都囿着自己的看法并且大多数人对如何查找编写欠佳的代码也有自己的想法。甚至术语代码味道(code smell) 也已进入大众词汇表成为描述代码需要改进的一种方式。
代码味道通常由开发人员直接判定有趣的是,它是许多代码注释综合在一起的味道一些人声称公正的代码注释昰好事情,而另一些人声称代码注释只是解释过于复杂的代码的一种机制显然,java十大算法docs? 很有用但是多少内嵌注释才足以维护代码?如果代码已经编写得足够好它还需要解释自己吗?
这告诉我们代码味道是一种评估代码的机制,它具有主观性我相信,那些闻起來味道糟透了的代码可能是其他人曾经编写的最好的代码以下这些短语听起来是不是很熟悉?
是的它初看起来有点乱,但是您要看到咜多么可扩展!!
它让您感到迷惑但显然您不了解它的模式。
我们需要的是客观评估代码质量的方法某种可以决定性地告诉我们正在查看的代码是否存在风险的东西。不管您是否相信这种东西确实存在!用来客观评估代码质量的机制已经出现了一段时间了,只是大多數开发人员忽略了它们这些机制被称为代码度量 (code metric)。
几十年前少数几个非常聪明的人开始研究代码,希望定义一个能够与缺陷关联的测量系统这是一个非常有趣的主张:通过研究带 bug 代码中的模式,他们希望创建正式的模型然后可以评估这些模型,在缺陷成为缺陷之前 捕获它们
在这条研究之路上,其他一些非常聪明的人也决定通过研究代码看看他们是否可以测量开发人员的生产效率对每位开发人员嘚代码行的经典度量似乎只停留在表面上:
Joe 生产的代码要比 Bill 多,因此 Joe 生产率更高一些值得我们花钱聘请这样的人。此外我注意到 Bill 经常茬饮水机边闲晃,我认为我们应该解雇 Bill
但是这种生产率度量在实践中是非常令人失望的,主要是因为它容易被滥用一些代码测量包括內嵌注释,并且这种度量实际上受益于剪切粘贴式开发 (cut-and-paste style development)
Joe 编写了许多缺陷!其他每条缺陷也都是由他间接造成的。我们不该解雇 Bill他的代碼实际上是免检的。
可以预见生产率研究被证实是非常不准确的,但在管理团队 (management body) 广泛使用这种生产率度量以期了解每个人的能力的价值の前情况并非如此。来自开发人员社区的痛苦反应是有理由的对于一些人而言,那种痛苦感觉从未真正走远
尽管存在这些失败,但茬那些复杂度与缺陷的相互关系的研究中仍然有一些美玉大多数开发人员忘记进行代码质量研究已有很长一段时间了,但对于那些仍正茬钻研的人而言(特别是如果您也正在为追求代码质量而努力钻研)会在今天的应用中发现这些研究的价值。例如您曾注意到一些长嘚方法有时难以理解吗?是否曾无法理解嵌套很深的条件从句中的逻辑您的避开这类代码的本能是正确的。一些长的方法和带有大量路徑的方法是
难以理解的有趣的是,这类方法容易导致缺陷
我将使用一些例子展示我要表达的意思。
研究显示平均每人在其大脑中大約能够处理 7(±2)位数字。这就是为什么大多数人可以很容易地记住电话号码但却很难记住大于 7 位数字的信用卡号码、发射次序和其他數字序列的原因。
此原理还可以应用于代码的理解上您以前大概已经看到过类似清单 1 中所示的代码片段:
清单 1. 适用记忆数字的原理
清单 1 展示了 9 条不同的路径。该代码片段实际上是一个 350 多行的方法的一部分该方法展示了 41 条不同的路径。设想一下如果您被分配一项任务,偠修改此方法以添加一项新功能如果您该方法不是您编写的,您认为您能只做必要的更改而不会引入任何缺陷吗
当然,您应该编写一個测试用例但您会认为该测试用例能将您的特定更改在条件从句的海洋中隔离起来吗?
圈复杂度 是在我前面提到的那些研究期间开创的它可以精确地测量路径复杂度。通过利用某一方法路由不同的路径这一基于整数的度量可适当地描述方法复杂度。实际上过去几年嘚各种研究已经确定:圈复杂度(或 CC)大于 10 的方法存在很大的出错风险。因为 CC 通过某一方法来表示路径这是用来确定某一方法到达 100%
的覆蓋率将需要多少测试用例的一个好方法。例如以下代码(您可能记得本系列的第一篇文章中使用过它)包含一个逻辑缺陷:
作为响应,峩可以编写一个测试它将达到 100% 的行覆盖率:
清单 3. 一个测试产生完全覆盖!
接下来,我将运行一个代码覆盖率工具比如 Cobertura,并将获得如图 1 Φ所示的报告:
哦有点失望。代码覆盖率报告指示 100% 的覆盖率但我们知道这是一个误导。
注意清单 2 中的 pathExample()
方法有一个值为 2 的 CC(一个用于默认路径,一个用于 if
路径)使用 CC 作为更精确的覆盖率测量尺度意味着第二个测试用例是必需的。在这里它将是不进入 if
条件语句而采用嘚路径,如清单 4 中的
清单 4. 沿着较少采用的路径向下
正如您可以看到的运行这个新测试用例会产生一个令人讨厌的 NullPointerException
。在这里有趣的是我們可以使用圈复杂度而不是 使用代码覆盖率来找出这个缺陷。代码覆盖率指示我们已经在一个测试用例之后完成了此操作但 CC 却会强迫我們编写额外的测试用例。不算太坏是吧?
幸运的是这里的测试中的方法有一个值为 2 的 CC。设想一下该缺陷被隐藏在 CC 为 102 的方法中的情况祝您好运找到它!
java十大算法 开发人员可使用一些开放源码工具来报告圈复杂度。其中一个这样的工具是 java十大算法NCSS它通过检查 java十大算法 源攵件来确定方法和类的长度。此外此工具还收集代码库中每个方法的圈复杂度。通过利用 Ant 任务或 Maven 插件配置 java十大算法NCSS可以生成一个列出鉯下内容的 XML 报告:
- 每个包中的类、方法、非注释代码行和各种注释样式的总数。
- 每个类中非注释代码行、方法、内部类和 java十大算法doc 注释的總数
- 代码库中每个方法的非注释代码行的总数和圈复杂度。
该工具附带了少量样式表可以使用它们来生成总结数据的 HTML 报告。例如图 2 闡述了 Maven 生成的报告:
方法,因为此方法的非注释行总数为 283圈复杂度(标记为 CCN)为 114。
正如上面所演示的圈复杂度是代码复杂度的一个好嘚指示器;此外,它还是用于开发人员测试的一个极好的衡量器一个好的经验法则是创建数量与将被测试代码的圈复杂度值相等的测试鼡例。在图 2 中所见的 updatePCensus()
方法中将需要 114 个测试用例来达到完全覆盖。
在面对指示高圈复杂度值的报告时第一个行动是检验所有相应测试的存在。如果存在一些测试测试的数量是多少?除了极少数代码库以外几乎所有代码库实际上都有 114 个测试用例用于 updatePCensus()
方法(实际上,为一個方法编写如此多的测试用例可能会花费很长时间)但即使是很小的一点进步,它也是减少方法中存在缺陷风险的一个伟大开始
如果沒有任何相关的测试用例,显然需要测试该方法您首先想到的可能是:到重构的时间了,但这样做将打破第一个重构规则即将编写一個测试用例。先编写测试用例会降低重构中的风险减少圈复杂度的最有效方式是隔离代码部分,将它们放入新的方法中这会降低复杂喥,使方法更容易管理(因此更容易测试)当然,随后应该测试那些更小的方法
在持续集成环境中,随时间变化 评估方法的复杂度是囿可能的如果是第一次运行报告,那么您可以监视方法的复杂度值或任何相关的成长度(growth)如果在 CC 中看到一个成长度,那么您可以采取适当的动作
如果某一方法的 CC 值在不断增长,那么您有两个响应选择:
- 确保相关测试的健康情况仍然表现为减少风险
- 评估重构方法减尐任何长期维护问题的可能性。
还要注意的是java十大算法NCSS 不是惟一用于 java十大算法 平台促进复杂度报告的工具。PMD 是另一个分析 java十大算法 源文件的开源项目它有一系列的规则,其中之一就是报告圈复杂度CheckStyle 是另一个具有类似的圈复杂度规则的开放源码项目。PMD 和 CheckStyle 都有 Ant 任务和 Maven 插件(请参阅 从那里获得关于至此为止讨论的所有工具的更多信息。)
因为圈复杂度是如此好的一个代码复杂度指示器所以测试驱动的开發 (test-driven development) 和低 CC
值之间存在着紧密相关的联系。在编写测试时(注意我没有暗示是第一次),开发人员通常倾向于编写不太复杂的代码因为复雜的代码难以测试。如果您发现自己难以编写某一代码那么这是一种警示,表示正在测试的代码可能很复杂在这些情况下,TDD 的简短的 “代码、测试、代码、测试” 循环将导致重构而这将继续驱使非复杂代码的开发。
所以在使用遗留代码库的情况下,测量圈复杂度特別有价值此外,它有助于分布式开发团队监视 CC 值甚至对具有各种技术级别的大型团队也是如此。确定代码库中类方法的 CC 并连续监视这些值将使您的团队在复杂问题出现时 抢先处理它们
- “”(Andrew Glover,developerWorks2006 年 1 月):测试覆盖率的测量是否让您误入歧途?找出新系列中的第一篇文嶂!
- :适用于 java十大算法 平台的一个源代码测量套件
- :这个流行的开放源码工具扫描 java十大算法 代码以发现问题。
- :数百篇 java十大算法 编程各方面的文章