需要帮助降低圈复杂度

本文关键字:复杂度 帮助 | 更新日期: 2023-09-27 18:35:15

我有一个名为UpdateUserDevices (UserModel)的方法。UserModel包含一个List<DeviceModel>,该将设备列表与特定用户相关联。(一对多)。

当我调用该方法时,一切都按 excpected 工作,但是嵌套循环和 if 语句非常复杂。

降低圈复杂度的好模式是什么?我想到了"CoR",但这可能有点矫枉过正。

private void UpdateUserDevices( UserModel model )
{
    // Get users current devices from database
    var currentDevicesFromDatabase = _deviceRepository.FindByUserId( model.Id ).ToList();
    // if both the model devices and the datbase devices have records
    // compare them and run creates, deletes, and updates
    if( model.Devices.Any() && currentDevicesFromDatabase.Any() )
    {
        var devicesToAdd = model.Devices.Exclude( currentDevicesFromDatabase, d => d.Id ).ToList();
        var devicesToDelete = currentDevicesFromDatabase.Exclude( model.Devices, d => d.Id ).ToList();
        var workingDevices = model.Devices.Union( currentDevicesFromDatabase );
        foreach( var device in workingDevices )
        {
            // Add devices
            if( devicesToAdd.Contains( device ) )
            {
                _deviceRepository.Create( device );
                continue;
            }
            // delete devices
            if( devicesToDelete.Contains( device ) )
            {
                _deviceRepository.Delete( device );
                continue;
            }
            // update the rest
            _deviceRepository.Update( device );
        }
        return;
    }
    // model.Devices doesn't have any records in it.
    // delete all records from the database
    if( !model.Devices.Any() )
    {
        foreach( var device in currentDevicesFromDatabase )
        {
            _deviceRepository.Delete( device );
        }
    }
    // database doesn't have any records in it
    // create all new records
    if( !currentDevicesFromDatabase.Any() )
    {
        foreach( var device in currentDevicesFromDatabase )
        {
            _deviceRepository.Create( device );
        }
    }
}

需要帮助降低圈复杂度

可能是我不明白到底发生了什么,但在我看来,您可以通过删除所有最外层的 if 语句并只执行最顶层的代码块来简化它。

private void UpdateUserDevices ( UserModel model )
{
    // Get users current devices from database
    var currentDevicesFromDatabase = _deviceRepository.FindByUserId( model.Id );
    var devicesToAdd = model.Devices.Exclude( currentDevicesFromDatabase, d => d.Id ).ToList();
    var devicesToDelete = currentDevicesFromDatabase.Exclude( model.Devices, d => d.Id ).ToList();
    var workingDevices = model.Devices.Union( currentDevicesFromDatabase ).ToList();
    foreach ( var device in workingDevices )
    {
        if ( devicesToAdd.Contains( device ) )
        {
            // Add devices
            _deviceRepository.Create( device );
        }
        else if ( devicesToDelete.Contains( device ) )
        {
            // Delete devices
            _deviceRepository.Delete( device );
        }
        else
        {
            // Update the rest
            _deviceRepository.Update( device );
        }
    }
}

实际上,foreach 可以分成三个独立的,没有嵌套的 if。

private void UpdateUserDevices ( UserModel model )
{
    var currentDevicesFromDatabase = _deviceRepository.FindByUserId( model.Id );
    // Take the current model and remove all items from the database... This leaves us with only records that need to be added.
    var devicesToAdd = model.Devices.Exclude( currentDevicesFromDatabase, d => d.Id ).ToList();
    // Take the database and remove all items from the model... this leaves us with only records that need to be deleted
    var devicesToDelete = currentDevicesFromDatabase.Exclude( model.Devices, d => d.Id ).ToList();
    // Take the current model and remove all of the items that needed to be added... this leaves us with only updateable recoreds
    var devicesToUpdate = model.Devices.Exclude(devicesToAdd, d => d.Id).ToList();
    foreach ( var device in devicesToAdd )
        _deviceRepository.Create( device );
    foreach ( var device in devicesToDelete )
        _deviceRepository.Delete( device );
    foreach ( var device in devicesToUpdate )
        _deviceRepository.Update( device );
}

继续状态是高圈复杂度的原因

取代

if( devicesToAdd.Contains( device ) )
{
   _deviceRepository.Create( device );
   continue;
}
// delete devices
if( devicesToDelete.Contains( device ) )
{
   _deviceRepository.Delete( device );
  continue;
}

与类似的东西

 if( devicesToAdd.Contains( device ) ) {
    _deviceRepository.Create( device );
 } else if( devicesToDelete.Contains( device ) ) {
      // delete devices
    _deviceRepository.Delete( device );
 }

然后你也可以删除继续,这有点代码气味。

否则,如果通过您的方法的组合可能较少(路径较少),至少从圈复杂性分析仪 sw 的角度来看是这样。

一个更简单的例子:

if (debugLevel.equals("1") {
    debugDetail = 1;
}
if (debugLevel.equals("2") {
    debugDetail = 2;
}

此代码有 4 条路径,每条路径都是 if 将复杂度乘以 2。从人类智能来看,这个例子看起来很简单。

对于复杂性分析器来说,这更好:

if (debugLevel.equals("1") {
    debugDetail = 1;
} else if (debugLevel.equals("2") {
    debugDetail = 2;
}

现在只有 2 条可能的路径!你,用人类的智慧看到,这两个条件是相互排斥的,但是复杂性分析器仅在第二个示例中使用 else-if 子句看到这一点。

我要做的是分解方法。您正在尝试做一些事情,以便我们可以将它们分开。将循环条件移动到变量中(可读性)。已检查边缘情况,要么为空,首先。将删除移动到其自己的方法(可读性/可维护性)。将主要(复杂逻辑)移至自己的方法(可读性/可维护性)。更新用户设备现在看起来很干净。

主要方法:

private void UpdateUserDevices( UserModel model )
{
    // Get users current devices from database
    var currentDevicesFromDatabase = _deviceRepository.FindByUserId( model.Id ).ToList();
    $isUserDevicesEmpty = !model.Devices.Any();
    $isRepositoryEmpty = !currentDevicesFromDatabase.Any();
    if($isRepositoryEmpty || $isUserEmpty){
        // Missing One
        if($isRepositoryEmpty){
            this.deleteEmpty(currentDevicesFromDatabase);
        }
        if($isUserDevicesEmpty){
            this.deleteEmpty(model.Devices);
        }
    }
    else{
        this.mergeRepositories(currentDevicesFromDatabase, model);
    }
    return;
}

合并存储库方法:原始方法的肉和土豆现在有自己的方法。这里发生的事情。删除了要添加devicesToUpdate workingDevice(直接与间接逻辑 => 采取联合然后对所有元素执行包含与执行一次相交相同)。现在我们可以有 3 个单独的 foreach 循环来处理更改。(您避免为每个设备做contains,也许是其他效率提升)。

private void mergeRepositories(UserModel model, List currentDevicesFromDatabase)
{
    // if both the model devices and the datbase devices have records
    // compare them and run creates, deletes, and updates
    var devicesToAdd = model.Devices.Exclude( currentDevicesFromDatabase, d => d.Id ).ToList();
    var devicesToDelete = currentDevicesFromDatabase.Exclude( model.Devices, d => d.Id ).ToList();
    var devicesToUpdate = model.Devices.Intersect( currentDevicesFromDatabase, d => d.Id ).ToList();
    foreach( device in devicesToAdd ){
        _deviceRepository.Create( device );
    }
    foreach( device in devicesToDelete ){
        _deviceRepository.Delete( device );
    }
    foreach( device in devicesToUpdate){
        _deviceRepository.Update( device );
    }
    return;
}

删除空方法:此处看不到任何内容

private void deleteEmpty(List devices){
    foreach( var device in devices )
    {
        _deviceRepository.Delete( device );
    }
    return
}

现在它很好,很简单。或者无论如何,我认为是这样。(我不得不做类似的事情来列出Ebay上的物品。我实际写的是一个略有不同的版本,没有在整个集合上使用 Intersect,但这既不在这里也不在那里)