Java

Lombokの使い方と@Dataの危険性

AIによる要約

LombokはJavaの定型コードを減らせる便利なライブラリです。ただし、@Dataを無条件に付けると、getter、setter、toStringequalshashCode、コンストラクタがまとめて生成され、現場では思わぬ副作用になることがあります。

新人SE
新人SE
Lombokって、とりあえず@Dataを付けておけば楽なんじゃないですか?
ポンコツSE
ポンコツSE
楽にはなります。ただし@Dataは複数の機能をまとめて生成します。DTO、Entity、値オブジェクトで同じ感覚で使うとレビューで止まりやすいです。

Lombokは、getter、setter、コンストラクタ、equals/hashCodeなどの定型コードをアノテーションで生成してくれるライブラリです。

現場でもよく使われますが、初学者が最初にやりがちなのが、クラスに何でも@Dataを付けることです。

この記事のポイント

  • Lombokは定型コードを減らす道具であり、設計判断を消す道具ではない
  • @Dataは複数アノテーションのまとめ指定なので、影響範囲が広い
  • DTO、Entity、値オブジェクトで使うアノテーションは分けて考える
  • 新人は、生成されるメソッドを説明できる範囲で使う

Lombokは何を楽にしてくれるのか

Javaでは、フィールドに対するgetter、setter、コンストラクタ、toStringなどを書く場面が多くあります。これらを毎回手書きすると、コード量が増えて本当に読みたい業務ロジックが埋もれます。

public class UserRequest {
    private String name;
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Lombokを使うと、このような定型コードを短くできます。

@Getter
@Setter
public class UserRequest {
    private String name;
    private String email;
}

ここまでなら、比較的意図が分かりやすいです。getterとsetterを生成したいから、@Getter@Setterを付けています。

@Dataは何を生成するのか

@Dataは、単なるgetter/setter生成ではありません。Lombok公式ドキュメントでも、@ToString@EqualsAndHashCode、全フィールドの@Getter、非finalフィールドの@Setter@RequiredArgsConstructorをまとめたショートカットとして説明されています。

@Data
public class UserDto {
    private Long id;
    private String name;
    private String email;
}

この1行で、getter、setter、toStringequalshashCode、必要なコンストラクタが生成されます。便利ですが、生成されるものが多いため、意図しないメソッドまで作られる可能性があります。

Lombokの@DataがGetter、Setter、ToString、EqualsAndHashCode、RequiredArgsConstructorをまとめて生成することを示す図解。
@Dataはgetter/setterだけでなく、複数のメソッドをまとめて生成します。

@Dataでまとめて生成されるもの

  • 全フィールドのgetter
  • finalではないフィールドのsetter
  • toString
  • equalshashCode
  • finalや@NonNullフィールド向けのコンストラクタ

無条件に@Dataを使う危険性

@Dataが危ないのは、コードが短くなることではありません。そのクラスに本当に必要か分からないメソッドまで生成してしまうことです。

@Data
public class UserEntity {
    private Long id;
    private String name;
    private String passwordHash;
    private LocalDateTime lastLoginAt;
}

この例では、toStringに出したくない値が含まれるかもしれません。また、equals/hashCodeにログイン日時のような変更される値が含まれると、同一判定が不安定になります。

// レビューコメント例
// Entityに@Dataを付けると、setter、toString、equals/hashCodeがまとめて生成されます。
// passwordHashの出力や、可変項目を含む同一判定が危険なので、
// 必要なLombokアノテーションだけ明示してください。

DTOなら@Dataでよいのか

画面やAPIのリクエストDTOでは、@Dataが使われている現場もあります。ただし、DTOなら必ず安全というわけではありません。

入力DTOでsetterが必要なのか、レスポンスDTOで変更可能にしてよいのか、ログに出してよい項目が含まれていないかを確認します。

@Getter
@Setter
public class UserCreateRequest {
    private String name;
    private String email;
}

単にリクエスト値を受け取るだけなら、@Getter@Setterだけで十分なことがあります。equals/hashCodetoStringまで必要かは別問題です。

Entityに@Dataを付けるときの注意

EntityはDBの行と関係するクラスです。IDが採番前はnullだったり、フレームワークがプロキシを使ったりするため、equals/hashCodeの扱いが難しくなります。

そのため、Entityに無条件で@Dataを付けるのは避けた方が安全です。既存プロジェクトでEntityにどのアノテーションを付けているかを確認し、同じ方針に合わせます。

@Getter
@Setter
@NoArgsConstructor
public class UserEntity {
    private Long id;
    private String name;
    private String email;
}

このように、必要なものだけを明示する方が、レビューする側も意図を読み取りやすくなります。

@EqualsAndHashCodeは明示的に使う

equals/hashCodeが必要なクラスでは、@Dataでついでに生成するより、@EqualsAndHashCodeを明示する方が意図が伝わります。

@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class ProductCode {
    @EqualsAndHashCode.Include
    private final String value;

    public ProductCode(String value) {
        this.value = Objects.requireNonNull(value);
    }
}

onlyExplicitlyIncluded = trueにすると、含めるフィールドを明示できます。新人のうちは、何となく全フィールドを比較に入れるより、この形の方がレビューで説明しやすいです。

よく使うLombokアノテーションの判断軸

まずこの判断で選ぶ

  • 読み取りだけ必要なら@Getter
  • 外部から値を入れる必要があるDTOなら@Setterを検討
  • DI用コンストラクタなら@RequiredArgsConstructor
  • 値オブジェクトの同一判定なら@EqualsAndHashCodeを明示
  • 生成される内容を説明できないなら@Dataを避ける

Lombokを使う判断は、「短くなるか」ではなく「そのクラスにそのメソッドが必要か」で考えます。

// 判断例
// Service: @RequiredArgsConstructor
// DTO: @Getter / @Setter
// 値オブジェクト: @Getter / @EqualsAndHashCode(onlyExplicitlyIncluded = true)
// Entity: プロジェクト方針を確認。無条件の@Dataは避ける

@Builderも便利だが使いどころを見る

@Builderは、オブジェクト生成を読みやすくできる便利なアノテーションです。ただし、必須項目が分かりにくくなったり、不正な組み合わせを作れてしまったりする場合があります。

@Builder
public class SearchCondition {
    private String userId;
    private String status;
    private LocalDate from;
    private LocalDate to;
}

検索条件のように任意項目が多いクラスでは相性がよいことがあります。一方で、必ず必要な値が多い業務オブジェクトでは、コンストラクタやファクトリメソッドの方が意図を表しやすい場合もあります。

現場でのおすすめ方針

新人が現場でLombokを使うなら、最初は小さく明示的に使うのが安全です。@Dataでまとめるより、@Getter@Setter@RequiredArgsConstructorのように必要なものを選ぶ方が、レビューでも説明しやすくなります。

レビュー前のセルフチェック

  • @Dataで生成されるメソッドを全部説明できるか
  • toStringに出したくない情報が含まれていないか
  • equals/hashCodeに可変フィールドが含まれていないか
  • Entityでプロジェクト方針に反していないか
  • 必要なアノテーションだけに分けた方が読みやすくないか

この記事とあわせて読みたい

まとめ

Lombokは現場でも便利な道具ですが、@Dataを無条件に使うと、必要以上のメソッドを生成してしまいます。新人のうちは、クラスの役割ごとに必要なアノテーションを選び、生成されるメソッドを説明できる状態で使うのが安全です。

-Java
-, , , , ,