JavaもJava8でstream処理やラムダ、Optionalの型などが追加されモダンなプログラミングが幾つか書けるような機構が適応されてきました。
しかし、Javaで書くプログラマーはモダンな実装する文化は浸透しているのでしょうか?
今回はモダンな実装の仕方として、「Immutable Programming」と「nullの扱いについて」について書いてみようと思います。
前提
Webアプリケーションサーバを実装する時を想定しています。
もちろんネイティブの実装のときにも適応される部分が多いです。
Java8の書き方で説明します。
Immutable Programming(イミュータブルプログラミング)とは
凄く簡単に説明すると、変数に再代入しないということです。
全てfinalを付けてプログラミングするようなものですね。
まだ、やったことがない人はそんなことが出来るのか?ということを感じるかもしれませんが、Webアプリケーションはほとんどのケースで再代入が必要はありません。
なぜならブラウザのHTTPの処理は1リクエスト1レスポンスであり、
(Request) -> Response の変換を行っていると考えることが出来るでしょう。
また、stateを管理する場所はsessionやdbなどに限られます。
つまり、ほとんどがstream処理のmapなどで変換処理で記述出来ると思います。
最近ではインフラまでImmutableにしようと言う流れもあり、Immutable Infrastructureも注目されています。
Immutableであることのメリットが大きいと考えられている証拠とも考えられます。
Immutable Programingのメリット
オブジェクトは生成されると値が変わることはありません。
つまり、メソッドの引数などで受け取ったものは他の処理を通っても変更されることがないため、デバッグしやすく、コードの見通しが良くなります。
なぜなら、メソッドを呼び出した後に受け渡したものやインスタンス中身など、どこかの分からない変数が書き換えられるということがなく、新しく変わることは戻り値で受け取った変数だけになるからです。
Immutable Programingのデメリット
Classのオブジェクトの中の1つの変数を変更するためにインスタンスを丸ごと作り直すという作業をするために、パフォーマンスが悪くなったり、メモリ効率が落ちるといえます。
ただ、現在のサーバスペックを考えると、誰かがバグを踏んで調査や修正の時間を掛けたり、機能追加のときに広いスコープでコードを理解して作業する人的コストを書けるよりもサーバのスペックを向上させてしまったほうが最終的なコストを抑えられるケースがほとんどです。
もしパフォーマンスが問題になった場合にはその箇所のみを修正する方が現実的なメリットが大きいです。
パフォーマンスに考慮したロジックに変更する際も、スコープがしっかり定義出来ているため、きれいに修正出来ることが多いでしょう。
実践方法の考え方
基本的にはstreamのmapのような変換処理を繰り返していくことになります。
オブジェクトの生成
基本的な考えとしてはオブジェクトは生まれた瞬間から完璧に利用できる形にすると言うことです。
どうすればそれが出来るかと言うと、コンストラクタでオブジェクトが存在するためのパラメータを全て渡すことです。
JavaにはJavaBeansと言う悪しき仕様(Immutableの視点から言うと)があります。
setterがありますが、これはJavaBeansの仕様に従って動作するライブラリ以外では使ってはいけません。(最近ではprivateなパラメータに書き込めるライブラリも増えました)
new をした後にsetすると言うことは、newとsetの間に不安定なオブジェクトが生まれています。
また、setメソッドがあるということは、どこかのメソッドで、どのタイミングで、何の値がセットされるか分かりません。
setterが存在するだけで、デバッグの範囲が広がってしまいます。
Immutable Javaを促進するためのツールやライブラリとしてLombokの紹介
Qiiaで探せば技術的な使い方を書いている人がたくさんいるので、細かなセットアップや使い方はそちらに任せて、Immutableの考え方で使うときのケースのみ書きたいと思います。
val型
型推論をしてくれて、自動的に変更できない変数にしてくれます!イミュータブルの神!
val x = "hogehoge";
Getterアノテーション
自動的にフィールドの変数に対するgetterを追加します。
class Hoge {
@Getter
private Integer id;
@Getter
private String name;
}
AllArgsConstructor アノテーション
自動的にフィールドの変数を全てを引数として取るコンストラクタを生成します。
@AllArgsConstructor
class Hoge {
private Integer id;
private String name;
}
RequiredArgsConstructorアノテーション
インスタンスを生成するstaticメソッドを追加します。
@RequiredArgsConstructor(staticName="of")
class Hoge {
private Integer id;
private String name;
}
Integer id = 1;
String name = "ジョジョ";
Hoge obj = Hoge.of(id, name)
Valueアノテーション
@Getterや@AllArgsConstructorのアノテーションなどをまとめて付与した効果が得られます。
結論
代入不可にするValueを使いまくればOK。
Nullの扱いについて
ここから話はガラッと変わってnullの扱いです。
最近のプログラミング言語は値が存在することと存在しないことをはっきりと表現して、プログラミングすることが当たり前になってきつつあると思います。
こちらの記事に詳しく解説されていますのでより知りたい方は参考にどうぞ。
Nullのケースが有ることをコードで表現するメリット
私が考える一番のメリットは、その変数が必ず値が入っているものか、値が入っていないこと(nullのケース)があるかがはっきりとコード上に表現できると言うことです。
Java8からはOptionalが導入されました。
これによって、必ず値があるケースはそのままの型。値がないケースがあるならOptionalの中に値が入っている状態と言う表現が出来ます。
DBのテーブルのカラムでnon nullはそのままの型、null可の場合にはOptional[カラムの型]でソースコード上で表現することが出来ます。
そして値がないケースの処理を強制させることが出来ます。
このプログラミングをマスタすればNullPointerExceptionが起きることはほぼ無くなるでしょう。
ただ、残念ながらスマートに書くための機能が足りないと感じています。
JDK9 のOptional
柔軟に書くためにメソッドが追加される予定です。
個人的に待っています。
ifPresentOrElseメソッド
現在のOptionalでは安全を確認する処理と取り出す処理を分離して処理する方法しかありません。
Optional o = something;
if(o.isPresent()) {
// 値が入っているときの処理
Hoge hoge = o.get(); // <- o.get()は安全ではない!!!
} else {
// 値がないときの処理
}
個人的にですが Optionalのgetメソッドは基本的に使えなくなって欲しいくらいです。
なぜorElseメソッドを書く必要が無いのにOptionalに包まれているのか?
宣言や仕様が間違っているのは明らかです。
でも!Java8ではスマートに書けないんです。。悲しい。
しかし!Java9ではこのように書くことが出来ます!
Optional o = something;
o.ifPresentOrElse(
hoge ->{
// 値があるときの処理
print("this is " + hoge)
},
() ->{
//値がないときの処理
print("none")
}
);
streamメソッド
Optionalの値があるときにだけ後続のmapなどの処理に値が渡ります!
これはスマートに流れるように処理を記述出来るようになること確実です!
Java9が待てないあなたに。
Scalaを勉強してみると関数型プログラミングの考え方やImmutable Programingが浸透しているため、より良いコードが書けるようになるかもしれません。
どんな言語でも、ただ動くだけではなくスタイリッシュで芸術的でありながら、実用的なコードを目指してプログラミングを上手くなりたいです!
最後に
モダンなコーディングについてはもっと知りたいので、より良い考え方を知っている方がいましたらコメントなどもお待ちしています。