重构此方法可将其认知复杂度从21降低到允许的15。如何重构并降低复杂性

时间:2020-07-09 13:15:12

标签: angular unit-testing sonarqube karma-jasmine sonarlint

如何降低给定代码段的复杂性?我在Sonarqube中遇到了这个错误--->重构此方法,将其认知复杂度从21降低到允许的15。

this.deviceDetails = this.data && {...this.data.deviceInfo} || {};
    if (this.data && this.data.deviceInfo) {
      this.getSessionInfo();
      // tslint:disable-next-line: no-shadowed-variable
      const { device, driver, ipAddress, port, active, connectionType } = this.data.deviceInfo;
      this.deviceDetails = {
        name: device.name || '',
        manufacturer: device.manufacturer || '',
        deviceType: device.deviceType || '',
        model: device.model || '',
        description: device.description || '',
        managerId: device.deviceManager && device.deviceManager.managerId || null,
        locationId: device.location && device.location.locationId || null,
        active: device.active,
        connectionType: connectionType || null,
        driver_id: driver && driver.driverId || null,
        ipAddress: ipAddress || '',
        port: String(port) || '',
        connectionStatus: active,
      };
      this.oldDeviceDetails = {...this.deviceDetails};
      this.deviceLocation = device.location && device.location.locationId || null;
    } else {

3 个答案:

答案 0 :(得分:2)

关于圈复杂度如何工作以及为什么应将其保持在较低水平的一些信息

首先,重要的是要了解“ 认知复杂性”与“ 环复杂性”相比如何工作。认知复杂性考虑了人脑感知的复杂性。这就是为什么它不能简单地指出条件路径的数量(简化了条件数量,而return语句则加1)。

另一方面,循环复杂度考虑了嵌套条件(例如,if语句内部的if),这使得阅读和理解代码更加困难从人类的角度来看。

SonarQube文档(https://www.sonarsource.com/docs/CognitiveComplexity.pdf)中的以下示例概述了我要解释的内容:

if (someVariableX > 3) { // +1
    if (someVariableY < 3) { // +2, nesting = 1
        if (someVariableZ === 8) { // +3, nesting = 2
            someResult = someVariableX + someVariableY - someVariableZ;
        }
    }
}

因此请注意,二进制操作会增加复杂性,但嵌套条件会为每个嵌套条件加1分。这里的认知复杂度为6,而循环复杂度仅为4(每个条件一个,返回路径一个);

例如,如果您使代码对人类更易读,通过从包含条件的行中提取方法,您可以同时实现更好的可读性和更少的循环复杂性。

尽管您提供的代码没有嵌套条件,但我认为重要的是首先了解圈复杂度计算的工作原理,以及为什么将其保持在较低水平是个好主意。

[TL; DR]一种将代码重构为不太复杂且可读性更好的版本的可能方法

让我们首先看一下注释所概述的代码的复杂度计算是如何完成的:

if (this.data && this.data.deviceInfo) { // +1 for the if conditaionl, +1 for the binary operator
    this.getSessionInfo();

    const { device, driver, ipAddress, port, active, connectionType } =             
    this.data.deviceInfo;
    this.deviceDetails = {
        name: device.name || '', // +1 for the binary operator
        manufacturer: device.manufacturer || '', // +1 for the binary operator
        deviceType: device.deviceType || '', // +1 for the binary operator
        model: device.model || '', // +1 for the binary operator
        description: device.description || '', // +1 for the binary operator
        managerId: device.deviceManager && device.deviceManager.managerId || null, // +2 for the varying binary operators
        locationId: device.location && device.location.locationId || null, // +2 for the varying binary operator
        active: device.active,
        connectionType: connectionType || null, // +1 for the binary operator
        driver_id: driver && driver.driverId || null, // +2 for the varying binary operator
        ipAddress: ipAddress || '', // +1 for the binary operator
        port: String(port) || '', // +1 for the binary operator
        connectionStatus: active,
    };
    this.oldDeviceDetails = { ...this.deviceDetails };
    this.deviceLocation = device.location && device.location.locationId || null; // +2 for the varying binary operator
} else { // +1 for the else path 
    // some statement
}

这可能是代码的重构版本(通过快速手动计数而不进行真正的SonarQube分析)可以将认知复杂度降低到12。(请注意,这只是手动计算。)

可以通过应用简单的重构(例如提取方法和/或移动方法)来完成此操作(另请参见Martin Fowler,https://refactoring.com/catalog/extractFunction.html)。

this.deviceDetails = this.data && { ...this.data.deviceInfo } || {}; // +2
if (deviceInfoAvailable()) { // +1 for the if statement
    this.getSessionInfo();
    // tslint:disable-next-line: no-shadowed-variable
    const { device, driver, ipAddress, port, active, connectionType } = this.data.deviceInfo;
    this.deviceDetails = {
        name: getInfoItem(device.name), 
        manufacturer: getInfoItem(device.manufacturer),
        deviceType: getInfoItem(device.deviceType),
        model: getInfoItem(device.model),
        description: getInfoItem(device.description), 
        managerId: getManagerId(device),
        locationId: getDeviceLocation(device),
        active: device.active,
        connectionType: getInfoItem(connectionType), 
        driver_id: getDriverId(driver), 
        ipAddress: getInfoItem(ipAddress), 
        port: getInfoItem(port), 
        connectionStatus: active,
    };
    this.oldDeviceDetails = { ...this.deviceDetails };
    this.deviceLocation = getDeviceLocation(device);
} else { // +1 for the else
    // some statement
}

function getDriverId(driver) {
    return driver && driver.driverId || null; // +2 for the binary operators
}

function getDeviceLocation(device) {
    return device.location && device.location.locationId || null; // +2 for the binary operators
}

function getManagerId(device) {
    return device.deviceManager && device.deviceManager.managerId || null; // +2 for the binary operators
}

function deviceInfoAvailable() {
    return this.data && this.data.deviceInfo; // +1 for the binary operator
}

function getInfoItem(infoItem) {
    return infoItem || ''; // +1 for the binary operator
}

通过简单的提取方法重构大量重复(请参见getInfoItem()函数)消除了,这使降低复杂性和增加复杂性变得容易可读性

说实话,我什至会更进一步,甚至重新组织代码,以便在提供设备详细信息时检查空项目和设置默认值(此处为空字符串)的逻辑由设备完成类或设备详细信息类本身,以具有更好的数据凝聚力以及对该数据进行操作的逻辑。但是由于我不知道其余的代码,因此这种初始重构应该使您更进一步,以提高可读性和降低复杂性。

答案 1 :(得分:0)

所有||只是加起来,这似乎是一个坏习惯。您可以将this.deviceDetails = {...转换为其自己的映射功能,以快速解决问题。

答案 2 :(得分:0)

如果您使用的是打字稿3.7或更高版本,则可以使用可选链接来简化某些条件。

  • device.deviceManager && device.deviceManager.managerId ||空

将成为

  • device.deviceManager?.managerId ||空