【问题标题】:Cyclomatic complexity rightfully reduced by using private methods?通过使用私有方法正确地降低了圈复杂度?
【发布时间】:2011-04-18 15:42:42
【问题描述】:

使用私有方法通过将一些决策点重构为单独的方法来降低 CC 会降低实际方法的 CC 并便于阅读,但不会减少在测试中获得完整分支覆盖的工作量。

这合理吗?你有什么现场经验?

【问题讨论】:

    标签: private cyclomatic-complexity


    【解决方案1】:

    如果你仍然感兴趣,我在Cyclomatic Complexity 上写了这篇文章

    以不同的方法剪切代码不会降低复杂性,只会有助于在本地建立更好的组织。真正减少 CC 的唯一方法就是重构!

    如果您只提取方法,您将需要相同数量的测试。看看你的数据结构可能对你没有帮助吗?如果有感觉,也可以考虑削减几个班级。

    【讨论】:

      【解决方案2】:

      有时,降低应用程序代码的复杂性和可读性会导致测试代码变得更加复杂和可读性降低。但是,这不是不进行重构的理由。生产代码的可读性比您的测试更重要。

      如果您将某些方法设为私有以减少 CC 并提高可读性,您可以使用 Mockito 之类的框架来测试私有方法本身。

      【讨论】:

      • 我明白了-但这不是“作弊”吗,因为我实际上并没有降低逻辑的复杂性,只是将其隐藏在其他方法中。好的,这可能很微妙。
      • 这不是作弊。 “提取方法(创建替换某些代码的方法)”是一个有效的重构过程。它并没有真正降低逻辑的复杂性,但确实增加了可读性......
      • 实际上几乎没有办法真正降低复杂性。它主要导致分离关注点,从而隐藏复杂性。完全有效。
      • 顺便说一句:“如果您将某些方法设为私有以降低 CC 并提高可读性”是什么意思?将方法设为私有都不会(为什么要这样做?)。分解方法确实有助于 CC 和可读性,但这不会损害可测试性。相反,如果你很好地解耦它们(使它们成为静态的,将它们放入一个单独的类中),它应该会使测试更容易。
      【解决方案3】:

      根据我的经验,这是一种非常常见的情况 - 让您的生产代码做正确的事情会使测试变得更加困难
      其他示例包括将实现细节隐藏在接口后面,永远不会到达外部调用者的 ORM 实体,使某些功能可用仅通过 Web 服务调用...并且通常在生产环境中拥有您期望的 API 会经常限制测试。

      是的,在进行大量重构后恢复覆盖范围是一件痛苦的事。有时,当难以测试的功能没有正确测试时,这会导致整体进度逆转。所以我一般同意 Fortega,除了最后一句话。不要让你的测试腐烂。他们会在您最不想要的时候回来。

      【讨论】:

        【解决方案4】:

        我真的不明白你的问题。显然,将大方法中的逻辑和决策点重构为私有方法将降低大方法的圈复杂度。这就是提取方法的重点——它使大型方法更短更简单,因此希望更容易理解和更改。

        这不是作弊,它只是让你的程序结构明确。

        但不会减少努力 在测试中获得完整的分支覆盖率。

        这对我来说似乎是不合理的。为什么要分解私有方法使覆盖测试更容易?我从未见过有人这么说。你是不是误会了什么?

        【讨论】:

        • 我提出了覆盖率作为一个实际示例,即整体复杂性(正如 Fortega 和 kostja 所评论的)并没有降低。问题可能是从两个角度来看CC:一个是用户角度告诉较低的CC更容易阅读,另一个是测试人员的角度告诉较低的CC更容易测试(实现覆盖等)。提取到私有方法并不满足第二个,但正如所评论的那样,没有什么能真正降低复杂性,只是重新排列它。只是想澄清这两个方面。
        • @ron:我同意;我无法更好地表达它:-)。
        【解决方案5】:

        您永远不应该重构您的代码,因为人们认为改进指标喷涌而出的数字对您的代码有好处。与其追求数字和花哨的报告(不幸的是,我已经看到这样做了),不如着眼于了解指标以及首先使用它的原因。

        圈复杂度是一种简单的数学度量,它仅说明如果所有分支都已导航,您的代码可以执行多少条不同的路径。所以是的,高圈复杂度确实表明您的测试可能会变得更加复杂。但有时根本没有更简单的方法来编写具有高 CC 和大量测试的代码。你只需要知道什么时候这是一个公平的做法,什么时候你的设计有问题。在旁注中,通过将代码拆分为方法来减少 CC 并不能简化测试任务,因为无论哪种方式都必须覆盖代码。它只是看起来“更漂亮”的数字。

        考虑以下情况:您的任务是设计对按键做出反应的代码,并根据按下的键执行某些任务。该设备仅具有固定数量的按钮,这意味着软件首先需要详尽且不需要可扩展(采用该命令模式)。

        您可以编写一个简单易读的switch 语句,每按下一个按钮就会触发一个方法。这将是快速有效地解决此任务的好方法。但是获得按键的方法的CC将是可怕的。你应该拆分代码吗?为什么?我的意思是它是可读的,完美地服务于它的目的,无论你做什么,你的测试仍然需要考虑每个按钮的按下。所以除了减少这个数字之外,重构没有任何好处。

        我的建议是了解什么时候圈复杂度是一个有意义的指标,什么时候不是。另外,请尝试 Micheal 的 blog post 中的一些重构建议。它有一些可靠的建议。

        【讨论】:

          【解决方案6】:

          假设您正在过一座重量限制为 10,000 磅的桥梁,而您正在驾驶一辆载有 15,000 磅货物的卡车。为了适应限制,您将货物分成三个拖车,每个拖车重 5,000 磅,然后将它们拉到卡车后面。它减轻了卡车的重量 - 从技术上讲 - 但桥上的压力保持不变。

          将代码从大型方法转移到较小的、未经测试的私有方法中是类似的。它使原始方法看起来不那么复杂,并且有一些好处。但是,如果它真的以一种有意义的方式降低了原始方法的复杂性,那么该方法将变得更容易测试。没有。

          对原始方法的任何测试仍必须测试其调用的私有方法中的所有逻辑,就像这些新方法中的所有代码仍在原始方法中一样。如果我们之前无法测试(或者非常困难),那么我们仍然会遇到完全相同的问题。

          如果我们将代码提取到私有方法中作为垫脚石,以将它们与原始方法(甚至可能与原始类)隔离开来,这会有所帮助。我们可以将这些方法移动到新类中,然后将这些类注入到原始类中。或者根据语言,也许我们可以注入方法。

          我们可以做到这一点

          • 使用模拟注入的依赖项测试原始方法。模拟没有复杂性。它每次都做它被告知的事情。现在对原始方法的测试不包括那些私有方法中的所有代码。我们只需要验证它是否按预期与这些依赖项交互。
          • 测试新的类和方法。它们也更小、更简单,这使得它们更容易测试。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-10-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-06-13
            相关资源
            最近更新 更多