我怎样才能在Ruby中编写一个双嵌套的if/else呢?

dgiusagp  于 2023-01-30  发布在  Ruby
关注(0)|答案(4)|浏览(177)

假设我有这样的代码:

def set_reminder(cond_one: false, cond_two: false)
  if cond_two
    if cond_one
      outcome_a
    else
      outcome_b
    end
  else
    if cond_one
      outcome_c
    else
      outcome_d
    end
  end
end

如何更优雅地编写这样一个函数,它有4个可能的结果(cond_onecond_two值的每个组合对应一个结果)?
我对这个版本不太满意,它在两个分支中都使用了一个if/else语句,在实际代码中,outcome已经是复杂的表达式了,所以编写类似return outcome_a if cond_one && cond_two的东西(针对所有4个结果)会很笨拙。

lc8prwob

lc8prwob1#

Ruby有一个非常强大的case expression,可以用于这类事情。

def set_reminder(cond_one: false, cond_two: false)
  case [cond_one, cond_two]
  when [true, true] then outcome_a
  when [true, false] then outcome_b
  when [false, true] then outcome_c
  when [false, false] then outcome_d
  end
end

正如评论中所指出的,考虑让你的论证表达的不仅仅是“布尔对”,参见Boolean blindness以获得关于这个问题的讨论。

ldfqzlk8

ldfqzlk82#

你可以像下面这样,把嵌套的if扁平化成一系列的保护语句。

def set_reminder(cond_one: false, cond_two: false)
  return outcome_a if cond_two && cond_one
  return outcome_b if cond_two
  return outcome_c if cond_one

  outcome_d
end

这样做更简洁,并允许进一步重构。
Sandi梅斯等人关于重构的书99 Bottles of OOP我怎么推荐都不为过。有一个Ruby版本。整本书带你经历了这种重构。从多个if到提取的类。

c0vxltue

c0vxltue3#

可以在构造函数中将其设置为散列:

@choice = {[true, true] => :a, [true, false] => :c,
           [false, true] => :b, [false, false] => :d}

那么你的方法就是

def set_reminder(cond_one: false, cond_two: false)
  @choice[[cond_one, cond_two]]
end

避免通过复合对象进行索引的一个替代方案是创建散列的散列:

@h_of_h = {true => {true => :a, false => :c},
              false => {true => :b, false => :d}}

def set_reminder_h2(cond_one: false, cond_two: false)
  @h_of_h[cond_one][cond_two]
end

我做了以下基准测试来比较各种建议的方法:

require 'benchmark/ips'

combos = [[true, true], [true, false], [false, true], [false, false]]

def set_reminder_case(cond_one: false, cond_two: false)
  case [cond_one, cond_two]
  when [true, true] then :a
  when [true, false] then :b
  when [false, true] then :c
  when [false, false] then :d
  end
end

def set_reminder_guard(cond_one: false, cond_two: false)
  return :a if cond_two && cond_one
  return :b if cond_two
  return :c if cond_one
  :d
end

@choice = {[true, true] => :a, [true, false] => :c,
           [false, true] => :b, [false, false] => :d}

def set_reminder_hash(cond_one: false, cond_two: false)
  @choice[[cond_one, cond_two]]
end

@h_of_h = {true => {true => :a, false => :c},
              false => {true => :b, false => :d}}

def set_reminder_h2(cond_one: false, cond_two: false)
  @h_of_h[cond_one][cond_two]
end

N = 1_000
SEED_VALUE = 123_456_987

# The choicess of true/false combos are being randomized, but since the
# seed is reset they are identical for the two functions being tested.
Benchmark.ips do |b|
  srand(SEED_VALUE)
  b.report('case') do
    N.times do
      v1, v2 = combos[rand(4)]
      set_reminder_case(cond_one: v1, cond_two: v2)
    end
  end
  srand(SEED_VALUE)
  b.report('hash') do
    N.times do
      v1, v2 = combos[rand(4)]
      set_reminder_hash(cond_one: v1, cond_two: v2)
    end
  end
  srand(SEED_VALUE)
  b.report('guard') do
    N.times do
      v1, v2 = combos[rand(4)]
      set_reminder_guard(cond_one: v1, cond_two: v2)
    end
  end
  srand(SEED_VALUE)
  b.report('hsh_of_hsh') do
    N.times do
      v1, v2 = combos[rand(4)]
      set_reminder_h2(cond_one: v1, cond_two: v2)
    end
  end
  b.compare!
end

在M1 MacBook Pro上使用Ruby 3.2.0生成的结果如下:

% ruby case_v_hash.rb 
Warming up --------------------------------------
                case   197.000  i/100ms
                hash   239.000  i/100ms
               guard   596.000  i/100ms
          hsh_of_hsh   562.000  i/100ms
Calculating -------------------------------------
                case      1.977k (± 1.2%) i/s -     10.047k in   5.083713s
                hash      2.408k (± 0.5%) i/s -     12.189k in   5.062504s
               guard      5.952k (± 0.6%) i/s -     29.800k in   5.006765s
          hsh_of_hsh      5.637k (± 1.3%) i/s -     28.662k in   5.085419s

Comparison:
               guard:     5952.2 i/s
          hsh_of_hsh:     5637.2 i/s - 1.06x  (± 0.00) slower
                hash:     2407.8 i/s - 2.47x  (± 0.00) slower
                case:     1976.6 i/s - 3.01x  (± 0.00) slower

使用--yjit

% ruby --yjit case_v_hash.rb
Warming up --------------------------------------
                case   243.000  i/100ms
                hash   290.000  i/100ms
               guard     1.075k i/100ms
          hsh_of_hsh   952.000  i/100ms
Calculating -------------------------------------
                case      2.419k (± 0.7%) i/s -     12.150k in   5.022058s
                hash      2.921k (± 0.8%) i/s -     14.790k in   5.062949s
               guard     10.715k (± 1.4%) i/s -     53.750k in   5.017634s
          hsh_of_hsh      9.430k (± 0.7%) i/s -     47.600k in   5.048054s

Comparison:
               guard:    10714.6 i/s
          hsh_of_hsh:     9429.9 i/s - 1.14x  (± 0.00) slower
                hash:     2921.4 i/s - 3.67x  (± 0.00) slower
                case:     2419.4 i/s - 4.43x  (± 0.00) slower

在我看来,user3574603的guard语句方法确实是赢家,而hash的hash方法则紧随其后,两者都主导了hash查找和case语句方法。

rqdpfwrv

rqdpfwrv4#

我想说的是,你不能在现有的基础上再改进了。你的方法有以下几个特点:

  • 任何阅读您的代码的人(包括您将来返回代码时的自己)都很容易理解;
  • 测试是直接的;
  • 变量cond_twocond_one每个仅被检查一次;以及
  • 它容易适应逻辑上的未来变化。

方法中的代码行数可以减少,但如果这会对可读性或测试的简易性产生负面影响,我建议不要这样做。
下面是一些或多或少等价的方法,我认为在这些方法中进行选择纯粹是一种风格上的选择。

  • 内部条件句使用三元组 *
def set_reminder(cond_one: false, cond_two: false)
  if cond_two
    cond_one ? outcome_a : outcome_b
  else
    cond_one ? outcome_c : outcome_d
  end
end
  • 使用case语句 *
def set_reminder(cond_one: false, cond_two: false)
  case cond_two
    case cond_one
    when true then outcome_a
    else           outcome_b
    end
  else
    case cond_one
    when true then outcome_c
    else           outcome_d
    end
  end
end
  • 用方法调用替换内部条件 *
def set_reminder(cond_one: false, cond_two: false)
  if cond_two
    set_reminder_cond_two_true(cond_one)
  else
    set_reminder_cond_two_false(cond_one)
  end
end

def set_reminder_cond_two_true(cond_one)
  cond_one ? outcome_a : outcome_b
end

def set_reminder_cond_two_false(cond_one)
  cond_one ? outcome_c : outcome_d
end

相关问题