昔、チームで使うコーディング規約を作ったことがあったのを思い出した。
あったこと
そのチームでは僕が入る以前にも Java のコーディング規約を持っていたんだけど、新しいプロジェクトで初めて Java 6 へ移行するにあたり、規約を見直してほしいということだった。
その中に、個人的には納得いかない箇所があった。
メソッド引数にコレクションなどの可変 (mutable) な型を受け取る場合、メソッド内で変更しないのであれば、
public void doSomething(ReadOnlyList<T> l) { // 略 }
のように、読み取るだけだということがわかるようにすべき、という内容だった。
ここで、引数の ReadOnlyList
は、List
インタフェースのうち読み取り系のメソッドのみを定義した、独自のインタフェースを指していた。
public interface ReadOnlyList<T> { public T get(); public Iterator<T> iterator(); // 以下略 }
これは、このチームで独自に考えたわけではなくて、どこかのサイトで公開されていたコーディング規約を踏襲しているらしかった。
個人的には、このルールにはいくつかやだなと思うポイントがあるんだけど、特にこの ReadOnlyList
というインタフェースの命名が気に食わなかった。
規約見直しを僕に依頼してきた人に、そのことを説明したんだけど、「ちょっと何言ってるかわからないですね」とサンドウィッチマンばりの回答が返ってきて、納得してもらえなかった。
僕が思うインタフェースの命名
少なくとも Java SE の API で、インタフェースの名前って、そのインタフェース自体の説明じゃなくて、それを implements
しているクラス (の一側面) を説明したものであるべきだ、と思う。
もう少し別の言い方にすると、インタフェースの名前は、それを implelements
するクラスの性質 (のひとつ) に名前を付けたものであるべきだ。
なぜなら、インタフェースは、「こういう性質のものは、こういう操作ができる」という定義を示すものだからだ。
public class KuriManju implements Cloneable { // 略 }
これは、KuriManju
クラス (のインスタンス) が複製可能であることを示している。包含関係ではないので、KuriManju
の中に Cloneable
な部分がある、みたいな話ではない。
複数のインタフェースを implements
する場合もある。
public class KeeperDiary implements Readable, Writable { public int read(); public void write(char c); // 略 }
Readable
インタフェースや Writable
インタフェース自体が、readable = 読み出せたり、writable = 書き込めたりという性質を持っているわけではない。それを implements
している KeeperDiary
クラス (のインスタンス) が、そういう性質を持っているということだ。
Readable
インタフェースも Writable
インタフェースも、KeeperDiary
の一側面を表しているに過ぎない。Readable
を implements
しているから読み取りしかできないとか、Writable
インタフェースを implements
しているから書き込みしかできないということはない。両方できる場合もあるし、読み書き以外の操作ができることだってあるだろう。
そして、仮に、読み取り可能であるという側面だけをメソッドに公開したいのなら、Readable
インタフェースを使えばいい。
public void doSomething(Readable r) { // 略 }
冒頭の例に戻って言うと
コーディング規約を見直したときに、ReadOnlyList
という命名が気になったと書いた。
だってさ。これを implements
したら、違和感があるよ。
public class FriendList<T> implements ReadOnlyList { // 略 }
こう書いてしまったら、FriendList
は読み取り専用のリストであるっていう字面になってしまう。
読み取りだけでなく書き込みもできる場合は、こうなるのか?
public class FriendList<T> implements ReadOnlyList, WriteOnlyList { // 略 }
FriendList
は読み取り専用リストであると同時に、書き込み専用リストでもある……んなわけあるかー!!
というのが、僕が ReadOnly〜
という付け方がいやだと思った理由。
補足: 他の理由
冒頭で説明したコーディング規約は、命名以外にもいやなポイントはある。
自分たちが変更できない既存のクラスには ReadOnlyList
を implements
することができないのだから、この規約に沿うためには、Java SE API に用意されているクラスは使わずに、ただ「特定のメソッドがリストを変更しないこと」を示したいだけで、既存のクラスを派生した独自のクラスを用意して、リストを作りたいときはそちらを使う必要がある。
public class RidiculousArrayList<T> extends ArrayList<T> implements ReadOnlyList<T> { // 略 }
また、java.util.Arrays.asList(T...)
のように API やライブラリが List
を返す場合も、当然それは ReadOnlyList
を implements
していないから、引数の型として ReadOnlyList
を要求するメソッドには直接渡せない。少なくともラッパ (wrapper) が必要になるし、さらに悪いケースでは、内容をコピーしてしまう (ReadOnlyList
を implements
していない List
の内容を、している List
に全部コピーしてしまう) だろう。
こうなってくるとですよ。
確かに、「メソッドが引数の中身をいじるかどうかを示したい」という動機はわかる。わかりづらいケースがあるのもわかる。
でも、そこを明示するためにと言って ReadOnlyList
インタフェースを作るという対策を取ると、本来不要な記述があちこちで必要になってしまい、かえってバグを産んだり、パフォーマンスが低下することになる可能性が増えてきませんか?
そこを考慮した上で言ってますか? してないですよね?
ちなみに
なんでこんな過去の話を、今更思い出して書いているかというと、.NET に IReadOnlyList
/IReadOnlyCollection
/IReadOnlyDictionary
というインタフェースがあることを、今日知ったからだ。
これはどっちの意味なんだろう。つまり、読み取り専用のリストやコレクションやディクショナリのためのインタフェースなのか、冒頭のコーディング規約のような変な命名なのか。
具象クラスであり可変のリストを示す System.Collections.Generic.List
が IReadOnlyList
を実装しているようなので、たぶん後者だろう。
この命名は失敗だと思う。あーあ。やっちゃったね。