仕様違反実装をしない様に

なーーんか、なんとなく思い出して引っ張ってきたエントリ。

「Mapなんだけど、getした時に絶対nullが返ってきて欲しくないんだよねー。」という実装が欲しくなった時のお話。

Map<String, String> foo = new HashMap<String, String>() {
  @Override
  public String get(String key) {
    String result = super.get(key);
    return result == null ? "" : result;
  }
};

Bar.baz(foo);
ジェネリクス使ったメソッドのオーバーライド - 都元ダイスケ IT-PRESS

マップがこのキーのマッピングを保持していない場合は null を返します。

http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/api/java/util/Map.html#get(java.lang.Object)

これは、java.util.Map インターフェイスの仕様な訳です。すなわち、このインターフェイスを実装したクラスは、Map型で受け取れるんですよね。

つまり、Mapの仕様を満たしていなければ問題があるのではないか、と気づいた。上のエントリーの様な、ちょっと弄った実装(fooの無名クラスによる実装)であっても、Map型の変数である限り、開発者はMapの仕様を元にコーディングする訳ですよね。

例えば、上のエントリーのようにfooが定義された(できた)として、Barクラスはこんなコードだったとします。

public class Bar {
  public static void baz(Map<String, String> quux) {
    ...
  }
}

そして、他人がBarクラス「だけ」を読んで修正を試みたとします。その時は、その他人は「quuxはMapだから、(MapのJavadocを読むに)対応する値が存在しない場合はnullを返すはずである」と判断するでしょう。

って、これダメじゃね? - 都元ダイスケ IT-PRESS

補足すると、もしかしたら対応する値が存在するか否かの判定する為にこんなコードを書くかもしれない。

public class Bar {
  public static void baz(Map<String, String> quux) {
        String key = ...
        if(quux.get(key) == null) {
            ...
        } else {
        ...
        }
  }
}

インターフェイスの実装は、Javadoc(仕様)を満たした上で実装しなければいけない。java.util.Map を実装する以上、Javadocに書いてある仕様に違反した実装は、ルール違反となる(エラーは出ないけど)。シグネチャだけ守ったって意味ない。implementsってのはインターフェイスを実装するのではなく、仕様を実装するんだ。

では、上記のような問題に対応するにはどうすればいいか? 当時考えていたのは、

  1. まったく同じシグネチャであっても、異なった仕様なのであれば、別のインターフェイスを用意すべき。→ DRY原則に觝触する気がして、なんかイケてない。
  2. Mapをラップして、Mapを実装しない新しいクラスを作る。→ 1よりマシだが、委譲メソッドを書きまくるのが冗長。

当時考えていたのはここまでなんですが。先日同僚と話していた時に出たアイデア。これでいいじゃんっw (名前は深く考えてない。要リファクタ。)

public class MyMap extends HashMap<String, String> {

    public String getValueNotNull(String key) {
        String result = get(key);
    return result == null ? "" : result;
    }

}

継承して、別のメソッド(ある意味インターフェイス)を作る。このメソッドを使いたいときはMyMap型で保持して、このメソッドを使用すればいい。

解決方法は簡単だった。だが、やっぱり「仕様違反実装はダメなんだよ」ってのが浸透していない気がする。よくありがちな問題なのに、聞いたことないもん、この手の話。

インターフェイスってのは、Javadocも含めて、出来上がりなんですね。逆に言うと、Javadocの無いインターフェイスは、意味がないとも言える。インターフェイスにはシグネチャしか無いので、通常以上に仕様の存在が重要になってくる*1インターフェイスは、メソッドが存在する事を保証する為にあるのではなく、仕様を保証する為にあるんだー。

結論:Javadoc超重要

身の回りのコードに、Javadocの無い(仕様の不明な)インターフェイスはありませんか? 大丈夫ですか?

# ああ、インターフェイス指向設計、早く読みたいが時間が取れない>< ゴメンナサイ

*1:読めば分かる、が通用しない。