解决圈复杂性和感知复杂性

Resolving Cyclomatic and Perceived Complexity

我的测试应用程序中有以下方法:

def on(definition, visit = false, &block)
  if @page.is_a?(definition)
    block.call @page if block
    return @page
  end

  if @context.is_a?(definition)
    block.call @context if block
    @page = @context unless @page.is_a?(definition)
    return @context
  end

  @page = definition.new(@browser)
  @page.view if visit

  @page.correct_url? if @page.respond_to?(:url_matches)
  @page.correct_title? if @page.respond_to?(:title_is)

  @model = @page

  block.call @page if block

  @page
end

当我 运行 rubocop 工具针对包含此方法的文件时,我得到以下响应:

C: Cyclomatic complexity for on is too high. [10/6]
C: Perceived complexity for on is too high. [10/7]

我不明白它认为的是什么 "too complex" 因此我无法弄清楚如何解决这个问题。理想情况下,我宁愿不只是告诉 rubocop 避免警告,因为它无疑告诉我一些有用的东西。

至于方法的复杂性,如您所见,我有几个 if 调用,然后我必须使用 @page object 才能确保它设置正确。 (这个例子,在上下文中,是 Watir-WebDriver object。)

我同意该方法很复杂,因为它需要检查 @page 是否已经存在并设置为某些内容,以及检查 @page 是否应与 [= 相同18=]。但是 - 同样,我不确定该怎么做。

此方法所在模块的完整代码在这里:

https://github.com/jnyman/symbiont/blob/master/lib/symbiont/factory.rb

我最初认为我可以将其分解为不同的方法调用,这可能会降低每个方法的复杂性。但这意味着阅读我的代码的人必须跳到一系列不同的方法才能理解 on 在做什么。在我看来,仅仅移动事物并不能从整体上消除复杂性;相反,它只是在洗牌。还是我错了?

如有任何建议,我们将不胜感激。

更新代码

我已经减少了一些。这是我现在拥有的:

def on(definition, visit = false, &block)
  if @page.is_a?(definition)
    block.call @page if block
    return @page
  end

  if @context.is_a?(definition)
    block.call @context if block
    @page = @context
    return @context
  end

  @page = definition.new(@browser)
  @page.view if visit

  @model = @page

  block.call @page if block

  @page
end

根据反馈,我删除了一个似乎没有用的 unless 限定符。我还删除了两行,我发现我可以在其他地方更好地使用它们(检查标题和 url)。

这完全删除了 "perceived complexity",只剩下这个:

C: Cyclomatic complexity for on is too high. [7/6]

我似乎 "one point"(或任何术语)太复杂了。

有些地方你的代码是多余的。例如,你有这个:

if @page.is_a?(definition)
  ...
  return ...
end

这意味着,在这之后的任何部分,你都可以假设@page.is_a?(definition)不是true,除非你在这部分之后修改@page。尽管如此,你有:

if @context.is_a?(definition)
  ... unless @page.is_a?(definition)
end