
Indeed.com の Java コード2014-12-14
10年間Javaを書いていた僕が Effective Java 第2版を読み返して新人に薦められるのかを考えてみたという記事に寄せて、ここ 1 年半くらい働いているIndeed.comの環境をシェアしたら面白いのではないかと思ったので記事を書きました。
Effective Java に対するコメントでは無く、Effective Java に対する、上記記事のコメントに対するコメントです。なので、未読であれば上記記事を先に読んだ方が話が分かりやすいかと思います。
環境で言うと、以下のような環境です。まあ前提が大分違うので、結論も違って当たり前なのですが、そういうものもあるんだと参考にしてもらえればと思います。
項目 1 コンストラクタの代わりに static ファクトリーメソッドを検討する
これは相当実践されてるなという感じです。上述の環境からかなり防御的なプログラミングが多く、不変クラスを実現するためにビルダーがよく使われます。
Google Guava とか、Google Protobuf を好んで使っている職場な事もあって、Builder の命名などはそれに習っているので、使う側になった場合もそんなに困ることはありません。
enum でシングルトンを強制する、というか言語上 enum のシングルトンが保証されている、という理解ですが、シングルトンにするために enum を使うというよりは、enum が適切なシーンでは極力 enum を使っていく事が多いです。
例えば、パラメータとして取りうる値が 3 種類なら、それは 3 種の値を持った enum が妥当だよね、という感じです。String でパラメータを受けるようにしちゃうと、バリデーションのロジックと実際の処理を行うロジックがまぜこぜに "してしまいがち" になるけれど、enum だったらそこは強制的に分かれるし見通しが良いね、とか、ライブラリに渡したり、他のロジックから受け取るときも enum なら "それらの値にしかなり得ないことが保証されている" から安心っていうのもあります。
細かいところだと、必要なところで EnumSet とかを使うとパフォーマンスも有利だしとかまあ色々です。
シングルトン云々の話題からは離れてしまいましたね。純粋なシングルトンでないとどうしてもいけないっていうケースは確かに出会ったことないです。
パフォーマンス上、シングルトンにしておくと有利だからそうする、みたいなケースがほとんどじゃないでしょうか。
これはケースバイケース。常に private にするなんて窮屈すぎるので、public コンストラクタも普通に使って良いと思ってます。もちろんファクトリ、ビルダーの時は private にして当然ですが、シンプルなクラスだったらそんなに目くじら立てる事もないのかなぁと。DI して interface と implements を切り分けるときはまた別のお話ですね。
まあ過去の経験から言えば、参加するエンジニアのレベルが予測できない場合の防御策としてはありと思います。
こういうのは、基本やっておいて損はないのかなと思ってます。特にライブラリ寄りだと、そいつはループの中から呼ばれるかも知れないぜ?っていうのもあるので、不変オブジェクトを static private に追い出すとかは良くやります。昨今、メモリの方が CPU より安いと考えた方が良いケースも多いですし。ただ、Web やってる人はマルチスレッドのことを常に忘れてはいけませんね。
同意。こんなん Web サービスで残しておいたら命取りです。
ファイナライザ?何それ美味しいの?
項目 8 equals をオーバーライドする時は一般契約に従う
ちゃんとやりますよ。equals のオーバーライド自体滅多にやらないですけどね。
いや、これはやらない方がいいんじゃ?toDebugString とか toHtml とか toJsonString とか目的別の名前推奨したいです。
clone はもうレガシー。ファイナライザくらい何それ (ry という世界な気がします。
それとか、コピーが欲しいクラスでビルダーが採用されてたりしたら、MyClass.Builder.of(instance).build(); 的な感じになったりします。
compareTo 実装するなら、equals とか hashCode も一緒に実装したいところですね。
項目 13 クラスとメンバーへのアクセス可能性を最小限にする
これは最初に書いた現在の開発環境に大きく影響を受けてますが、ガンガンにそうしてます。あとからやっぱ private じゃない方が良かったー!ってなったら、その時に親クラスの方を変えてしまえる環境だからですね。ただ Unit Test を書くための package private (デフォルト) もよく使います。とはいえその時は必ず、@VisibleForTesting アノテーションを付けるようにする、っていうくらい private に出来るものは極力そうするっていう感じです。
ケースバイケースですかねー。個人的には、閉じた単一プロダクトの中では継承。コード全体の見通しが悪くなる外部ライブラリ内のクラスに対しては移譲、っていう使い分けが多いように思いますが、例外もいっぱいです。
そもそも、前述の通りの環境なので、移譲が必要そうになったらライブラリ側に手を入れてそっちで継承させてしまったり、という事もしますし。
あぁ、この辺ゆるいですね。いいんだか悪いんだか、昨今の IDE の静的解析が優秀なので、ドキュメントがなくてもソースが簡単に追えるからぶっちゃけ困ってないというか、いやドキュメントがあった方が最初は楽なんだろうけど、それが最新状態とは誰も保証してくれないから結局ソース読むよね、っていう。
その辺を意識してか、全社のコードリポジトリの全文検索エンジンがあったり、jar をリポジトリから依存解決するときに、デフォルトでソースもセットで落ちてくるようになってたり、なるべく少ないコストでソース追えるような環境にはなってます。
元記事読んで思ったのは、Indeed の開発環境はやはり防御的だな、という感じです。これまでに働いていた環境ではここまでというのは無かったのですが、まあ慣れてしまえば書くのもおっくうじゃないですし、メリットを享受するだけになるので、そういうスタイルが普通になってきました。
こういうスタイルで書きながらも、高速な開発サイクルを回せてるのはすげぇなぁと思うところです。
そういえば、Java は開発おせーんだよ、Python 使え Ruby 使えだのいう話がありますよね。
でも一方で、じゃあそれらは過酷なパフォーマンス負荷に耐えられるんだっけ?という話もあったり、言語にこだわるよりいわゆる 10 倍速いプログラマ雇うことにこだわった方がいいんじゃない?とか、Java はもはや IDE とセットで一つの言語と言っても過言で無いので、IDE まで含めて考えると言うほど生産性悪くないよ?むしろ静的解析のおかげで、コードベースが大きくなったときは明らかに Java の方が楽だよ?とか色々です。
トータルで見るとまだまだ Java の牙城は揺るがないのかなぁと思います。
Effective Java の話に戻ると、なんだかんだ今の自分の思考/指向とも合致することが多いので、初学者にも勧めて良い本だなと、元記事と同じ結論になりました。
Effective Java に対するコメントでは無く、Effective Java に対する、上記記事のコメントに対するコメントです。なので、未読であれば上記記事を先に読んだ方が話が分かりやすいかと思います。
環境で言うと、以下のような環境です。まあ前提が大分違うので、結論も違って当たり前なのですが、そういうものもあるんだと参考にしてもらえればと思います。
- バージョン管理には git を採用している。
- 基本自分のメインとなるプロダクトはあるものの、どこのプロダクトにあるどのコードに対しても誰でもコミット・プッシュして、マージリクエストを送って良い (権限としてはマージも可能だけれども、そこは紳士協定。)
- 1つのプロダクトは平均して3〜4人、色んな人がいじりたがる全社でメインのプロダクトだと、同時 (同じ週) に10人以上がコードをいじる事もある。
- 全てのプログラマが、Effective Java 的な本を読んでいるわけではないが、素養としては間違いなくそれを使いこなせるレベルにある。
- 全てのコードは (コメントを消す、import を整理するなどの超基本的なものを除き) 誰かしらにコードレビューをされてからマージされる。
- プロダクトは jar として利用されるライブラリと、それ自体が war としてウェブアプリになるものとに分かれている。
- 週 1 以上、頻繁に本番リリースされるので、アジャイル的な開発工程になっている。
項目 1 コンストラクタの代わりに static ファクトリーメソッドを検討する
項目 2 数多くのコンストラクタパラメータに直面した時にはビルダーを検討する
これは相当実践されてるなという感じです。上述の環境からかなり防御的なプログラミングが多く、不変クラスを実現するためにビルダーがよく使われます。
Google Guava とか、Google Protobuf を好んで使っている職場な事もあって、Builder の命名などはそれに習っているので、使う側になった場合もそんなに困ることはありません。
項目 3 private のコンストラクタか enum 型でシングルトン特性を強制する
enum でシングルトンを強制する、というか言語上 enum のシングルトンが保証されている、という理解ですが、シングルトンにするために enum を使うというよりは、enum が適切なシーンでは極力 enum を使っていく事が多いです。
例えば、パラメータとして取りうる値が 3 種類なら、それは 3 種の値を持った enum が妥当だよね、という感じです。String でパラメータを受けるようにしちゃうと、バリデーションのロジックと実際の処理を行うロジックがまぜこぜに "してしまいがち" になるけれど、enum だったらそこは強制的に分かれるし見通しが良いね、とか、ライブラリに渡したり、他のロジックから受け取るときも enum なら "それらの値にしかなり得ないことが保証されている" から安心っていうのもあります。
細かいところだと、必要なところで EnumSet とかを使うとパフォーマンスも有利だしとかまあ色々です。
シングルトン云々の話題からは離れてしまいましたね。純粋なシングルトンでないとどうしてもいけないっていうケースは確かに出会ったことないです。
パフォーマンス上、シングルトンにしておくと有利だからそうする、みたいなケースがほとんどじゃないでしょうか。
項目 4 private のコンストラクタでインスタンス化不可能を強制する
これはケースバイケース。常に private にするなんて窮屈すぎるので、public コンストラクタも普通に使って良いと思ってます。もちろんファクトリ、ビルダーの時は private にして当然ですが、シンプルなクラスだったらそんなに目くじら立てる事もないのかなぁと。DI して interface と implements を切り分けるときはまた別のお話ですね。
まあ過去の経験から言えば、参加するエンジニアのレベルが予測できない場合の防御策としてはありと思います。
項目 5 不必要なオブジェクトの生成を避ける
こういうのは、基本やっておいて損はないのかなと思ってます。特にライブラリ寄りだと、そいつはループの中から呼ばれるかも知れないぜ?っていうのもあるので、不変オブジェクトを static private に追い出すとかは良くやります。昨今、メモリの方が CPU より安いと考えた方が良いケースも多いですし。ただ、Web やってる人はマルチスレッドのことを常に忘れてはいけませんね。
項目 6 廃れたオブジェクト参照を取り除く
同意。こんなん Web サービスで残しておいたら命取りです。
項目 7 ファイナライザを避ける
ファイナライザ?何それ美味しいの?
項目 8 equals をオーバーライドする時は一般契約に従う
項目 9 equals をオーバーライドする時は、常に hashCode をオーバーライドする
ちゃんとやりますよ。equals のオーバーライド自体滅多にやらないですけどね。
項目 10 toString を常にオーバーライドする
いや、これはやらない方がいいんじゃ?toDebugString とか toHtml とか toJsonString とか目的別の名前推奨したいです。
項目 11 clone を注意してオーバーライドする
clone はもうレガシー。ファイナライザくらい何それ (ry という世界な気がします。
それとか、コピーが欲しいクラスでビルダーが採用されてたりしたら、MyClass.Builder.of(instance).build(); 的な感じになったりします。
項目 12 Comparable の実装を検討する
compareTo 実装するなら、equals とか hashCode も一緒に実装したいところですね。
項目 13 クラスとメンバーへのアクセス可能性を最小限にする
項目 14 public のクラスでは、public のフィールドではなく、アクセッサーメソッドを使う
項目 15 可変性を最小限にする
これは最初に書いた現在の開発環境に大きく影響を受けてますが、ガンガンにそうしてます。あとからやっぱ private じゃない方が良かったー!ってなったら、その時に親クラスの方を変えてしまえる環境だからですね。ただ Unit Test を書くための package private (デフォルト) もよく使います。とはいえその時は必ず、@VisibleForTesting アノテーションを付けるようにする、っていうくらい private に出来るものは極力そうするっていう感じです。
項目 16 継承よりコンポジションを選ぶ
ケースバイケースですかねー。個人的には、閉じた単一プロダクトの中では継承。コード全体の見通しが悪くなる外部ライブラリ内のクラスに対しては移譲、っていう使い分けが多いように思いますが、例外もいっぱいです。
そもそも、前述の通りの環境なので、移譲が必要そうになったらライブラリ側に手を入れてそっちで継承させてしまったり、という事もしますし。
項目 17 継承のために設計および文書化する、でなければ継承を禁止する
あぁ、この辺ゆるいですね。いいんだか悪いんだか、昨今の IDE の静的解析が優秀なので、ドキュメントがなくてもソースが簡単に追えるからぶっちゃけ困ってないというか、いやドキュメントがあった方が最初は楽なんだろうけど、それが最新状態とは誰も保証してくれないから結局ソース読むよね、っていう。
その辺を意識してか、全社のコードリポジトリの全文検索エンジンがあったり、jar をリポジトリから依存解決するときに、デフォルトでソースもセットで落ちてくるようになってたり、なるべく少ないコストでソース追えるような環境にはなってます。
まとめ
元記事読んで思ったのは、Indeed の開発環境はやはり防御的だな、という感じです。これまでに働いていた環境ではここまでというのは無かったのですが、まあ慣れてしまえば書くのもおっくうじゃないですし、メリットを享受するだけになるので、そういうスタイルが普通になってきました。
こういうスタイルで書きながらも、高速な開発サイクルを回せてるのはすげぇなぁと思うところです。
そういえば、Java は開発おせーんだよ、Python 使え Ruby 使えだのいう話がありますよね。
でも一方で、じゃあそれらは過酷なパフォーマンス負荷に耐えられるんだっけ?という話もあったり、言語にこだわるよりいわゆる 10 倍速いプログラマ雇うことにこだわった方がいいんじゃない?とか、Java はもはや IDE とセットで一つの言語と言っても過言で無いので、IDE まで含めて考えると言うほど生産性悪くないよ?むしろ静的解析のおかげで、コードベースが大きくなったときは明らかに Java の方が楽だよ?とか色々です。
トータルで見るとまだまだ Java の牙城は揺るがないのかなぁと思います。
Effective Java の話に戻ると、なんだかんだ今の自分の思考/指向とも合致することが多いので、初学者にも勧めて良い本だなと、元記事と同じ結論になりました。
カテゴリ: Development