これは Java Advent Calendar 2014 の13日目です。昨日は @skrb さんの「Duke で Swing」、明日は @bitter_fox さんです。
いきなりですが、テーマ変更します。ごめんなさい。Mayaaのことは書きません。
実は登録時には「Mayaaのことを書く」みたいなことを書いていました。確かにMayaaを扱って通算5年、かなりマニアックなことを書くこともできますが、マニアックなことを書いても誰も相手してくれないと思うので、そういうのは別の日にひっそりと書こうと思います。
そこで方針を変えようと思った時、僕の勤務先では開発者同士のコードレビューが盛んに行われ、特に現場に古くからいた僕は後から入ったほぼ全員の人のソースコードのレビューを求められて、日々の時間の多くをレビューに費やしていることを思い出しました。レビューを行う際はどうしてもこのコードは良いとか悪いとか自分なりにジャッジしなければなりません。それはその場の感覚で行っていましたが、何らかの指針を持っているはずです。そこで、それについて書いてみようと思います。
ネタとしては、駆け出しの頃読んで感動した、ある本を再読し、それを元に自分の今のコーディングに対する考え方を書いてみたいと思います。ある本とは、そうです。Effective Javaです。
よく「新人Javaエンジニアは Effective Java を読め」と言われてきました。
Effective Javaは一時絶版になりましたが丸善出版が再版してくれたため、また新人プログラマーにすすめることができるようになりました。以前からJavaで仕事するなら読んでおいた方が良いと言われる定番の一冊です。
僕は2004年ごろ初版を、2008年に第2版が英語で出たときは毎日少しずつ訳しながら読みました。(結局全部訳し終わる前に日本語版が出ちゃったのですが。。。)
初版は2001年、第2版は2008年の出版です。さすがに2014年現在では古いと言わざるをえません。しかし、プログラミングで大事なことは文法よりもその後ろにある思想にあると思います。
なおここから先は、あくまで僕の視点からのコメントです。立場の違う方からは違う意見も出ると思います。注意して書きますが、明らかに間違ってる場合はご指摘いただければ、幸いです。また、時間の都合上全部は無理で、はじめの方のちょっとだけになると思いますがご了承ください。
2014/12/15 追記:この記事に対して下記のブログから反響をいただいています。
この記事を公開後、Otchyさんがブログに記事を書いてくれました(ありがとうございます)。こちらも併せて読んでいただくことをおすすめします。
僕の立ち位置
個人的な感想を述べる都合上、予め自分の立場を明かします。
* 2004年よりJavaを使ったECサイト開発に従事し本格的にJavaコードを書き始める
* Java EEサーバーのようなエンタープライズ向け製品は使用していない
* 開発者2名という小さなチームで自社プロダクトを開発していた
* 今は10数名の開発者がコードを書いていて、自らもコードを書きつつ、レビューをしている
ちなみにタイトルで10年やってるとは言っていますが、Java7ですら今年になってから使い始めたレベルなので偏っていると思います。もっと短い経験でも僕よりJavaに詳しい方はいっぱいいると思います。
項目別コメント
項目 1 コンストラクタの代わりに static ファクトリーメソッドを検討する
項目 2 数多くのコンストラクタパラメータに直面した時にはビルダーを検討する
これら2つを組み合わせると「流れるようなインターフェース」と呼ばれたかっこいいAPIを実現できます。しかしこのようなAPIは使用者に対して、例えば「Hogehogeクラスのサブクラスは必ずコンストラクタを使わずにnewInstance()メソッドを使うこと」などの情報を徹底周知する必要があります。それを行う明確な理由があるなら採用するべきです。
OSSライブラリではインターフェースダサダサでは使ってもらえないので、より美しいインターフェースにすることで、利用者の同意を得るというプロセスになると思います。
JavaBeansパターンは暗黙に使い方を示しているところがあるので、可変性に注意して使えばカジュアルで良いと思います。
型パラメータの省略ができるというファクトリーメソッドのメリットは、ダイヤモンド演算子によってJava7ではなくなりましたね。
項目 3 private のコンストラクタか enum 型でシングルトン特性を強制する
enumシングルトンを見たことがありません。そもそも純粋なシングルトンが必要なケースってあまり良いイメージがないですがどうなのでしょうか?
項目 4 private のコンストラクタでインスタンス化不可能を強制する
個人的にはRhinoで
var Util = new Packages.my.domain.HogehogeUtil;
では何故か上手く行かず、
var Util = new Packages.my.domain.HogehogeUtil();
と書く都合上、この方法は使いません。別にユーティリティクラスのインスタンス化を制限しなくてもいいんじゃないかなあと思います。
項目 5 不必要なオブジェクトの生成を避ける
new String("hoge")や、new Boolean(true)のような明らかな悪手は取るべきではないですが、ストイックにインスタンス化を削減する努力が必要なのであればある程度は許容しても良いと思っています。
項目 6 廃れたオブジェクト参照を取り除く
項目 7 ファイナライザを避ける
同意
項目 8 equals をオーバーライドする時は一般契約に従う
項目 9 equals をオーバーライドする時は、常に hashCode をオーバーライドする
ほんとこれ、理解できない人はequalsをOverrideしないでくださいレベルです。
項目 10 toString を常にオーバーライドする
欠点の一つとして掲げられている「一度形式を明示すると未来永劫形式を変更できない」がまさに怖いことと、文字列化することが嬉しくないクラスもあるので、何も「常に」toStringを記述する必要はないのではないかと思います。一方で文字列表現が妥当(例えばHTMLパーサで要素型など)な場合は迷わずtoStringが書けると思います。
項目 11 clone を注意してオーバーライドする
タイトルは注意してと書いてありますが、本文を読むと「使うな」と言わんばかりに読めます。これを読んだあとのJava初級者は可変恐怖症、継承恐怖症を患うと思います。実際は、Java標準ライブラリでも開発しない限り問題に当たることはまれなので、cloneを絶対に作るなとは思いません。cloneの用途としては、そのオブジェクトの型が分かっていない時も、同じ型のインスタンスを作ることができるもっとも簡単な方法だと思います。
やはりタイトル通り「注意してオーバーライドする」が妥当なのだと思います。
項目 12 Comparable の実装を検討する
ほんとこれ、昔ドハマりしたので、イコールじゃないもののcompareToに0なんて返さないように気をつけてください。
項目 13 クラスとメンバーへのアクセス可能性を最小限にする
項目 14 public のクラスでは、public のフィールドではなく、アクセッサーメソッドを使う
項目 15 可変性を最小限にする
この章を読んだ学習者は、明日からprivateばかり書く可能性があります。privateにしておけば安全だと言わんばかりです。そんなとき僕は「なぜprivateなのか考えてprivateにするべき」といいます。例えば、継承されることを前提としたクラスのスーパークラス側にこんな記述があったとします。
class A {
protected void foo() {
hogehoge(1);
}
private void hogehoge(int x) {
// ~省略~
}
}
サブクラスでfooをOverrideしようとすると、どうなるでしょうか?
class B extends A {
@Override
protected void foo() {
hogehoge(2);
}
}
これはコンパイルエラーになります。hogehogeがprivateだからです。ではこの時「とにかくprivateにすべし」メンタルを持ったプログラマーはどんな対応を取るでしょうか?こんなコードを書いたりします。
class B extends A {
@Override
protected void foo() {
hogehoge(2);
}
private void hogehoge(int x) {
// ~省略~ (スーパークラスのhogehogeのコピペ)
}
}
そして、後に、別のプログラマーがhogehogeはをprotectedに変更したらどうなるでしょうか?privateからスコープを広げたのだから通常は問題がないと思うはずです。しかし、これはコンパイルエラーになります。「サブクラスがスーパークラスのメソッドを隠蔽している」ということになるためです。
実際はこの場合、Aというクラスを定義した時点でhogehogeはprivateにするべきではありませんでした。
このようにprivateは、不用意に使いすぎると、コードのコピペを促し、また、隠蔽されているからと品質の悪いコードがリリースされる可能性さえあります。なので自分は「どうしてもprivateにするべき事情がある場合に限って、privateにするべきだ」と言う言い方で、後輩たちを指導しています。
可変性についても同じです。僕も一時、不変性バンザイ厨になりました。でも実際はシリアライズ・デシリアライズ・リフレクションあらゆる方面からその不変性を揺るがしてくるので、今は諦めて「可変性に注意する」という立場にシフトしています。
項目 16 継承よりコンポジションを選ぶ
これは、なんというか、個人的にはJavaが悪いと思っています。ラッパークラスは僕もよく作りますが、これを作りたい場合、Eclipseなどの開発環境を使えば自動的に生成することもできますが、微調整するときに、大量のメンバーをポチポチコピペしてラッパークラスを作る必要があり、時間がもったいないしミスも起こりやすいです。そんなことをしているから、Javaは生産性が悪いと言われても、返す言葉がないのですよね。
個人的には、ライブラリ開発者はコンポジションを使って良いが、一アプリケーション開発者は、なるべくこのような努力をしなくても開発ができるようにフレームワークを設計するべきだと思っています。
項目 17 継承のために設計および文書化する、でなければ継承を禁止する
個人的には継承の禁止は継承を禁止するべき必要がある時にのみクラスにfinalを付けるべきだと思います。
まとめ
このあと項目 78までありますが、概ね言いたかったことを書けたと思うので、割愛します。
読み返してみるとこの本はかなり防御的に偏っている気がしました。この本の指示を従って書くと良いのは次のような事例では良いかと思いました。
- 一人又は少数のJava言語に精通した人が開発し、多くの人が利用しているライブラリ
- 仕様がほぼ確定し、リリース後に試行錯誤はあまりしない
一方で、多くの人は次のケースの現場でコードを書いているのではないでしょうか?
- 複数の人が開発し、全ての人がEffective Javaを読んでいるわけでもなく、かつ、全てのコードを一人の人がレビューしているわけでもない、ゆるやかに共同作業しているケース(Web系の現場でありがち)
- それが最終的なアプリケーションとしてリリースされるのみでjarファイルが静的にどこか別のプロジェクトでライブラリとして再利用されるわけではない
- 一回のリリースで完成させるのではなく、反復的にリリースすることでフィードバックを得ながら開発方針を修正している
つまり多少厳密でなくても高速に開発サイクルを回すことを優先したい場合は、ここで推奨されているスタイルをある程度崩す勇気が必要だと思いました。どこまで崩すかの判断には崩すことのメリット・デメリットを理解していなければなりません。それには、多くの現場を経験し、多くのコードを書いた経験が必要でしょう。たった一冊の本を読んだだけで体得できるものではないでしょう。
では、Effective JavaはJava学習者にとって良書なのでしょうか?
この本はJavaでコードを書く上の罠をよく示しています。罠は知らないで踏むと大変危険です。そういった意味で、Effective Javaを読んだレベルになって初めて議論に参加できるステージに立てるのだと思います。新人プログラマーの方々にはやはり、早く一緒に議論がしたいという願いを込めて、Effective Javaを推薦したいと思います。
追記
検索していたら訳者の柴田さんがこんなことを書かれているのを見つけました
『Effective Java 第2版』は、やはり初心者向けではない [プログラミング言語Java教育]
この「クラスが適切にパラメータ化されていれば、ClassCastException がスローされます。」の1文を読んですぐに理解できる受講生はいません。この1文を理解するのに必要な知識はすでに学んでいるのですが、その知識を応用して、すぐに理解するのは非常に難しいようです。
ああ、確かにこういうところわかりにくいかもしれないですね。Genericsの章はずっと先にあるので、なるほど、言われてみればそうだなあと思います。むしろ、こういうレベルまで細かく見れる人は全然問題無いと思います。多くの場合、枝葉のことは分からず、とにかく「Effective Javaに書いてあった」という理由で判断してはいけないと思うだけであり、かつて自分はそうであったという戒めでもあるのです。
多くのハウツー本は、斜め読み+気になった箇所だけ精読というスタイルの読み方が良いと思っていますが、Effective Javaに関しては、斜め読みだけでは不十分で、何度も読み返して良い本だと思いました。