可視性と公開APIと非公開(内部)APIと

Javaではpublic, protected, default, privateという4種類の可視性がある。

Javaを始めてしばらくの間、この4つの使い分けがよくできていなかった。

「外から呼ぶならpublic、呼ばないならprivate」時代

当時から、なるべく可視性は下げた方が良い(オブジェクト指向は「隠す技術」である → 継承とコンポジット - 都元ダイスケ IT-PRESS参照)ということは理解していたので、「外から呼ぶならpublic、呼ばないならprivate」という指針からスタートした。

上記に加えて「継承先からしか呼ばないならprotected」時代

Template Methodパターンを覚えた頃の話。この時代が一番長かった。

そして残ったひとつ、default(package-private)の使い方が全く分からなかった時代でもある。色々使おうと頑張ってみたが、package分けが大きく影響してくるので、指針がしっかりしていないと上手く使いこなせない。

その頃のエントリ。デフォルトスコープが上手く使えない - 都元ダイスケ IT-PRESS

というかこの時代って、「protectedとdefaultってどっちが可視性高い(public寄り)んだっけ?」としばしば混乱していた時代だw さらに、protectedは継承先からしか見えない。パッケージが一緒でも継承していないと見えない、と思い込んでいた時期だな。(実際はprotectedは同じパケ内であれば見える)

インターフェイスを理解して何とか4スコープを使いこなす時代(現在)

エンジニアのためのJavadoc再入門講座 現場で使えるAPI仕様書の作り方

エンジニアのためのJavadoc再入門講座 現場で使えるAPI仕様書の作り方

この本だったっけな(適当w)。公開APIと非公開(内部)APIについての記述があった。記憶を頼りに、さらに自分の考えを補足して整理してみる。

可視性 API Javadoc 備考
public 公開 必須 外部に「ご自由にお使いください」と公開するメソッド。誰がどんな使い方をしても文句は言えない。さらに、外に使わせるのであれば、使い方をセットにして示すのがスジだろう。つまりjavadocだ。使い方を制限したい場合は、javadocにて釘を刺す必要がある。ちなみに「外部」というのは、自分が作った外のクラスだけではなく、このメソッドを見ることができる全てのプロジェクトのコードだ。
protected 公開 必須 これも外部に公開するメソッドだ。publicと違うのは、外部(上記参照)には継承した時しか見えない。オーバーライドして動きを微調整するためのメソッドによく使う。逆にというかつまりというか、継承さえすればいつでもこのメソッドを外部が見ることができるので、公開APIであることは確かだ。つまり、publicと同じように使い方を明示する必要がある。
default 内部 無くても良い 外部に公開しない、コンポーネントに閉じた実装の詳細メソッド。なぜなら、このパッケージ名を使えるのは基本的に自分だけ(自分が保有するドメインをパッケージ名に使っているはずだ)だから。privateとの違いは、情報隠蔽をクラスの境界で区切るのではなく、パッケージの境界で区切る点だ。また、隠蔽境界の外から見た場合、privateのように意固地に「絶対見せない」という訳ではない。境界の外側の立場の人(使う側)がこのパッケージに侵入してきて、自己責任でハックすることを許す、多少緩い対応だと考えることができる。
private 内部 無くても良い 完全にクラス内に閉じた実装の詳細。

見づらい表になっちまったかなw

可視性というのは、読んで字のごとく「見える範囲」を表すものなのだが、逆に「変更が影響する範囲」と言い換えることができる。privateは同じクラス内にしか見えない。つまり、そのメソッド(のシグネチャや処理内容)を変更する際は、そのクラスにしか直接影響が出ない。そのクラスが壊れていないことさえ確認すれば、修正してOKだ。

defaultは、同じパッケージ内にしか見えない。そのメソッドを変更する際は、そのパッケージ内にしか直接影響が出ない。そのパッケージ内のクラスが壊れていないことさえ確認すれば、修正してOKだ。

さてここでpublic,protectedについてが問題だ。こいつらは、どこからでも見える、つまりあらゆる所に変更の影響を出してしまう。誰が使っているか分からない、誰が継承しているか分からないクラスだ。

まぁ、プロプライエタリなプロジェクト内部であれば、使っている箇所を全部検索することも可能だが、OSSとしてリリースするライブラリだった場合は、誰がどう使っているかなんてのは知りようがない*1。つまり、変更してしまったら、どの部分を壊してしまうか分からない。つまり、堅い考え方をすれば「一度リリースした公開APIは、二度と変更不可能」なのだ。このルールを守らないと、互換性が維持されていないということになり、「ライブラリのバージョンを上げたら動かなくなった」というトラブルの元になる。

ちなみに、私の知る限り、EclipseAPIは上記のルールに厳密に則っており、メジャーバージョンアップの時以外は公開APIに変更は入らない。バージョンアップしたらプラグインが動かなくなった、とか困るから。

publicなインターフェイスなんてのは非常に扱いが難しい。一度リリースしたら、既存のメソッドを削除することも、新しくメソッドを追加することも許されない。前者は、「このインターフェイスのメソッドを呼んでいたコード」のコンパイルを壊すことになるし、後者は「このインターフェイスをimplementsしたクラス」のコンパイルを壊すことになる。誰がimplementsしてるか分からないからね〜。

そのせいで、EclipseのIDocumentProviderExtensionというインターフェイスは、

  • IDocumentProviderExtension
  • IDocumentProviderExtension2
  • IDocumentProviderExtension3
  • IDocumentProviderExtension4
  • IDocumentProviderExtension5

という妙なインターフェイスがいっぱいあるキモい状況になっているのだ…。最後の番号はバージョン番号なんだな。

まとめ

  • 型(クラスやインターフェイス等)やメンバ(フィールドやメソッド)を作る際、こいつは公開APIなのか非公開(内部)APIなのか、常に意識せよ。
  • 公開APIは基本的にリリース後に変更してはならない。

Jiemamyも、このルールに則りたいとは思っているのだが、互換性の維持っつーのは非常にコストがかかる。本当は変更してはならないんだけど、ゴメンナサイっ!!って言いながら変更しなければメンテナンスが追いつかない。v1.0リリースまでの間は、完全な互換性の維持は難しそうだ…。なるべく頑張るけどね…。

ただ、v1.0出したら、そのAPIの互換性は死ぬ気で維持する所存であります。

*1:こう書くと「なんだOSSの話か、仕事にゃ関係ねー」と誤解されそうだが、「プロプライエタリだが、自社内で使う共通ライブラリ」だった場合も「どのプロジェクトでどう使われているか」は追跡が難しいと思う。他で使われる心配のないプロプライエタリプロジェクトに閉じたクラスであっても、同じ統一されたルールでコーディングしておくと混乱せずに済むと思う。