我最近回答了一个非常相似的问题,更详细地介绍了认知复杂性的工作原理(请参阅https://stackoverflow.com/a/62867219/7730554)。
但总的来说,我认为了解如果存在嵌套条件,认知复杂性会进一步增加这一点很重要。这种计算是这样完成的,因为人脑可以更好地处理按顺序编写的语句而不是嵌套条件。因此,对于每个条件语句(if、switch、for 循环等),复杂度值都会增加 +1。但是对于每个嵌套条件,在最后一级的顶部添加另一个 +1。这意味着,if 中的 if 不仅会添加 +1,还会添加 +2。如果,在 if 内,在 if 内将导致第一个 if 条件 +1,第二个 if 条件 +2 和第三个 if 条件 +3。如果您想更深入地了解这一点,我建议您查看:https://www.sonarsource.com/docs/CognitiveComplexity.pdf
让我们首先分析一下您的方法中高复杂度值的来源:
function isEmpty(val) {
if (val == null) return true // +1
if ('boolean' == typeof val) return false // +1
if ('number' == typeof val) return val === 0 // +1
if ('string' == typeof val) return val.length === 0 // +1
if ('function' == typeof val) return val.length === 0 // +1
if (Array.isArray(val)) return val.length === 0 // +1
if (val instanceof Error) return val.message === '' // +1
if (val.toString == toString) { // +1
switch (val.toString()) { // +2
case '[object File]':
case '[object Map]':
case '[object Set]': {
return val.size === 0
}
case '[object Object]': {
for (var key in val) { // +3
if (has.call(val, key)) return false // +4
}
return true
}
}
}
return false
}
如果您查看我添加的 cmets,您可以很容易地看到与圈复杂度相关的最有问题的代码所在的位置。这也与代码的人类可读性有关。
因此,提高可读性同时降低认知复杂性的一个简单步骤是寻找“早期回报的选项>”。
为了说明这一点,我简单地反转语句 *if (val.toString == toString)" 以立即返回 false if *val.toString != toString":
function isEmpty(val) {
if (val == null) return true // +1
if ('boolean' == typeof val) return false // +1
if ('number' == typeof val) return val === 0 // +1
if ('string' == typeof val) return val.length === 0 // +1
if ('function' == typeof val) return val.length === 0 // +1
if (Array.isArray(val)) return val.length === 0 // +1
if (val instanceof Error) return val.message === '' // +1
if (val.toString != toString) { // +1
return false;
}
switch (val.toString()) { // +1
case '[object File]':
case '[object Map]':
case '[object Set]': {
return val.size === 0
}
case '[object Object]': {
for (var key in val) { // +2
if (has.call(val, key)) return false // +3
}
return true
}
}
}
现在最后一个 switch 语句可以在 if 语句之外执行,并且我们将嵌套级别减少了一层。通过这个简单的改变,认知复杂性现在已降至 14 而不是 17。
您甚至可以更进一步,通过将返回值提取到变量中来更改最后一个 case 语句,或者从代码块中提取一个单独的方法。这将进一步降低 isEmpty() 方法的复杂性。
除了提取方法之外,您还可以使用声明性方法并使用,例如数组方法find(),这将进一步降低认知复杂性。
为了说明我所做的两个想法:
function isEmpty(val) {
if (val == null) return true // +1
if ('boolean' == typeof val) return false // +1
if ('number' == typeof val) return val === 0 // +1
if ('string' == typeof val) return val.length === 0 // +1
if ('function' == typeof val) return val.length === 0 // +1
if (Array.isArray(val)) return val.length === 0 // +1
if (val instanceof Error) return val.message === '' // +1
if (val.toString != toString) { // +1
return false;
}
return checkForComplexTypes(val)
}
function checkForComplexTypes(val) {
var result = null
switch (val.toString()) { // +1
case '[object File]':
case '[object Map]':
case '[object Set]': {
result = val.size === 0
}
case '[object Object]': {
result = Object.keys(val).find(key => has.call(val, key))
}
return result
}
}
这应该将isEmpty()方法的认知复杂度降低到8和整个代码包括提取的 checkForComplexTypes() 函数复杂度得分为 9。
注意:JavaScript 目前不是我的主要语言,所以我不能完全保证最后一个重构步骤的正确性。