【问题标题】:How to refactor this function to reduce its Cognitive Complexity from 17 to the 15 allowed如何重构此函数以将其认知复杂度从 17 降低到允许的 15
【发布时间】:2020-11-02 11:07:50
【问题描述】:
  • 如何重构此函数以降低复杂性
  • 当我当时使用 switch case 时,代码复杂度更高,如何减少它

    如何做到这一点


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

【问题讨论】:

  • if ('function' == typeof val) return val.length === 0?不带参数的函数如何“空”?
  • if (val instanceof Error) return val.message === '' 类似,我猜 - 如果错误没有消息,我不会认为它 empty这是一个错误这一事实本身就传达了意图和信息。此外,您可以有不同的错误子类型来传达信息,例如,如果您收到几乎不需要消息的DivisionByZeroError
  • if (val.toString == toString) 这个检查似乎是错误的。至少因为您可以只使用instanceof 来检查 Map、Set 和 File。

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


【解决方案1】:

如果您无法拆分函数或使用 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 的核心类型,然后用 val.isEmpty() 代替 isEmpty(val)。例如:

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);

【讨论】:

    【解决方案2】:

    我最近回答了一个非常相似的问题,更详细地介绍了认知复杂性的工作原理(请参阅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 目前不是我的主要语言,所以我不能完全保证最后一个重构步骤的正确性。

    【讨论】:

      猜你喜欢
      • 2021-09-11
      • 2021-07-01
      • 2018-06-08
      • 2021-03-14
      • 2021-10-31
      • 1970-01-01
      • 2019-03-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多