Ruby: const_defined? / const_get の罠

クララオンライン グローバルソリューション事業部の平田です。

今回は、失敗談を交えて const_defined? / const_get について解説したいと思います。

 

const_defined? / const_get とは

Module.const_defined? / Module.const_get はクラス・モジュール内に定数が定義されているか調べる・定数を得るために利用されます。たとえば、Math.const_defined? を利用して Math::PI が定義されているか調べることができます。

また、同様に Math.const_get を利用して Math::PI の値を得ることができます。

通常はこのように面倒な手順を踏む必要はありませんが、定数名を動的に決定する場合には有用な機能です。

 

陥った罠

あるAPIにアクセスしたレスポンスのクラスを動的に決定しようと、指定したクラスが定義されていることをチェックしてそのクラスを処理を実装していました。(コードは簡略化してあります。)

ここでは、Foo::Response::Domainが存在すればそのクラス自身を、そうでない場合には nil を返すことを意図していましたが、Foo::Response::Domainが存在しない場合でも、親クラスやモジュールなどで Domain が定義されていればそのクラスを返してしまうのです。

このシステムではたまたま Domain というクラスが定義されていたため、結果的に意図しないクラスのインスタンスが作成され、謎のエラーに悩まされることとなりました。

これは以下のコードを実行してみると一目瞭然です。

 

実は仕様が変更されていた

実は、先のコードは Ruby 1.8 では意図した通りに動作します。この挙動は Ruby 1.9 になった時点で仕様が変更されており、第2引数に false を指定することで、1.8 と同様にそのクラス自身に定義された定数のみを対象とすることができます。

最終的にコードは次のように変更され、無事動作するようになりました。

 

まとめ

Rubyは強力なメタプログラミング機能を備えていますが、仕様を十分理解しないままに利用すると思わぬ動作を引き起こします。言語の内部にアクセスするような機能は原因の特定やデバッグも難しいこともあるため、十分注意して利用するべきです。

 

…と若干の反省も込めて、締めくくりたいと思います。

 

Share on LinkedIn
LINEで送る
Pocket

平田

平田

本業はインフラエンジニアだけど、最近はプログラミングやっています。