tokuhirom's Blog

LL から Java に移行した人がはまりがちなこと

こんにちは。Java 初心者です。

Java 初心者、得に LL から Java に来た人にありがちな問題について社内向けに書いたものをオープンアンドシェアさせていただきます。

前提として、我々は Java 8 でガンガン攻めているということをご承知おきください。

また、自分がこの数ヶ月で「うわー。こうしとくべきだったのかー」と気づいたやつをドヤ顔で語っているということにもご注意ください。

【追記】 対象は中規模 B2C の場合です(中規模というのは facebook より小さいという程度の意味です)

例外を握りつぶさないようにしよう

Eclipse が生成する以下のようなコードをそのまま残しているケース。 これは言うまでもなく良くないですね。デバッグが困難になります。

try {
} catch (IOException e) {
	e.printStackTrace();
}

Perl でいうと eval { ... }; warn $@ if $@; みたいなもんですからね。。

賞味、ウェブアプリケーションを書いている場合、何も考えたくない時は以下のように RuntimeException でくるんで rethrow してあげるのがいいです。

try { ... } catch (IOException e) {
	throw new RuntimeException(e);
}

フレームワークがなんか適当にエラーページ出してくれます。

基本的に、ウェブアプリケーションではリカバリ可能な例外というものがほぼ無いんで、何も考えずに RuntimeException にしちゃって問題ないです。たいがいの場合は。(バッチ処理やワーカーの処理の場合はこの限りではありません)

null を return しないようにしよう

Java 8 で書いている場合、基本的に null を返してはいけませんね。Optional 使いましょう。

Optional 型は、null か null じゃないものかが入っているコンテナです。Java でメソッドを書いている場合、基本的にいつでも null を返せるわけですが、null にたいしてメソッドを呼び出すと NullPointerException が発生します。

毎回 null かどうかをチェックするのはめんどくさいし、まあ意味が無いケースも多いわけです。 そういうわけで、null を返したいケースでは Optional 型を利用して、null が返る可能性を明示して、それ以外のときには基本的に null を返さないようにする、ということが肝要となってまいります。

ライブラリから null が返ってくるケースはままあるわけですが、それをアプリケーションコード内のメソッドから返却してはいけません。

@NonNull を活用しよう

lombok を使ってる場合、@NonNull というアノテーションいれるだけで、引数に対する null check が簡単にできるんで、気が向いた時に入れておくと、どこから null が混入したかがわかりやすくなって便利だったりします。

リソースを明示的にクローズしましょう!

Perl では Reference Count GC が全面的に採用されており、リソースはその変数が参照されなくなった時点で即座にリリースされます。 ファイルも $fh がスコープから外れたらすぐにクローズされます。

しかし、Java の場合にはそうではない!!ちゃんと明示的に閉じて上げる必要があります。 しかし、明示的に .close メソッド呼ぶとかだるいので、try-with-resources を使いましょう。

try-with-resources を使いまくろう

Java 7 で導入された try-with-resources 構文はめちゃんこ便利なので必ず覚える必要がある。

以下のように書くと、try を抜けるタイミングで、自動的に br.close() が呼ばれます。

static String readFirstLineFromFile(String path) throws IOException {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  }
}

昔の Java の本や、インターネッツにのってるコードだと以下のようなコードがよくありますが、今ではずっと簡潔に書くことができるのです。

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
  BufferedReader br = new BufferedReader(new FileReader(path));
  try {
    return br.readLine();
  } finally {
    if (br != null) br.close();
  }
}

try-with-resources を使っていて、ウェブアプリケーションを書いている限り、基本的に finally block を書くことはほとんどありません。finally を書きたいなーと思ったら、一度立ち止まってよく考えてみるか、誰かに相談してみるといいと思います。

(Note. try-with-resources は、Guard の代わりにも使えますね)

Object 型をできるだけ避けよう

どうせ Perl だったら返り値なんか Object 型相当なんだし、Object 型をたまには返してもいいよね?と思う向きもあると思いますが、実際にはアプリケーションコードで Object 型を返却しなければいけないケースは現在ではほぼありません。だいたいはジェネリクスにより、うまいことなんとかなります。

なんとかならないケースがあったら、たぶんだいたいはよく調べたらなんとかなるので、誰かに聞いてください。

FindBugs を使おう

FindBugs は Perl でいうと Perl::Critic に対応するもの、JS でいうと jshint に相当するものです。 ありがちなコーディングミスをひと通り教えてくれます。

人間によるコードレビューを受ける前にひと通り findbugs でチェックしておきましょう。

findbugs の利用は maven を使っている場合は

mvn clean compile findbugs:check

とするか、pom.xml に以下のように書いて、mvn site && open target/site/index.html するのがいいかと思います。

<reporting>
	<plugins>
		<plugin>
			<groupId>org.codehaus.mojo</groupId>
			<artifactId>findbugs-maven-plugin</artifactId>
			<version>3.0.0</version>
		</plugin>
	</plugins>
</reporting>

Effective Java を読もう

Effective Java って、なんか Java 6 対応!! とか書いてあるし、めっちゃ内容古そうに見えますが、実際まあ、よく出来ている書籍で、ハマりどころをひと通り教えてくれます。 (実際は訳注で Java7 に関するフォローも入っています)

Effective Java は Effective Perl というよりは Perl Best Practices に近い書籍なんで、ひと通り読んでおくといいと思います。

というか、読んでなくてダメなコードを書いていると後ろ指さされる感じあるので、読んだほうがいいです。

マルチスレッドで動いていることを意識してコーディングしよう

Perl の場合には基本的にアプリケーション・サーバーは pre-fork モデルで実行するのが普通です。 pre-fork の場合は、各プロセスの間ではデータは共有されません。

しかし!! Java の場合は multi-thread モデルで、各リクエストごとに thread が割り当てられます。 他のスレッドと変数を共有することが可能となっているのです。

これはつまり、スレッド間での競合が起きるとまじひどい目にあうということです。 ですから、基本的に、スレッド間でのデータ共有をできるだけ避けてコーディングするようにしたほうがいいです。

明示的にスレッドセーフであると宣言されているライブラリ以外はスレッドセーフではないと考えよう

たまに、「何回か動かして問題ないからスレッドセーフだろ。。」みたいな考え方をする人がいるのですが、そういうことではないです!

ごくたまに起きるスレッド競合とかあるとまじ最悪な感じになりますので、「このライブラリはスレッドセーフです!」と明示的にマークされているもの以外は、スレッド間で共有しないようにしましょう。

JSON を文字列で書かずに Jackson で生成しよう

なぜか知らないけど、Java で書くときになると JSON を手で書いて文字列でハードコードしたくなる人がいます。

これは、invalid な JSON をハイて、ひどい目にあうリスクがあるんで、基本的に Jackson などにお任せしましょう。

printf デバッグの代わりにロガーを使おう

System.err.println() とか System.out.println() でデバッグするのはやめましょう。 かわりに slf4j のロガーに出しましょう。

あと、そもそも printf デバッグを避けて debugger を使うようにしていきましょう。

jax-rs でクラスとメソッドに分けて Path 定義するのを避けよう

クラスに @Path("/blog") してメソッドに @Path("/{id}") つける、みたいなコーディングが jax-rs だと可能なんですが、こういうふうに書くのは grep-ability が低くなってよくないので、@Path("/blog/{id}") みたいに書くのが僕は好きです。

ここは好みの面もあると思いますけど、メソッド側にまとめて書いたほうがメンテナンス性が高いと、僕は思います。

Diamond inference を使おう

Java7 以後では Diamond inference で簡潔に書けるので、簡潔に書いたらいいです。

List<Foo> foo = new ArrayList<Foo>();

List<Foo> foo = new ArrayList<>();

Javadoc を書こう

クラス/メソッドにコメント書きましょう。Java の場合、メソッド補完するときに javadoc のコメントが見えるんで、書いといたほうが開発効率が後々よくなってきます。

Hibernate はインメモリキャッシュを勝手にやるので注意

Data::ObjectDriver 的なメモリキャッシュを持ってくれるので、気をつけよう!

デフォルトだと、セッションの間でだけ有効なインメモリキャッシュが有効になっているはず。 一部で、「よっしゃここは SQL で高速化やでー」といって hibernate バイパスして SQL 直接ぶっこんだりするとひどい目にあう気がしている。

警告をなくそう!

warnings が出ているのひと通り治しましょう。警告出たままにしておくと、重要な警告を見落とす結果になってしまいます。 引き継ぎした案件で warnings 出まくってると、めっちゃなえます。

Java の警告、そもそもなんでそんなに怒ってるのかわからないというケースもままあると思うんで、そういうときはスクショをとって他のひとに聞くとよいです。

Jackson で constructCollectionType じゃなくて TypeReference を使おう

Jackson は以下のように constructCollectionType とか使えますが、可読性が低いと感じるのが僕です。

JavaType type = mapper.getTypeFactory().
  constructCollectionType(List.class, Foo.class)

↓↓こっちのほうが好き。

List<Foo> list = mapper.readValue(jsonString, new TypeReference<List<Foo>>() { });

後者のほうが、読みやすいが、無名クラスを一個つくることになるというデメリットはあります。 ただまあ、そんなデメリットでもないんで、別によいのでは。

(これは好みの問題でもあるんで、どっちでもいいっちゃいいです)

自分で考えすぎないで人に頼ろう

社内の hipchat に Java っていう名前のチャットルームがあるので、入って聞いたらいいです。 だいたいすぐに誰か答えてくれると思います。

基本的に自分でがんばって考えても無駄なんで、どんどん人に聞くのがよいと思います。

【追記】 Reflection をさける

アプリケーションコードで Reflection を使いたくなったら、それはたぶん何かが間違ってる可能性があるというか、Java っぽくないコードを書こうとしているケースな可能性が極めて高いので、だれかに相談してください。