はじめに:突然ですが、問題です
こんなメソッドがあります。
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"が出力されませんね )
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で型検査する
といった工夫を取り入れましょう。