Code Fix

中級

gsub! は置換が発生しないと nil を返す(gsub との違い)

破壊的メソッドのgsub!やsub!は、置換対象が見つかって実際に変更が起きた場合は変更後の文字列を、何も置換されなかった場合はnilを返します。戻り値をそのまま次のメソッドにつなげると、置換が起きなかった入力でNoMethodErrorになります。

エラーメッセージの読み方

main.rb:3:in `<main>': undefined method `upcase' for nil (NoMethodError)
main.rb
ファイル名
3
行番号 — 実際にクラッシュした行
<main>
発生場所 — <main> ならトップレベル、メソッド名ならそのメソッドの中
NoMethodError
例外クラス — 何が起きたか。ここを検索するのが最短です
undefined method `upcase' for nil
詳細メッセージ — どの値が問題だったか

このエラーが出る典型パターン

パターン1

 1  text = "hello"
 2  result = text.gsub!("x", "y")
                    ^
 3  puts result.upcase
main.rb:3:in `<main>': undefined method `upcase' for nil (NoMethodError)

gsub!は置換が1件も発生しないとnilを返します。置換が起きるとは限らない入力にはgsub!の戻り値をそのまま使うのは危険です。

直し方: gsub!gsub にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  word = "ruby"
 2  result = word.gsub!("z", "q")
                    ^
 3  puts result.length
main.rb:3:in `<main>': undefined method `length' for nil (NoMethodError)

置換対象が見つからなかった場合、gsub!は変更前の文字列すら返さずnilになります。

直し方: gsub!gsub にします。

この問題を解いてみる →

パターン3

 1  line = "sample"
 2  result = line.gsub!("!", "?")
                    ^
 3  puts result.reverse
main.rb:3:in `<main>': undefined method `reverse' for nil (NoMethodError)

常に文字列を返してほしいだけなら、破壊的でないgsubを使うのが安全です。

直し方: gsub!gsub にします。

この問題を解いてみる →

パターン4

 1  note = "memo"
 2  result = note.gsub!("x", "y")
                    ^
 3  puts result.capitalize
main.rb:3:in `<main>': undefined method `capitalize' for nil (NoMethodError)

gsub!の戻り値は「変更があったかどうか」を表す設計で、変更が無ければnilになります。

直し方: gsub!gsub にします。

この問題を解いてみる →

パターン5

 1  title = "draft"
 2  result = title.gsub!("q", "w")
                     ^
 3  puts result.upcase
main.rb:3:in `<main>': undefined method `upcase' for nil (NoMethodError)

メソッドチェーンでgsub!をつなげる場合、置換が起きなかった経路でnilが混ざる危険を考える必要があります。

直し方: gsub!gsub にします。

この問題を解いてみる →

よくある誤解

「gsub!もgsubと同じく、変更後の文字列を常に返すはず」という思い込みは誤りです。gsub!は「変更があったかどうか」を戻り値で表現する設計になっており、変更が無ければnilが返ります。

まとめ

gsub! は置換が発生しないと nil を返す(gsub との違い)は中級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)