著作一覧 |
こんなバグをやっちった。
class Base { String convert0(String s) { return null; # Dummy } } .... class Foo extends Base { String convert0(int n) { return ...; // 実装がある } } ... Base[] bases = { new Foo(); } ... StringBuilder sb = new StringBuilder(); for (Base b : bases) { sb.append(b.convert0("hello")); // Foo#convert0は呼ばれない }
これは、ダックタイピングは……というのと同じ種類の問題のように見える。メソッドにオーバーロードがあるため、異なるシグネチャで書いてしまってもコンパイルは通るからだ。
どう解決できるだろうか?
それ抽象という方法がある。
abstract class Base { abstract String convert0(String s); } .... class Foo extends Base { String convert0(int n) { return ...; // 実装がある } // String convert0(String s)が未定義でコンパイルエラー }
が、実際にはある長い長いデータをばしばし切り分けていくベルトコンベア的なシステムなので、Baseクラスにデフォルトの実装をしなけりゃやってられない(convert0メソッドからconvert365メソッドまであるという感じ)。というか、具象クラスのほとんどすべてのメソッドが空実装になる。ということは、機械的な空メソッドの実装を行う必要が出てくるため、結果的に、同傾向のバグが入りかねない。何よりも、あまりにもDRYじゃない。空メソッドを実装した具象メソッドがたくさんできてしまうからだ。interface Baseにしても、したがって話は変わらない。
いや、すべてのメソッド呼び出しが走るテストを書かないのが悪いというのはもっともだけど、それは置いておいて。
常に親へ委譲(とは違うが)を使えばどうだろうか?
class Base { String convert0(String s) { return null; } } .... class Foo extends Base { String convert0(int n) { super.convert0(n); //// そんなメソッドは無いエラー ……実際の処理 return ……; } }
あまりに意味不明瞭かも知れない。でも、実装バランスからはそれほど悪くはないかも知れない。定義済みメソッドはむちゃくちゃ多いが、オーバーライドするメソッド数はそれほど多くはないからだ。また、パターンがあるので、意味不明瞭さはすぐに解消するだろう。しかし、削除されるかも。でも、この実行時には意味を持たないsuper呼び出しを書くというのは、結局規約の問題なので忘れてしまっても意識的に省略してもビルドはできるし、たいていはうまく動く。あまり良い防御方法とは言えないのではないか。
問題は呼ばれるべきメソッドが呼ばれていないということだ。したがって、呼ばれるべきではないメソッド側では対処できないだろう。
class Base { String convert0(String s) { assert false : "not override correctly"; return null; } }
というか、こんなことするなら、抽象メソッドにするほうが良い。し、それは現実的ではないと否定したばかりだ。
と、書いているうちに、可視化ツールというアイディアが生まれた。オーバーライドしてあるメソッドとそうでないメソッドが色で区別される。すると、
class Foo extends Base { String convert0(int n) { // このメソッドは紫で表示されている return ...; // 実装がある } String convert1(String s) { // このメソッドは紺で表示されている return ...; // 実装がある } }
なぜ、convert0は紫なんだ? ……、あ、パラメータの型が違う!
今のIDEは、予約語、文字列、直定数、コメントといった、静的なというか言語仕様に基づいた色分けはしてくれるのは知っている。でも、これが役に立つのは(読みやすいとかは別にして)、コメントが別の色になるってやつだけだ。長い/* */を持つ腐ったプログラムを眺める時に役に立つのは知っている。
そうではなく、あるいはそれに加えて、継承についての色分けがあれば、少なくてもこの手のバグは防げる。
(overrideキーワードの記述を必須にするというC#の方法もあるな。でも、これはJavaの話だし、overrideキーワード必須は逆に面倒で忘れやすいし、しかも忘れても実行時に変だと気づくだけで、後からソース読むときの役にしか立っていないように思える)
というか、こういう色付けするエディターってすでにありそうな気がしてきた。あるんだろうな。elispで書きたくはないし。
ジェズイットを見習え |
子側にこまめに@Override付けるのが好きですが、忘れればアウトですね。親側でprotected final String convert0(int n) {}しておくとか。
>親側でprotected final String convert0(int n) {}しておくとか。<br>あ、なるほど。この場合、それが良い解決方法です(というのは、途中から引数の型を変える大修整が入ったのが原因。その時点で古いメソッドシグネチャをそうやって殺せば、修正漏れが焙りだされる)。今度からそうします(修正が終わったら削除するだろうな)。
Deprecated の進化版というか,コンパイル時にエラーチェックをするためだけに存在して,実装は要求されないような空メソッドが定義できると面白いかもしれませんね.<br>- メソッド探索の候補には参加する<br> - このメソッドが選択されるとコンパイルエラー<br>- 実装は含めることができない<br>みたいな.
確かに属性はまだまだ使い途がありそうですね。