我应该重构这个深度嵌套的ruby方法吗?

时间:2013-12-03 22:01:40

标签: ruby rspec coding-style format indentation

下面的Ruby代码是应该分解成更小的方法,还是应该“按原样”保留,因为如果分解它会碎片过多?我们不确定应该采用什么样的正确方法。 我们知道使用单个测试的小方法通常是最佳实践,但是在这种情况下,这样做似乎会使这些代码过于“薄”并大大降低可读性。这类代码在这个领域有哪些最佳实践?

if $?.success?
    puts "Running git fetch"
    `git fetch`
    if $?.success?
        puts "Running git reset --hard origin/#{mbranch}"
        `git reset --hard origin/#{mbranch}`
        if $?.success?
            puts "Running git merge origin/#{branch} --no-ff -m \"STAGED:#{ticket} - #{title}\""
            mergeres=`git merge origin/#{branch} --no-ff -m "STAGED:#{ticket} - #{title}"`
            if $?.success?
               `git log -n 1 | grep "STAGED:#{ticket}"`
               if $?.success?
                  ret=true
                  if resjs eq "yes"
                     puts "Entering reservejs directory..."
                     `cd /#{dir}/zipcar-main/zipcar acs/packages/zipsite/www/reservations/reservejs`
                     if $?.success?
                        puts "Running git fetch on reservejs"
                        `git fetch`
                        if $?.success?
                           puts "Running git checkout master on reservejs"
                           `git checkout master`
                           if $?.success?
                              puts "Running git reset --hard origin/master on reservejs"
                              `git reset --hard origin/master`
                              if $?.success?
                                 puts "Running git merge origin/#{branch} on reservejs"
                                 `git merge origin/#{branch} --no-ff -m "STAGED:#{ticket} - #{title}"`
                                 if $?.success?
                                    `git log -n 1 | grep "STAGED:#{ticket}"`
                                    if $?.success?
                                       b=Dir.chdir("#{firstdir}")
                                       return true
                                    end
                                 end
                              end
                           end
                        end
                     end
                  end
               end
            else
               puts "#{mergeres}"
            end
         end
      end
   end

5 个答案:

答案 0 :(得分:5)

你的结构尖叫需要一些迭代。而不是深度嵌套的条件,为什么不是一个步骤数组,而你只是在步骤失败时中止处理。

def do_stuff

  # array of [cmd, output] pairs.
  steps = [
    ["git fetch",                          "Running git fetch"],
    ["git reset --hard origin/#{mbranch}", "Running git reset --hard origin/#{mbranch}"],
    # more steps and feedback labels
  ]

  steps.each do |step|
    cmd, feedback = step # step[0] is the command, step[1] is the feedback
    puts feedback
    return false unless system(cmd).success?
  end
end

或者您可以使用哈希数组,甚至可以让您根据需要扩展“步骤”的定义方式。

steps = [
  {
    cmd: 'git fetch',
    msg: 'Doing a git fetch, hold onto your butts!'
  },
  # more step hashes...
]

这使得步骤的线性顺序更具可读性。并且将来如果你必须在某个地方插入一个命令,你可以这样做而不会愤怒地咒骂。


根据您想要走多远,您可以创建一个Step类来封装每一步!

steps = [
  Step.new(
    cmd: 'git fetch',
    msg: 'Doing a git fetch, hold onto your butts!'
  ),
  # more step instances...
]

steps.each do |step|
  step.run!
  return false if step.failed?
end

您甚至可以让每个步骤接受一个块,以便围绕该步骤运行任何自定义逻辑。

Step.new(
  cmd: 'git fetch',
  msg: 'Doing a git fetch, hold onto your butts!'
) do |result|
  # run some ruby code with result of command before next step
end

并表示你可以创建一个运行步骤集合的类!

runner = StepRunner.new(
  Step.new(
    cmd: 'git fetch',
    msg: 'Doing a git fetch, hold onto your butts!'
  ),
  # more step instances..
)

runner.run!

if runner.success?
  puts 'all steps complete!'
else
  puts "failed with error: #{runner.error}"
end

但那已进入过度杀伤区。这取决于你想要这个系统的灵活性。如果这是一种常见的模式,但步骤的类型和数量各不相同,那么您可能需要更多的抽象。如果这是一个孤立的事件,你可以在更简单的一面做一些事情。

答案 1 :(得分:1)

至少,你可以通过折叠它来使它更具可读性:

begin
  # do_stuff
end if $?.success?

begin
  # do_stuff
end if $?.success?

可替换地:

# do_stuff
return false unless $?.success?

答案 2 :(得分:1)

在这种情况下,你应该反过来说条件,即只要不满足主流条件就让它分开。

假设您在方法foo中拥有整个过程。然后,当条件不满足时,您可以使用return逃离主流。

def foo
  return unless $?.success?
  puts "Running git fetch"
  `git fetch`

  return unless $?.success?
  puts "Running git reset --hard origin/#{mbranch}"
  `git reset --hard origin/#{mbranch}`

  return unless $?.success?
  puts "Running git merge origin/#{branch} --no-ff -m \"STAGED:#{ticket} - #{title}\""
  mergeres=`git merge origin/#{branch} --no-ff -m "STAGED:#{ticket} - #{title}"`

  return unless $?.success?
  `git log -n 1 | grep "STAGED:#{ticket}"`

  ...

  return true
end

答案 3 :(得分:0)

始终将代码分解为小块总是!!我不是Ruby忍者,但是“OK”的最大嵌套级别就像是3级,之后它只是丑陋的丑陋代码。 最好是碎片而不是那个嵌套级别肯定。如果你将来需要编辑这个东西,那肯定是痛苦的;]

答案 4 :(得分:0)

不要重现当前流行答案的大部分内容,这些答案显示了通过一系列“步骤”表示和迭代的各种方式,我只想提出以下额外的机会来增强可读性和可维护性:

  • 认识到许多命令涉及输出命令字符串本身并提供一种机制来指示而不是必须复制字符串
  • 可能使用变量来表示每个命令,以便您可以在名称中记录该命令的意图。变量可以是Array或Hash或Proc或某些自定义类,具体取决于您使用的方法
  • 为了模块化而将控制机制分解为单独的方法