3
1

【Ruby】ちゃんと動かないcase/when

Posted at

はじめに:突然ですが、問題です

こんなメソッドがあります。

def role_name(role)
  case role
  when 'Admin'
    'Administrator'
  when
    'Member'
  else
    'Guest'
  end
end

このとき、以下のコードの実行結果はどうなるでしょうか?

puts role_name('Admin')
puts role_name('Member')
puts role_name('Guest')

正解はCMのあとで!!

<CM>

Rubyを知れば、Railsはもっと楽しくなる。
「プロを目指す人のためのRuby入門 改訂2版」好評発売中!🍒

<CMおわり>

では正解です。先ほどのコードを実行するとこうなります。
(おや、"Member"が出力されませんね :thinking:

Administrator

Guest

あなたは正解しましたか!?

先ほどのコードの何がおかしいのか

もう一度role_nameメソッドの実装を見てみましょう。

def role_name(role)
  case role
  when 'Admin'
    'Administrator'
  when
    'Member'
  else
    'Guest'
  end
end

どこがおかしいかわかりましたか?

はい、ここですね。

  when
    'Member'

これ、whenに対応する条件式が'Member'になってしまっています。
そしてこの条件式が真になったときの処理がありません。

問題点がわかりやすいように書き直すならこうです。

def role_name(role)
  case role
  when 'Admin'
    'Administrator'
  when 'Member'
    # 何もしていない(nilを返す)
  else
    'Guest'
  end
end

いちおうRubyの文法的には正しいので、構文エラーが発生することはありません。
しかし、実装としては明らかに間違いです。

正しく書き直すならこう

roleが"Member"なら"Member"を表示する、という要件なら、以下のように書くのが正解です。

def role_name(role)
  case role
  when 'Admin'
    'Administrator'
  when 'Member'
    'Member'
  else
    'Guest'
  end
end

こうすればroleが"Member"の場合でも名称が返ってきます。

puts role_name('Member')
#=> Member

RuboCopで検知する

もしRuboCopを使っているなら、このミスはLint/EmptyWhenというcop(ルール)で検知可能です。

app/models/user.rb:65:5: W: Lint/EmptyWhen: Avoid when branches without a body.
    when ...
    ^^^^

RBS + Steepで検知する

もし事前に role_name メソッドの型定義をRBSで書いていれば、steep check で型エラーが発生するので実装ミスを検知できます。

# user.rbs
def role_name: (String role) -> String
$ steep check
# Type checking files:

...........................................................................................F

app/models/user.rb:65:5: [error] Cannot allow method body have type `(::String | nil)` because declared as type `::String`
│   (::String | nil) <: ::String
│     nil <: ::String
│
│ Diagnostic ID: Ruby::MethodBodyTypeMismatch
│
└     def role_name(role)
          ~~~~~~~~~

Detected 1 problem from 1 file

まとめ

今回紹介したコードは、僕がコードレビューの最中に実際に遭遇した事例です。

構文エラーのようで構文エラーではない(ふつうに実行できてしまう)ので、テストが不十分だとバグを抱えたまま本番リリースされかねません。
Ruby初心者の方はこういったコードを書いてしまわないよう注意しましょう。

また、「コードを書いた本人が気を付ける」だけではうっかりミスは避けにくいので、

  • 必ずコードレビューを受ける
  • RSpecやMinitestのテストコードで条件分岐を全網羅する
  • RuboCopを導入する
  • RBS/Steepで型検査する

といった工夫を取り入れましょう。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1