技術と魚

雑感と備忘録

条件分岐が網羅的だと思ったら意図的に最後に例外を仕込むことのススメ

例えば、Rubyはcase式に引っかからなかったらnilになります。

$ ruby -e 'p(case 0; when 1 then true; end)'
nil

場合分けのビジネスロジックを書く際にはcase式が有効です。

もし、自分が網羅的なパターンマッチを書いたと思った場合は、最後にelse raiseをしておくことをオススメします。 マジで良いこと尽くしです。


Aさんは自社サービスのエンジニア。今、会社のマーケターに「顧客を若年層(Young)、中年層(Middle)、老年層(Old)3分類し、それぞれに対し適切な商品パッケージ(X, Y, Z)を勧めたい」と言われ、Aさんは以下のようにビジネスロジックを書いたとします。

def find_recommended_package(customer)
  case customer
  when 'Young'
    X.new
  when 'Middle'
    Y.new
  when 'Old'
    Z.new
  end
end

これを書いた頃Aさんは、customerは 'Young', 'Middle', 'Old' しかあり得ないと思っています。だから、この文は例外なくX,Y,Zのいずれかにしか評価されません。

しかし、マーケターがある時「中年層は男女で明らかに異なる嗜好性を示すので、女性だったら別で考えよう」と言ったとします。このようなことは非常によくあります。

すると、チームの別のエンジニアBさんが上記のコードの存在を知らないで顧客セグメントに 'MiddleFemale' を追加したとすると、上記のcase式はその時にnilになり得ます。

上記のコードの利用側は、nilが来るなんて思っていません。例えば、返ってきた商品パッケージの品名を取得して返す、なんてことをしています。

package = find_recommended_package(customer)

return package.title

すると、 customer = 'MiddleFemale' が来た場合は undefined method 'title' for nil:NilClass (NoMethodError) という例外になります。 言うまでもなくfind_recommended_packageが 'MiddleFemale' を予測していないからこうなるのだと分かります。

しかし、さらにチームのエンジニアCさんは、このエラーの修正タスクをアサインされ、以下のように修正します。

package = find_recommended_package(customer)

unless package
  return ''
end

return package.title

しかし、おそらく本来なら、以下のようにcase式内で適切に 'MiddleFemale' をハンドリングするべきところです。

def find_recommended_package(customer)
  case customer
  when 'Young'
    X.new
  when 'Middle'
    Y.new
  when 'MiddleFemale'
    W.new
  when 'Old'
    Z.new
  end
end

find_recommended_packageメソッドの役割を理解していないCさんを責めるべきでしょうか?

いえ、上記のコードは最小限なのでそう見えますが、実際のロジックはそう簡単に判別できるほど簡単ではありません。実際に「find_メソッドの中の方でハンドリングするべきです」などとCさんに指摘しても、「へぇ、そうなんだ。でも、それを知る術がないよね。毎回Aさんに聞くべきなのかな?」となります。

そこで、Aさんがもともと以下のようにコードを書いていた場合を考えます。

def find_recommended_package(customer)
  case customer
  when 'Young'
    X.new
  when 'Middle'
    Y.new
  when 'Old'
    Z.new
  else
    raise "Unknown customer: #{customer}"
  end
end

すると同じことが起きた時、エラーの内容は Unknown customer: MiddleFemale (RuntimeError) となります。

なので、Cさんからするとraiseした箇所に容易に到達できて、場合分けが足りていないことが原因だと分かります。そしてマーケターの人に、「今、こういうロジックで商品パッケージを顧客に勧めているんですが、新しいセグメントに対してはどのように対処するべきでしょうか?」と適切に聞くことにも繋がります。

さらにマーケターの人もこのことを認識していなかった場合は、「ああ、そうだったね。だったら、こうしようか」と建設的にビジネスを進めることにも繋がります。

このように、適切な箇所で正しくエラーになってくれることが、ビジネスに好影響を与えることができます。

などなど、良い面が盛りだくさんなので網羅的だと思ったらそのときに意図的にelse raiseしてやりましょう。