Code Fix

上級

method_missing で super を呼ばないと本来のエラーが消える

method_missingを独自定義すると、未定義のメソッド呼び出しをすべて横取りできます。想定していない呼び出しに対してsuperを呼ばずnilなどを返してしまうと、本来出るはずだったNoMethodErrorが出ず、バグが静かに握りつぶされます。

なぜエラーが出ないのか

出力: false
(エラーなし)
Rubyは何も報告しません。文法として正しいためです
出力: false
実際の挙動 — 期待した結果と食い違っている箇所
見つけ方
エラーが出ないので、出力を目で確かめるしかありません。この種の誤りが最も発見が遅れます

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

パターン1

 1  class Ghost
 2    def method_missing(name, *args)
 3      name == :boo ? "Boo!" : super
 4    end
 5  
 6                                                                                          
                                                ^
 7  end
 8  
 9  g = Ghost.new
10  puts g.respond_to?(:boo)
出力: false

method_missingで応答できるようにしたメソッドでも、respond_to_missing?を実装しない限りrespond_to?は「知らない」と答えてしまいます。

直し方: (空) def respond_to_missing?(name, include_private = false) name == :boo || super end にします。

この問題を解いてみる →

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

パターン2

 1  class Robot
 2    def method_missing(name, *args)
 3      name == :beep ? "Beep!" : super
 4    end
 5  
 6                                                                                           
                                                ^
 7  end
 8  
 9  r = Robot.new
10  puts r.respond_to?(:beep)
出力: false

method_missingだけを実装してrespond_to_missing?を省略すると、実際には反応できるメソッドなのにrespond_to?がfalseを返す食い違いが起きます。

直し方: (空) def respond_to_missing?(name, include_private = false) name == :beep || super end にします。

この問題を解いてみる →

パターン3

 1  class Cat
 2    def method_missing(name, *args)
 3      name == :meow ? "Meow!" : super
 4    end
 5  
 6                                                                                           
                                                ^
 7  end
 8  
 9  c = Cat.new
10  puts c.respond_to?(:meow)
出力: false

method_missingとrespond_to_missing?は必ずセットで実装するのが基本です。片方だけでは動的なメソッドの存在確認と実際の呼び出し結果が食い違います。

直し方: (空) def respond_to_missing?(name, include_private = false) name == :meow || super end にします。

この問題を解いてみる →

パターン4

 1  class Duck
 2    def method_missing(name, *args)
 3      name == :quack ? "Quack!" : super
 4    end
 5  
 6                                                                                            
                                                 ^
 7  end
 8  
 9  d = Duck.new
10  puts d.respond_to?(:quack)
出力: false

d.quackを実際に呼べば動くのに、respond_to?(:quack)はfalseと答えるという矛盾が起きます。

直し方: (空) def respond_to_missing?(name, include_private = false) name == :quack || super end にします。

この問題を解いてみる →

パターン5

 1  class Alarm
 2    def method_missing(name, *args)
 3      name == :ring ? "Ring!" : super
 4    end
 5  
 6                                                                                           
                                                ^
 7  end
 8  
 9  a = Alarm.new
10  puts a.respond_to?(:ring)
出力: false

respond_to?に依存するライブラリのコードは、respond_to_missing?の実装漏れがあると誤動作します。

直し方: (空) def respond_to_missing?(name, include_private = false) name == :ring || super end にします。

この問題を解いてみる →

よくある誤解

「method_missingは自分が処理したいメソッド名だけを拾ってくれるはず」という思い込みは誤りです。method_missingは条件に一致しない呼び出しも含めて全て渡ってくるため、想定外の名前に対しては明示的にsuperを呼んで本来のNoMethodErrorを発生させる必要があります。

まとめ

method_missing で super を呼ばないと本来のエラーが消えるは上級でつまずきやすい項目です。上の5パターンを実際に手で直すと、エラーメッセージのどこを読めばよいかが掴めます。

演習をはじめる

関連するエラー

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