圈复杂度那些事儿-前端代码质量系列文章(二)

5,353 阅读5分钟

Are You Smart Enough To Debug Your Own Code?


Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.


上面这句话的意思是:调试的难度相当于写代码的两倍。因此,如果你写出了你自认为最聪明巧妙的代码,那么你将不具备拥有足够的智商去调试它。根据这段话的意思,我们不应该去写出“自作聪明”的代码,否则的话我们自己都没有能力维护。应该写出浅显易懂,刚毕业的实习生都能看懂的代码才是好代码。


01 圈复杂度定义

怎么才能写出浅显易懂的代码就不得不提软件工程质量度量方法中一个重要的概念——圈复杂度。

圈复杂度(Cyclomatic complexity,简写CC)也称为条件复杂度,是一种代码复杂度的衡量标准。由托马斯·J·麦凯布(Thomas J. McCabe, Sr.)于1976年提出,用来表示程序的复杂度。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。

圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和维护。有研究表明复杂度和出现缺陷的数量存在强相关性,表明了越复杂的代码越可能会出错。


02 圈复杂度计算

圈复杂度衡量的是程序中线性独立路径的数量。

例如:如果程序中不包含控制、判断、条件语句,那么复杂度就是 1,因为整个程序只有一条执行路径;

如果程序包含一条 IF 语句,那么就会有两条路径来执行完整个程序,所以这时候的复杂度就是 2;

两个嵌套的 IF 语句,或者包含两个判断条件的一个 IF 语句,复杂度就是 2 * 2 = 4。

更加具体的情况看下图:


圈复杂度的计算方法


  1. 圈复杂度可以通过程序控制流图计算,公式为:V(G) = e + 2 - n

  e : 控制流图中边的数量

  n : 控制流图中节点的数量

  1. 圈复杂度对应程序控制流图中从起点到所有终点的路径的条数,所以也可以通过数路径的方式获得圈复杂度。


03 降低复杂度


那么问题来了,我们如何降低代码复杂度呢?如果你想写出易读且可测试的代码,可以使用能够有效降低代码复杂度的利器——函数式编程。

我们已经了解了圈复杂度指的是一段程序执行分支的可能性数量。而函数式编程就是尽量避免使用所有循环语句这种会增加代码执行分支的语句。函数式编程是一种指导我们如何编写程序的方法论,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

一种比较极端的观点任务数学是物理世界的根基,一切问题都是数学问题,世间万物都可以用数学函数去表示。电脑的运算也可以视为数学上的函数计算,所以我们的代码其实也都可以归纳化简成为一系列数学表达式。

函数式编程有要求只用"表达式",不用面向过程的"语句"。表达式和语句的区别是表达式总有一个返回值,而语句表示执行了某种操作。只使用表达式的函数我们可以成为“纯函数”,纯函数的好处是不论函数被执行多少次,都不会产出任何的副作用,因为它不会去修改中间状态。

由于我们使用函数式编程,我们就可以用表达式去代替各种条件判断语句,从而降低代码的圈复杂度,使我们的代码可读易测试。在函数式编程语言里没有 for 循环,因为这些逻辑意味着有状态的改变。相替代的是,这种循环逻辑在函数式编程语言里是通过递归、把函数当成参数传递的方式,也就是高阶函数实现的。

JavaScript虽然在设计的时候不是函数式语言,但是我们可以通过 lodash,underscore等方法库来让我们的程序更加函数式。函数式编程是我最为推崇的有效降低代码复杂度的编程方法。但是具体关于函数式编程的内容不属于本篇的范畴,在这里不再展开阐述。

除了函数式编程,我们还能使用一些更为传统的方式来降低代码复杂度。常见的方法有:

  • 提取函数 - 将独立业务或模块代码独立出来,封装提炼为函数,并通过函数名诠释代码作用,提高代码可读性。当一个函数过于复杂时就应该拆分为多个负责独立功能的子函数。

  • 替换算法 - 复杂算法会导致bug可能性的增加及可理解性/可维护性。

  • 合并条件式 - 把复杂的条件表达式,使用函数进行封装。举个例子:

if(data.code === 200 && data.id && data.list.length > 2) {
    db.insert(data);
} 

我们可以将条件表达式封装为函数:

if(isValidate(data)) {
    db.insert(data);
}


通过以上这些方法我们可以使自己的代码保持一个较低的代码复杂度。在我们写代码的时候我们记住要让自己的代码 Stay Simple Stay Foolish 。一个有助于你记住这个原则的办法是“写程序时时刻记着,这个将来要维护你写的程序的人是一个有严重暴力倾向,并且知道你住在哪里的精神变态者”。




关注微信公众号查看更多原创内容