如何重构此功能以将其认知复杂度从17降低到15

时间:2020-07-13 09:11:21

标签: javascript node.js ecmascript-6 sonarqube refactoring

  • 如何重构此功能以降低复杂性
  • 当我当时使用开关盒时,代码复杂度更高,因此如何减少它

    如何做到这一点


var has = Object.prototype.hasOwnProperty



var toString = Object.prototype.toString


function isEmpty(val) {
 
  if (val == null) return true
  if ('boolean' == typeof val) return false
  if ('number' == typeof val) return val === 0
  if ('string' == typeof val) return val.length === 0
  if ('function' == typeof val) return val.length === 0
  if (Array.isArray(val)) return val.length === 0
  if (val instanceof Error) return val.message === ''
  if (val.toString == toString) {
    switch (val.toString()) {
      case '[object File]':
      case '[object Map]':
      case '[object Set]': {
        return val.size === 0
      }
      case '[object Object]': {
        for (var key in val) {
          if (has.call(val, key)) return false
        }

        return true
      }
    }
  }
  return false
}
module.exports = isEmpty

2 个答案:

答案 0 :(得分:0)

如果无法拆分功能或使用OOP方法,则可以使用功能数组并对其进行迭代:

const has = Object.prototype.hasOwnProperty;
const toString = Object.prototype.toString;

function isEmpty(val) {
    let isEmpty = null;

    const checkFunctions = [
        (val) => 'boolean' === typeof val ? false : null,
        (val) => 'number' === typeof val ? val === 0 : null,
        (val) => ['string', 'function'].includes(typeof val) ? val.length === 0 : null,
        (val) => Array.isArray(val) ? val.length === 0 : null,

        (val) => val instanceof Error ? val.message === '' : null,

        (val) => val.toString == toString && ['[object File]', '[object Map]', '[object Set]'].includes(val.toString()) ? val.size === 0 : null,
        (val) => {
            if (val.toString == toString && val.toString() === '[object Object]') {
                for (var key in val) {
                    if (has.call(val, key)) return false
                }
                return true;
            }
        }
    ];

    for (let i = 0; i < checkFunctions.length; i++) {
        isEmpty = checkFunctions[i](val);
        if (isEmpty !== null) {
            return isEmpty;
        };
    }
}

console.log(isEmpty(''), true);
console.log(isEmpty('Hallo'), false);
console.log(isEmpty(0), true);
console.log(isEmpty(1), false);
console.log(isEmpty({}), true);
console.log(isEmpty({a: 1}), false);

您还可以扩展JS的核心类型,然后代替isEmpty(val)编写val.isEmpty()。例如:

String.prototype.isEmpty = function() {return this.length === 0}
Array.prototype.isEmpty = function() {return this.length === 0}

console.log("".isEmpty(), true);
console.log("foo".isEmpty(), false);
console.log([].isEmpty(), true);
console.log([1,2,3].isEmpty(), false);

答案 1 :(得分:0)

我最近回答了一个非常相似的问题,它进一步详细介绍了认知复杂性的工作原理(请参见https://stackoverflow.com/a/62867219/7730554)。

但是,总的来说,我认为了解如果存在嵌套条件会进一步增加认知复杂性,这一点很重要。之所以进行这种计算,是因为人脑可以更好地处理按顺序编写的语句,而不是嵌套条件。因此,对于每个条件语句(if,switch,for循环等),将在+1上添加复杂度值。但是,对于每个嵌套条件,在最后一级之上会再加上+1。这意味着,在if中的if不仅会加+1,而且还会加+2。如果在if中,在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
}

如果您查看我添加的注释,则可以轻松地看到关于圈复杂度最成问题的代码所在的位置。这也与代码的人工可读性有关。

因此,提高可读性并同时降低认知复杂度的一个简单步骤就是寻找“ 早期回报 ”。

为了说明这一点,我简单地颠倒了* if(val.toString == toString)“语句,如果* val.toString!= toString”立即返回false:

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语句之外执行,并且我们将嵌套级别降低了一个。通过这种简单的更改,认知复杂性现在已经从17降到了14

您甚至可以更进一步,通过将返回值提取到变量中来更改最后一个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目前不是我的主要语言,因此我不能完全保证最后一个重构步骤的正确性。