日々常々

ふつうのプログラマがあたりまえにしたいこと。

オーバーライドの誤用

似た言葉だからと言うわけではありませんが、オーバーロードと同様にオーバーライドもしばしば誤用されます。もっとも。オーバーロードの誤用に比べると見かける頻度は低いのですが、これはオーバーライドが理解されていると言うよりも、そもそも継承があまり使われていないせいであります。*1
最も簡単な誤用方法は、オーバーライドされるメソッドと全く違う挙動をさせることです。

class SuperClass {
  int i;
  void add(int j) {
    i += j;
  }
}

class SubClass extends SuperClass {
  void add(int j) {
    i = j;
  }
}

いつもながら無茶な例だと思いますが、クラスフィールドiに加算するSuperClassのaddメソッドが、SubClassではiに代入するようにオーバーライドされている、と言う感じ。ちなみにiの初期値は0だから、addが1回実行されるだけならiの値は同じなので、手を抜いた試験だとバグが見つからないと言うおまけ付き。
流石にaddメソッドで足し算ですらなくなるようなオーバーライドはしないと思いますけれども、メソッド名をつけるのが下手な方もいらっしゃるわけで、何をするか良く判らない名称のメソッドが、全く違う処理内容にオーバーライドされている、と言うのは普通に起こってしまっている話です。
オーバーライドする場合、オーバーライドされるメソッドを満足させる必要があります。メソッド名から外れた事をさせるのは最悪です。オーバーライドされるメソッドJavaDoc等に記述されていることは必ず実装すべきです。オーバーライドされるメソッドと内容に差があるならば、明示的に記述する必要があります。とりあえず、不可解な名称を持つドキュメントが整備されていないメソッドは、オーバーライドするべきではありません。

この問題は、そのクラスを使う側から考えれば単純な話になります。フィールドの型は出来る限り大きく書かれます。ArrayListを受けるフィールドの型にListが使われるとかいうあれです。その理由は別の機会として、スーパークラスやインタフェースの型でサブクラスが扱われているので、スーパークラスのとあるメソッドを実行する場合、スーパークラスメソッドの挙動が求められているわけです。サブクラスで同じ挙動では少し都合が悪いのでサブクラスなりにオーバーライドされたりするわけですが、そのときスーパークラスメソッドの挙動とあまりにかけ離れると、使う側にとって困ったことになるのは明白です。と言うわけで、今作っているサブクラスを使う人のことを考えれば、オーバーライドによってメソッドの内容をトンデモにすることはありえないのです。


よくオーバーライドされるメソッドに、Object#toStringがあります。JavaDocに「すべてのサブクラスで、このメソッドをオーバーライドすることをお勧めします。」と書かれているアレです。toStringはオブジェクトの文字列表現を返すと言うことになっています。つまり、オブジェクトの文字列表現以外を返すこと以外をしてはいけないのです。少し前の現場で、妙なコードレビューがありました。toStringを普通にそれっぽくオーバーライドしていたのですが、それを捕まえて「どこからも呼ばれていない処理がある。」とか。ああ直接呼んではいないね確かに。「これはログ出力に使っているなら、ログ出力用やそれに準ずる文言を追加すべきである。」とか。確かにログ出力にも使ってるけど、別にログ出力のために実装したんじゃないやい。Java判らない人が勉強もせず昔の知識を振りかざしてコードレビューだとか言って踏み込んで来るのは勘弁して欲しい。あれ、脱線した。

*1:もちろん「継承よりもコンポジションを多用せよ」と言うのを受けて継承を使用しない事を選択しているわけでもなく、継承を使う事を考えずに、似たクラスは複製で作成すると言う意味です。そういう方々が普通にプロのプログラマだとかシステムエンジニアだとか名乗ってる現実があるわけで。