需要帮助减少圈复杂度

时间:2013-10-14 20:15:32

标签: c# solid-principles cyclomatic-complexity

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

当我调用该方法时,一切都按照要求进行操作,但是对于嵌套循环和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 );
        }
    }
}

3 个答案:

答案 0 :(得分:1)

可能我不明白究竟发生了什么,但在我看来,你可以通过删除所有最外面的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可以分成三个独立的,没有嵌套的ifs。

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

}

答案 1 :(得分:0)

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

替换

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

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

使用else - 如果可能的组合较少(路径较少)通过您的方法,至少从圈复杂度分析器sw的视图

更简单的例子:

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

此代码有4个路径,如果将复杂度乘以2,则每个路径都增加一个。 从人类智能来看,这个例子看起来很简单。

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

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

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

答案 2 :(得分:0)

我会做什么涉及方法的细分。你正在尝试做一些事情,所以我们可以将它们分开。将循环条件移动到变量(可读性)。检查边缘情况,首先是空的。将删除移动到自己的方法(可读性/可维护性)。将main(复杂逻辑)移动到其自己的方法(可读性/可维护性)。 UpdateUserDevices现在看起来很干净。

主要方法:

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

Merge Repos 方法:原始方法的肉和土豆现在有自己的方法。发生在这里的事情。已移除workingDevice以添加devicesToUpdate(直接与间接逻辑=&gt;取联合然后对所有元素执行包含与执行交叉一次相同)。现在我们可以有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,但这既不在这里也不在那里)