简体   繁体   English

需要帮助降低圈复杂度

[英]Need help reducing cyclomatic complexity

I've got a method called UpdateUserDevices (UserModel) . 我有一个名为UpdateUserDevices (UserModel) The UserModel contains a List<DeviceModel> which associates a list of devices with a specific user. UserModel包含一个List<DeviceModel> ,该列表将设备列表与特定用户相关联。 (One-to-many). (一对多)。

When I call the method, everything is working as excpected, however it's quite complex with nested loops and if statements. 当我调用该方法时,一切都按预期运行,但是使用嵌套循环和if语句则相当复杂。

What would be a good pattern to reduce the cyclomatic complexity on this? 有什么好的方法可以减少这种方法的圈复杂度? I thought about "CoR" but that might be overkill. 我曾考虑过“ 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 );
        }
    }
}

It might be that I don't understand exactly what happens, but it seems to me like you could simplify it a lot by removing all your outermost if statements and just performing the topmost codeblock. 可能是我不完全了解会发生什么,但是在我看来,您可以通过删除所有最外面的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 );
        }
    }

}

Actually the foreach could be split into three separate with no nested ifs. 实际上,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 );

}

The continue statemant is the cause of the high cyclomatic complexity 持续状态是圈复杂度高的原因

Replace 更换

if( devicesToAdd.Contains( device ) )
{
   _deviceRepository.Create( device );
   continue;
}

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

with something similar to 类似于

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

and then you also could remove the continues, which are a bit of a code smell. 然后您也可以删除继续,这有点代码味道。

With else-if there are less combinations possible (less paths) to go through your method, at least from the view of the cyclomatic complexity analyser sw. 否则,至少从循环复杂度分析仪sw的角度来看,如果有更少的组合(更少的路径)可以通过您的方法。

A simpler example: 一个简单的例子:

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

This code has 4 paths, each addional if multiplies the complexity by 2. From human intelligence this example looks simple. 这段代码有4条路径,每条路径都将复杂度乘以2。从人类的智慧来看,此示例很简单。

For the complexity analyser this is better: 对于复杂性分析器,这更好:

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

This now has only 2 possible paths! 现在只有2条可能的路径! You, using human intelligence see, that both conditions are mutually exclusive, but the complexity analyser sees that only in the second example using the else-if clause. 您使用人类智能看到,这两个条件是互斥的,但是复杂性分析器仅在使用else-if子句的第二个示例中看到了这两个条件。

What I would do involves a breakdown of the method. 我要做的事情涉及该方法的分解。 You are trying to do a few things so we can separate them out. 您正在尝试做一些事情,以便我们将它们分开。 Moved loop conditionals into variables (readability). 将循环条件移动到变量中(可读性)。 Checked Edge case, of either empty, first. 首先检查Edge大小写为空。 Moved the delete to its own method (readability/maintainability). 将删除操作移至其自己的方法(可读性/可维护性)。 Moved main (complex logic) to its own method (readability/maintainability). 将main(复杂逻辑)移至其自己的方法(可读性/可维护性)。 The UpdateUserDevices now looks clean. 现在,UpdateUserDevices看起来很干净。

Main Method: 主要方法:

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 Method: The meat and potatoes of the original method now has its own method. 合并回购方法:原来方法的肉和土豆现在有自己的方法。 Things that happened here. 这里发生的事情。 Removed workingDevice to add in devicesToUpdate ( Direct vs Indirect logic => taking the union then doing contains for all elements is the same as doing intersect once). 删除了workingDevice并添加到devicesToUpdate (直接与间接逻辑=>进行并集然后对所有元素进行包含与一次相交相同)。 Now we can have 3 separate foreach loops to process the changes. 现在,我们可以有3个单独的foreach循环来处理更改。 ( You are avoiding doing the contains for every device, maybe other efficiency gains). (你避免做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;
}

Delete Empty method: Nothing to see here 删除空方法:什么也没看到

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

    return
}

Now its nice and simple. 现在,它很简单。 Or so I think anyway. 还是我想无论如何。 ( I had to do something similar for listing Items on Ebay. What I actually wrote was a slightly different version without using Intersect on the whole set, but that's neither here nor there ) (我必须做类似的事情才能在Ebay上列出Items。我实际上写的是一个略有不同的版本,没有在整个集合上使用Intersect,但这既不是这里也不是那里)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM