重构长和太多分支方法Rubocop

时间:2015-07-03 19:19:43

标签: ruby rubocop

我在下面提供了一些调查结果......

这是我在当前代码中使用的方法:

def query(data_set, conditions)
  query_data_set = data_set.dup
  search_conditions = parse_conditions(conditions)

  search_conditions.each do |condition|
    if condition.class == Array
      condition.each do |term|
        if term.class == Symbol
          query_data_set = entity.send term, query_data_set
        else
          query_data_set = search_data_text(query_data_set, term)
        end
      end
    end

    if condition.class == Hash
      condition.each do |key, value|
        query_data_set = method("query_#{key}").call(data_set, value)
      end
    end
  end

  query_data_set
end

Rubocop讨厌这个有三个原因:

C: Assignment Branch Condition size for query is too high. [17.03/15]
  def query(data_set, conditions)

C: Method has too many lines. [19/7]
  def query(data_set, conditions)

C: Use next to skip iteration.
  search_conditions.each do |condition|

我不想跳过迭代,所以我不确定为什么要使用next。从来没有跳过迭代对这段代码有意义的情况。

所以转到其他投诉,你会看到这个方法的顶部我已经打破了一个动作(对parse_conditions的调用)。还有search_data_text的电话。我唯一的观点是,我试图采用模块化的方式,似乎有意义。

即使我将那个大的search_conditions.each块移动到一个单独的方法,Rubocop也会抱怨我的新方法太长了。我想这意味着添加我的第二个方法将调用的第三个​​方法?这对我来说似乎很奇怪。或许这意味着我不得不分支这么多,我想。但为什么分支不好?即使我切换到其他一些结构(比如一个案例......),我仍然会分支。我确实需要测试这些条件,因为嵌套数组,带符号的数组或散列的处理方式不同。

我试图建立自己的直觉,能够看到这样的问题并找出一个有效而有效的解决方案......但似乎我最终做的是非常低效和无效<我有时间。鉴于我无法理解为什么我的代码不好,这种权衡让我感到担忧。

任何人都在考虑这个问题并帮助我了解如何将上述方法变为一种状态,使其保持一定的可读性,但这符合Rubyists更喜欢的风格指南?

---------------------- 更新 ----------------------

关于我能想到的最好的是:

def query(data_set, conditions)
  query_data_set = data_set.dup
  parse_conditions(conditions).each do |condition|
    query_data_set = check_conditions(condition, query_data_set)
  end
  query_data_set
end

def check_conditions(condition, data)
  if condition.class == Array
    condition.each do |term|
      data = entity.send term, data if term.class == Symbol
      data = search_data_text(data, term) unless term.class == Symbol
    end
  end

  if condition.class == Hash
    condition.each do |key, value|
      data = method("query_#{key}").call(data, value)
    end
  end
  data
end

对于Rubocop来说,check_conditions方法仍然太长,但仍然有太高的分支条件大小。

据我所知,我检查的任何地方都无法向我展示不同,唯一可以做的就是从数组和哈希检查中找出方法。换句话说,if中的每个check_conditions条件都会得到自己的方法。但这对我来说似乎是不必要的愚蠢。我基本上打破逻辑,将变量传递给不同的方法,这样我就可以将方法计数保持在某个任意值以下。

作为一种设计方式,这对我来说是错误的。但是,我没有看到改变逻辑的方法,以至于它仍然能够完成它所需要的,但每种方法只需要七行。

3 个答案:

答案 0 :(得分:0)

def query(data_set, conditions)
  data_set = data_set.dup
  parse_conditions(conditions).each do |condition|
    condition.each do |e|
      case e
      when Array  then method("query_#{e.first}").call(data_set, e.last)
      when Symbol then entity.send(e, data_set)
      else             search_data_text(data_set, e)
      end
    end
  end
end

答案 1 :(得分:0)

好的,找到了办法。它可能不是最干净但没有其他工作。这就是我的所作所为:

def query(data_set, conditions)
  query_data_set = data_set.dup
  parse_conditions(conditions).each do |condition|
    query_data_set = check_conditions(condition, query_data_set)
  end
  query_data_set
end

def check_conditions(condition, data)
  data = process_condition_array(condition, data) if condition.class == Array
  data = process_condition_hash(condition, data) if condition.class == Hash
  data
end

def process_condition_array(condition, data)
  condition.each do |term|
    data = entity.send term, data if term.class == Symbol
    data = search_data_text(data, term) unless term.class == Symbol
  end
  data
end

def process_condition_hash(condition, data)
  condition.each do |key, value|
    data = method("query_#{key}").call(data, value)
  end
  data
end

所以我有一种我觉得合情合理的方法,虽然承认有点罗嗦。但一切都在一个地方。现在,这已成为四种方法,作为代码的读者,您必须通过这些方法跟踪变量。我想这是进步。 : - /

有趣的是,在所有之后,我的整个课程现在被Rubocop认为太长了。 叹息。无论如何,生活在那种特殊的任意风格中,至少比生活在其他任意风格中更可口。

答案 2 :(得分:0)

为了扩展你自己的答案,我可能会提出一些功能性的好处......(即Enumerable.injectlambda A.K.A.一阶函数。)

(请注意,我没有质疑或更改您自己代码的逻辑;我只是机械地对其进行了转换)。

def query(data_set, conditions)
  parse_conditions(conditions).inject(data_set) do |data_set, condition|
    check_conditions(condition, data_set)
  end
end

processors = {
  'Array' => lambda do |condition, data|
               condition.inject(data) do |data,term|
                 term.class == Symbol ? entity.send(term, data)
                                      : search_data_text(data, term) 
               end
             end,
  'Hash' => lambda do |condition, data|
               condition.to_a.inject(data) do |data, pair|
                 method("query_#{pair[0]}").call(data, pair[1])
               end
             end
}

def check_conditions(condition, data)
  processors[condition.class.name].call(condition, data)
end

少了几行代码(无关紧要),并且显式的if / else控制结构加上显式的“data =”和“return data”部分都消失了。现在一切都非常明确,而且隐藏小错误的机会就少了。

当然,如果你在一个知道/喜欢功能风格的团队中工作,那么只做这样的事情;有些人挖了它,有些人讨厌它。

如果您没有处理HashArray,我可能会建议使用OO而不是函数(即将两种处理方法分配到各自的类中)。通过重新打开Hash和Array技术上可行;当然不可取。