普段、Perlを書くときは require_ok
などでモジュールがコンパイル可能か・構文エラーがないかをテストするようにしているけれど、 require_ok
では発見できない構文エラーを発見したのでそれの詳細と対策について。
実行時エラーになる構文エラー (?) とindirect object notation
実行時エラーに構文エラーになることなんてあるのかよと思うけど、厳密には構文エラーではなく、意図しない構文解析の結果、コンパイルは成功するが実行するとエラーになるというコードができあがってしまった。
具体的には以下のようなコード:
package App::Logger; use Exporter 'import'; our @EXPORT = qw(INFO); sub INFO ($;@) { ... } 1;
package App::Loader::User; sub find { local $@; eval { ... }; if ($@) { INFO $@; } } 1;
App::Logger
はいろいろ便利なことをしてくれるアプリケーションロガーで定義されたサブルーチン INFO
はprototypeが宣言されており INFO 'hi';
INFO 'user: %s', $user->name;
のように呼び出せる。
App::Loader::User
は App::Loader::INFO
を呼び出しているパッケージだけど、 use App::Logger
するのを忘れていた。しかしコンパイルは通る。つまり構文解析には成功しているということである。なぜだろうか?
具体的には INFO $@;
がなぜ構文解析に成功するのか。答えはindirect object notationとして妥当な構文であるからだった。
indirect object notationはperldoc perlmodに詳しいが、 MOD->METHOD
という呼び出しは METHOD MOD
と等しく、後者をindirect object notationという。
つまり INFO $@
は $@->INFO
と構文解析されて妥当というわけである。
no indirect プラグマ
謎は解けたが、ひとつのことを成し遂げるために複数のやりかたがあるという状態は、特にチーム開発においてはうれしいことばかりではない。堅牢にソフトウェアを作るためには、曖昧さが少なくエンバグのおそれが少ない書き方ができるほうが望ましい。
indirect object notationを意図せず使っていたら警告なりしてほしいところだけど、そんなときはindirectプラグマが使える。
no indirect;
を書いたスコープでindirect object notationが使われていると警告される。 no indirect 'fatal';
で警告する代わりに例外を投げる。
プラグマの影響範囲はデフォルトでレキシカルだけど no indirect 'global';
でグローバルに有効にできる。
これでindirect object notationでハマることを減らせそうで助かってめでたいですね。