在不影响业务逻辑的情况下降低Cyclomatic Complexity

时间:2015-08-28 05:33:37

标签: c# cyclomatic-complexity

考虑这种方法:

public ActionResult DoSomeAction(ViewModel viewModel)
{
    try
    {
        if (!CheckCondition1(viewModel))
            return Json(new {result = "Can not process"});

        if (CheckCondition2(viewModel))
        {
            return Json(new { result = false, moreInfo = "Some info" });
        }

        var studentObject = _helper.GetStudent(viewModel, false);

        if (viewModel.ViewType == UpdateType.AllowAll)
        {
            studentObject = _helper.ReturnstudentObject(viewModel, false);
        }
        else
        {
            studentObject.CourseType = ALLOW_ALL;
            studentObject.StartDate = DateTime.UtcNow.ToShortDateString();
        }

        if (studentObject.CourseType == ALLOW_UPDATES)
        {
            var schedule = GetSchedules();

            if (schedule == null || !schedule.Any())
            {
                return Json(new { result = NO_SCHEDULES });
            }

            _manager.AddSchedule(schedule);
        }
        else
        {
            _manager.AllowAllCourses(studentObject.Id);
        }

        _manager.Upsert(studentObject);

        return Json(new { result = true });
    }
    catch (Exception e)
    {
        // logging code
    }
}

此方法有许多退出点,并且具有 8 的Cyclomatic Complexity。 我们的最佳做法是说它不应该大于 5

  • 是因为有多个IF吗?

  • 我可以做些什么来重构这个,以便它有更少的退出点?

提前致谢。

2 个答案:

答案 0 :(得分:1)

这是我在上述问题下的评论摘要

  

我们的最佳实践表明它不应该大于5。

“5”听起来有点低。 nDepend和Microsoft分别推荐“30”和“25”。

NDepend的:

  

CC高于15的方法难以理解和维护。 CC高于30的方法非常复杂,应分成较小的方法(除非它们是由工具自动生成的)

微软:

  

圈复杂度=边数 - 节点数+ 1   其中节点表示逻辑分支点,边表示节点之间的线。   当圈复杂度超过25时,规则会报告违规行为。

OP:

  

“是因为多个IF” -

是和||

  

我可以做些什么来重构这个,以便它有更少的退出点?

一种简单的方法就是采用方法和split into smaller methods。即,而不是在一种方法中的所有if s,将一些if逻辑移动到一个或多个方法中,每个方法都调用另一个。

e.g。

class Class1
{
    class  Hobbit
    {

    }

    void Foo(object person)
    {
        if (...)
        {
                // ...

        }
        else if (...)
        {
                // ...
        }

        if (x == 1 && person is Hobbit)
        {
            if (...)
            {
                // ...
            }

            if (...)
            {
                // ...
            }

            if (...)
            {
                // ...
            }

        }
    }
}

可以通过将最里面的if组移动到单独的方法中来改进:

    void Foo(object person)
    {
        if (...)
        {
                // ...

        }
        else if (...)
        {
                // ...
        }

        if (x == 1 && person is Hobbit)
        {
            DoHobbitStuff();
        }
    }

    void DoHobbitStuff()
    {
        if (...)
        {
            // ...
        }

        if (...)
        {
            // ...
        }

        if (...)
        {
            // ...
        }
    }

然而,在“5”时,我不相信你的代码需要重构以达到减少CC的目的。

  

我可以做些什么来重构这个,以便它有更少的退出点?

根据nDependreturn等退出点不计算在内:

  

以下表达式不计入CC计算:

     

其他|做|开关|试试|使用|扔|终于|返回|对象创建|方法调用|现场访问

答案 1 :(得分:0)

看一下你的代码,很明显你的高圈复杂度和难以重构的方法都是糟糕设计的指标(例如代码味道)。让我们回顾一下。

_helper
_manager

这些东西是什么?为什么他们有这么模糊的名字?如果你找不到任何其他合适的名称,那就意味着你的担忧分离是错误的。

_helper.GetStudent(viewModel, false);
_helper.ReturnstudentObject(viewModel, false);

我甚至无法想象这些方法如何运作。一些通用帮助器如何知道如何从通用ViewModel获取“学生”?这两种方法有什么区别?

    var studentObject = _helper.GetStudent(viewModel, false);

    if (viewModel.ViewType == UpdateType.AllowAll)
    {
        studentObject = _helper.ReturnstudentObject(viewModel, false);
    }
    else
    {
        studentObject.CourseType = ALLOW_ALL;
        studentObject.StartDate = DateTime.UtcNow.ToShortDateString();
    }

这看起来好像应该是ViewModel的一部分。您正在根据ViewModel的内部状态做出决策,只允许ViewModel执行此操作。

_manager.Upsert(studentObject);

那是“UpdateOrInsert”吗?这是一些奇怪的命名惯例。

让我感到困惑的另一件事是你似乎在使用类似MVC的Web调用实现,但是你正在使用ViewModels。这怎么样?我总是将ViewModels与UI绑在一起,而不是与web绑定。