2020/04/21

Lombok を使って本質的なコーディングに集中しよう

Lombok を使えば、いわゆるボイラープレート (言語仕様上しかたなく書くコード) を減らすことができます。

コード量の多さは罪深い

Java プログラムを書いていると、冗長なコード (ボイラープレート) を書かないといけないことが多いですよね。最近の Java はコード量の削減に取り組み始めていますが、以前の Java はそういったことにはまったく興味がないようでした。DTO や Immutable な VO を作るときのコード量の多さはうんざりします。

IDE による自動生成もありますが、コードを書くことに変わりはなく、変更があったときの自動再生成忘れのトラブルなどもあって、なかなかストレスフルです。

見積方程式 (COCOMO) の記事で書きましたが、システムの規模 (コード量) はコスト、開発期間に影響を与えます。単純なコードであっても、あるいは自動生成したコードだとしても完全にブラックボックス化できていて面倒を見る必要が無くなっていない限り、コストと期間を増大させます。例えば equals() を自動生成することはできますが、equals() を開発者の視界から除去することはできません。目に入ること自体がコストになりますから、コスト、期間に影響が出ます。

Lombok とは

そのような Java の状況を一変させたのが Lombok です。2009年頃から世に出てきました。
Lombok では、アノテーションの指示に従って様々なボイラープレートを自動生成します。プリプロセッサのようにソースコードを自動生成するのではなく、コンパイラと一体で動作します。javac や Eclipse Compiler for Java といったコンパイラの提供する API を介して、構文解析に介入して処理が行われます。
使用者からすると、Java+Lombok という新しいプログラミング言語を使っている感覚です。Lombok により削減されたコードは、プログラマの意識から完全に消えますから、コスト、期間に影響する形で真に削減されたと考えることができます。

Lombok の衝撃を見てみよう

以下のようなプログラムがあったとします。

public final class PointLombok {
  private final int x;
  private final int y;
  public PointLombok(final int x, final int y) {
    this.x = x;
    this.y = y;
  }
  public int x() {
    return this.x;
  }
  public int y() {
    return this.y;
  }
  @Override
  public boolean equals(final Object o) {
    if (o == this) return true;
    if (!(o instanceof PointLombok)) return false;
    final PointLombok other = (PointLombok) o;
    if (this.x() != other.x()) return false;
    if (this.y() != other.y()) return false;
    return true;
  }
  @Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    result = result * PRIME + this.x();
    result = result * PRIME + this.y();
    return result;
  }
  @Override
  public String toString() {
    return "PointLombok(x=" + this.x() + ", y=" + this.y() + ")";
  }
}

x, y という2つの int フィールドを持つ Value object (VO) です。Immutable になっていて、コンストラクタでのみ値が設定できます。VO なので final クラスです。Getter ではなく、get 接頭辞無しのアクセサが用意されています。Immutable なので Setter はありません。VO ですから equals, hashCode もきちんと実装する必要があります。toString() も要りますね。

長いですね。これが Java の冗長さです。これを Lombok で書くと、こうなります。

@lombok.Value
@lombok.experimental.Accessors(fluent = true)
public class PointLombok {
  int x, y;
}

劇的に短くなりました。
@lombok.Value と言明するだけで、Value object に必要とされる性質を一通り自動的に定義してくれるわけです。

Lombok のインストール

インストールについて詳しく説明するのは、やめておきますが、いくつかポイントがあるので簡単に。

手順としては Using lombok を見ると分かりますが、基本的な考え方としては、以下の3つの仕込みが必要となります。

  1. コンパイラに lombok を仕込む (コード生成のため)
  2. IDE に lombok を仕込む (Java+Lombok という新言語に対応させるため)
  3. 開発対象のコンパイル時のクラスパスに lombok を仕込む (lombok.* アノテーションを認識させるため)
例えば Eclipse の場合だと、lombok.jar をインストーラとして使用すれば、1, 2 は完了します。3 はプロジェクトのクラスパスに lombok.jar を含めてあげる必要があります。

Gradle プロジェクトの場合は、1 に対応するのが annotationProcessor の設定です。IDE じゃないので 2 は無くて、3 に対応するのが compileOnly の設定です。それぞれ test の方も設定します。

この基本的な考え方をおさえておけば、困ったときの指針になると思います。

Lombok の機能

最後に主要な機能を紹介しておきます。他にもいろいろありますが、あとは Lombok features を見てみてください。
  • @Data
    • Mutable な DTO を作ります
  • @Value
    • Immutable な VO を作ります
    • 趣旨は JEP 359 で Java 14 に Preview 版が導入された、record に似ています
  • @Getter, @Setter, @EqualsAndHashCode, @ToString
    • その名の通りの要素を個別に生成します
  • @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
    • 様々なパターンのコンストラクタを生成します
  • @Builder
    • Immutable なオブジェクトを逐次処理的に作るビルダーを生成します
  • @With
    • Immutable なクラスに setter 風のメソッドを付けます
    • 実際には、指定したフィールドの値のみ差し替えた新しいオブジェクトを作るメソッドになっています (だから Immutable なままです)
  • @Log4j. @Slf4j, @CommonsLog, @Log, ...
    • 様々なロギングフレームワークの Logger フィールドを生成します
  • var, val
    • 型推論で自動的に型を決めてくれるローカル変数宣言です
    • var は non-final、val は final です
    • var は Java 10 から JEP 286 により Java 標準仕様になっています