package private についてふと思ったこと。

ふと思ったので自分用にメモしておく。思い付きなので深く考えずにとりあえず殴り書きするだけっす。

あるオブジェクト内部の情報を外部に開示したくない場合に、情報隠蔽は有効な方法だと思われる。しかし、特定の相手には開示したいという場合もあり、その場合には java なら package private が利用できる。

おいら的には今は kotlin でのコーディングが大半を占めるようになったので、internal は使えても package private は使えない。kotlin 転向後に結構この点は心に引っかかったし、コミュニティでもこの点のディスカッション(Kotlin to support package protected visibility)が続いている。(たぶん永遠に終わらないwww)

当初はおいらも package private があったほうが良い派の意見だった。しかし、少なくとも現時点では全く困っていない。むしろ package private は多くの場合に保守性を低下させる要因になると考えるようになった。

package private 導入を望む人たちの意見は基本的に以下のコメントに集約されていると思われ。

if i understand it correctly your suggestion implies that if i want to have 1 public class and 9 package protected that do the actual job in java, i have to create a separate project declare those 9 as internal and then have this project compiled and accessible using Maven or Gradle or whatever one uses for the DI?

Imho that's an overkill...

Note that if i do 10 classes in one file 9 private i wont even be able to test those 9.

I personally never saw any code working around package protected access using same package name... it will just differ from your regular package structure and will constantly remind devs "fix me this is a shitcode".

言ってることはもっともだし、確かにそういうケースは多いと思う。しかし、言語の自由度を高くしたいという要求であれば、極論を言えばアセンブラを選択するべきであり、java vm 限定ならバイトコードを直接書くべきという結論に行きつく。当然ならがそういう話ではないわけで、行きつく先は、自由度をどのレベルに設定するかという話になる。

言語仕様がどうあるべきかという話なら、その言語がどのような設計方法論を支持するものなのかを考える必要があるんじゃなかろうか。

話は逸れるが、上記以外のコメントで java 言語仕様にあるんだから入れろ的なニュアンスの話もあったが、Java 言語が発表された 1995年当時と今では普及している設計方法論が異なるので、互換性のために java vm 仕様を踏襲しろというのは良いが java 言語仕様をそのまま新しい言語に取り込めというのは暴論だと思われる。

話は戻るが、べき論や抽象論で討論しても平行線なので、上記コメントにある、1つの public class が 9つの package private class を参照しているというケースをどのように扱うかという点について考えるのがこのディスカッションの落としどころになるんじゃないかという気がする。

この点において、ディスカッションは平行線を辿っている。『module にすればいいじゃん』 → 『それは overkill だ』的な感じでw。

あらゆる視点で考えるほどおいらはこの問題について考える気はないし、おいら自身が困っているわけじゃないのでどちらが正しいかというのも正直どうでもいい。しかし、おいらが困っていない理由だけは自分用にメモしておく。

『1つの public class が 9つの package private class を参照している』というようなケースに関して、それをおいらは package private にしようと思わない。理由は以下。

  • public A と package private の B, C というクラスのグループと、public D と package private の E, F というクラスのグループがあるとして、それらのグループ単体で package private を利用できるとする。その際、A と D を同一パッケージにしたいが、A から E, F、D から B, C にアクセスさせたくないとしたら、package private では対応できない。このような設計の拡張が無いことを保証するには、プロジェクトのソースコードの永久凍結宣言をするか未来予知ができるエスパーを雇わない限り無理。つまり、現代の多くのプロジェクトで採用されるクラス設計方針との相性が悪いという話。おいら的には、kotlin 転向後に、package private が使えないため auto completion の候補が多くなってウザーとか思った時期もあったが、これは慣れた。そもそも、上記の破綻例を解決できるようなパッケージングシステムがあったとしたら、現行の package private 方式もウザーとなるはず。慣れの問題ですね。
  • もう一つの理由は、(っていうか実はこれが唯一最大の理由と言ってもいいのだが)、package private を利用したい場合の大半は、静的な設計と動的な設計をごちゃまぜにしてるんじゃないだろうかという話。静的な設計の例としては、Stack 上に push, pop メソッドを公開して内部データは隠蔽するというような場合。この情報隠蔽は必要で、情報隠蔽をしないと Stack が外部要因によって自身の使命を果たせなくなる。クラス自身が自分の使命を全うするのは自身の責務。では、クラスAが package private のクラスBとCを参照するというケースで package private を public にしたらカプセル化が崩れるのかという点については、これは否となる。クラスとして B や C が外部から静的に参照可能でも全然困らない。重要なのは、動的なオブジェクトとして A が B, C を隠蔽していることである。*1

蛇足となるが、最後の理由はクラス設計の一般論としても重要で、動的視点を用いてアクセス制御を行おうとすると、クラス単体を設計する際にアプリケーション全体の動的設計まで考慮する必要が生じ、しかも未来予知ができるエスパーを雇わない限り保守性が著しく低下してしまう。現状のおいらの指針としては、モジュール設計はクラス設計の前に一通り完了させ*2、モジュール内のクラスはデフォルトで internal とし、実際に外部から呼ばれる必要が生じた段階で public にし、private に関しても実際にそうする必要が生じた際に適用するという感じですかね。

追記

  • CodeComplete にも "Avoid friend classes" っていうのがありますね。C++ の話だけど java でいうところの package private と同様に考えていいんじゃなかろうか。

*1:具体的なモノで例えるなら、Car クラスと Engine クラスがあるとする。世界で初めての車で、Engine はその車用に作られた概念とする。現状では Engine が使いまわされるなんて思いもしないとする。この状態で Car を 運転者に対するモノとしてとらえた場合、Car が Engine をカプセル化し、同時に情報隠蔽が行われるのは妥当。現時点だと Engine は Car のみにしか使われないので、Engine は package private で扱われるのも妥当に見える。しかし、Engine というクラス自体が public であっても全く困らないので、package private にする妥当性は全体的な視点によるものになる。つまり、Engine のクラス設計をする際に、Engine 外(具体的には Car)の事情を踏まえて考えてしまっているということであり、概念レベルでは Engine が Car に依存しているということになってしまう(要は syntactically independent だが semantically dependent ということ)。ソースコード上では単方向の参照だが、概念設計レベルでは双方向参照になってしまっている。これは概念設計上致命的な問題じゃなかろうか。実際に、Engine を列車に適用しようとした途端に package private は破綻する。Car と Engine の例は極論だという反論も出そうなものだが、それなら自社内の自分のプロジェクトだけで使うおそらく一度きりしか使わない特殊な形式のテキストファイルを処理するエンジンだった場合はどうだろうか。このような場合でも、そのエンジンが将来的に拡張されるかどうかは未来予知能力がない限りわからないし、分からないということは package private にする妥当性が無い(むしろ設計レベルでの保守性を低下させる)ということに他ならない。

*2:一通り完了させるというのは完成版ができるという意味ではなく、もちろん後で手戻りする可能性は織り込み済み。そのうえで、トップダウンの視点でモジュール内の主要クラスの洗い出し程度までの詳細化の範囲内のベストエフォートで完成させるという意味。『イテレーションの中で詳細化するから今は大雑把でいいやw』とか考えたら後で死にます。