手写代码时,我们往往会遵照一些比较成熟的代码指标标准来约束代码(比如:圈复杂度,参数个数,嵌套层次等),以提高质量(可参见本公众号文章:谈代码指标)。在模型开发时,是否有对应的约束呢?分享一篇国内模型开发大佬老胡的文章:
多年来,一直建议软件开发的时候做到KISS(Keep It Simple, Stupid),但好像多数工程师对KISS没啥兴趣,经常做出来非常复杂的模型,圈复杂度300多的模型我见过,圈复杂度600多的模型我也见过,圈复杂度800多的模型我还见过。这篇公众号文章我有意选择了一个或许能激发KISS愿望的图片,期待能给大家带来一点改变。
不得不说,ISO 26262的发布,让越来越多的工程师开始关注模型的圈复杂度了,这是好事,利国利民的好事!ISO 26262中有关代码和建模标准中有一条,叫做“Enforcement of low complexity”。正是这条规则让汽车行业的模型开发工程师们开始关注圈复杂度这个指标了。
于是,我们开始被问到:
——怎么计算模型的圈复杂度?
——模型的圈复杂度多大合适?
——如何降低模型的圈复杂度?等等。
好,下面我来大概说说这些问题。
圈复杂度的概念
圈复杂度用于衡量一个模块判定结构的复杂程度,数量上表现为独立线性路径的条数。圈复杂度的数据等于满足判定全覆盖所需要的测试用例的个数。
以上面这段控制流图为例,有一种非常简单的计算圈复杂度的方法,就是数出图中节点间的连线把整个平面划分为多少个区域,这段控制流图我们数下来整个平面被分成了7个部分,那么它的圈复杂度就是7。
我们再看,如果我们在测试的时候,要做到判定全覆盖,则需要如下测试用例:
1) N1-N2-N3-N6-N9
2) N1-N2-N5-N6-N9
3) N1-N2-N5-N8-N9
4) N1-N4-N5-N6-N9
或者N1-N4-N5-N8-N9
5) N1-N4-N8-N9
6) N1-N4-N7-N8-N9
7) N1-N7-N8-N9
注:假设1号节点用N1表示,2号节点用N2表示,….。
以上7个测试用例,可以满足圈复杂度为7的模块判定全覆盖。
圈复杂度对开发活动的影响
复杂度对于开发活动的影响是多方面的,我试着来说一下看,有不完善的地方,也希望朋友们补充。
首先看可测性,圈复杂度提出的初衷主要是为了评价软件的可测性。从上述图例我们也可以看到,如果我们在测试过程中要求做到判定覆盖率100%覆盖,那么我们需要的测试用例个数就是圈复杂度的数值。如果圈复杂度过高,无疑会给单元测试带来很大麻烦,且不说写这么多测试用例本身就是一件非常繁杂的事情,这么多的测试用例对于后期的维护和管理也非常困难。
再看设计难度,高复杂度模块的可设计性也会大打折扣,但有一点,算法的不同描述方式,我们能够应对的复杂程度应该是不同的。比如,同样是复杂度为50的模块,如果分别使用汇编语言、C语言、图形化语言来描述,开发难度可想而知。图形化设计可以应对更高的复杂度,但不是说可以无限制的提高复杂度,至于什么样的复杂度合适,我认为这是一个经验值,适合你开发团队的才是最好的。需要强调一点,选择不同的描述形式,对于模块单元测试的可测性没有改变。
再就是高复杂度对于可维护性的影响,我们开发产品,不可能做到“一次发布再无变更”,更多情况下,我们会面临非常频繁的变更,过于复杂的模型,我们很难应对各种变更,“牵一发而动全身”的尴尬可能很多人已经深有体会。
高复杂度的这些害处不难理解,如何降低复杂度这是越来越多的开发者正在关心的问题,降低复杂系统的复杂度没有捷径,模块化,将复杂的系统分解为若干个模块,保证分解后的模块都不复杂即可,而模块化的原则我们都非常熟悉:高内聚、低耦合。
方法和原则非常简单,但实施这个过程却并不简单,这需要基于足够深入的需求分析,这需要足够长的时间去完成,而目前很多公司的开发过程中,需求分析过程花费的时间实在太少。没有足够深入的需求分析,就难以对复杂系统做出合理的划分,后续的工作只会越来越麻烦,甚至有公司因为模型过于复杂而被迫重新设计。