HN 日本語サマリー

← 一覧へ戻る
プログラミング

LombokはJava Recordsの時代でも依然として関連性がある

Lombok Is Still Relevant in the Age of Java Records (swaranga.dev)

8 pointsby swaranga5 コメント

要約

この記事は、Java開発者の間でLombokの使用に対する批判的な意見に反論しています。著者は、Lombokがコンパイラに「ハック」を導入するという批判や、Java Recordsの登場により不要になったという主張は、現実世界のアプリケーション開発においては的外れであると主張しています。Lombokは15年以上にわたり、バグの根本原因となったのは一度だけであり、その場合も開発者のコーディングミスとテスト不足が原因だったと指摘しています。また、Lombokの@Slf4jや@RequiredArgsConstructorといった機能は、Java Recordsでは代替できない利便性を提供すると述べています。

全文翻訳

LombokはJava Records時代でも依然として有効である

6ヶ月ごとに新しいJDKがリリースされると、Javaコミュニティのイデオロギー的な純粋主義者たちが決まって現れ、Project Lombokについて文句を言い始めます。彼らは、Lombokがjavacコンパイラに対して「奇妙なハック」を行っていること、アップグレードを遅らせること、そしてRecordsがあるためもう必要ない、と主張します。

私は、その使用に対するこうした絶え間ない原則的な姿勢に少しうんざりしています。私はアプリケーション開発者であり、コンパイラエンジニアではありません。私はLombokを約15年間使用してきましたが、その利点が欠点をはるかに上回ることは明らかです。そのような教条的な意見を持つ人々と一緒に仕事をしていないことを嬉しく思います。なぜなら、結局のところ、私たちは動作するソフトウェアを届けるために給料をもらっているのであって、コンパイラを甘やかすためではないからです。

私は今でもLombokに賭けており、私にとって、それに対する議論は現実世界では通常的外れです。

Lombokは「奇妙なバグ」を導入する

10年半以上にわたるLombokの使用経験で、ポストモーテムでLombokが根本原因として指摘されたのは、私の知る限り1度だけでした。しかも、その場合でも、その非難は私の意見では不当でした。

@Dataアノテーションが付いたクラスにプリミティブなbooleanフィールドがありました。SQSからのメッセージをデシリアライズする際、受信したペイロードにこのフィールドが欠落していました。プリミティブとして定義されていたため、Jacksonはデフォルト値のfalseを割り当て、それが偶然別のコードパスをトリガーしてしまいました。

ポストモーテムではこれを「Lombokの問題」とラベル付けしました。

私は同意しません。 Lombokは単にセッターを生成しただけです。真の根本原因は以下の通りでした。

  1. 不適切なデータモデリング: そのフィールドは論理的に3つの有効な状態(true、false、null)を必要としていたため、最初からボックス化されたBoolean型であるべきでした。
  2. テストカバレッジの不足: このエッジケースは、テストですぐに検出されるか、PRレビュー中にフラグが立てられるべきでした。

開発者のコーディングエラーとテストカバレッジのギャップに対してLombokを非難するのは、単に怠慢です。

「もうRecordsがある」という神話

Lombokを擁護するたびに、誰かが必ず「Recordsを使えばいい!」と口を挟んできます。

Recordsはイミュータブルなデータキャリアには素晴らしいですが、Lombokの完全な代替にはなりません。これを証明するために、現在のプロジェクトで実際に依存しているLombokの機能を確認しました。

以下がその生データです。

find . -name "*.java" | xargs cat | grep "import lombok" | sort | uniq -c | sort -n -r

    144 import lombok.extern.slf4j.Slf4j;
    131 import lombok.NonNull;
    120 import lombok.RequiredArgsConstructor;
     68 import lombok.Builder;
     30 import lombok.Data;
     24 import lombok.Getter;
     22 import lombok.NoArgsConstructor;
     22 import lombok.AllArgsConstructor;
     12 import lombok.experimental.Accessors;
      8 import lombok.extern.jackson.Jacksonized;
      6 import lombok.EqualsAndHashCode;
      4 import lombok.AccessLevel;
      2 import lombok.Singular;
      2 import lombok.experimental.UtilityClass;
      1 import lombok.ToString;
      1 import lombok.extern.log4j.Log4j2;

そのリストの上位を占めているのは@Dataではありません。ロギングと依存性注入をサポートするコードです。

@RequiredArgsConstructorなしでコーディングすることは想像できません。Springでは、インジェクトされるサービスをファイナルフィールドとして宣言し、そのアノテーションを1つ追加するだけで、コンストラクタのボイラープレートコードが消えます。Recordsはこれを全く解決しません。@Slf4jと@Builderを組み合わせると、モジュールごとに数百行にも及ぶ、面倒でエラーを起こしやすいボイラープレートコードを節約でき、それらを自分で書いたり、レビューしたり、保守したりする必要がなくなります。

IDEがゲッターやセッターを生成することについてはどうでしょうか?あなたはまだその散らかったコードを読む必要があります。さらに、新しいフィールドを追加するということは、equalsとhashCodeメソッドを手動で再生成することを忘れないようにする必要があるステップであり、忘れやすいステップです。

Lombokの「ダークマジック」

純粋主義者は、LombokのAST操作は「マジック」であり、コンパイラの動作を変更することはアーキテクチャ上の罪であると主張します。しかし、これらの開発者自身が、実行時にさらに悪いことをするフレームワークの上に、エンタープライズシステム全体を構築することを喜んで行っています。

例えばJacksonを見てみましょう。Javaの純粋主義者は、すべてを明示的にし、JVMのルールに厳密に従うことを望みます。しかし、JacksonがJSONペイロードをデフォルトコンストラクタを持たないオブジェクトにデシリアライズする際、しばしばリフレクション(またはObjenesisのようなライブラリ)を使用して、コンストラクタを一切呼び出さずにそのオブジェクトのメモリを割り当てます。あなたは、Lombokがコンパイル時に予測可能なsetName()メソッドを生成することに腹を立てますが、実行時にJVMのオブジェクト生成ライフサイクルを完全にバイパスするフレームワークについては全く問題ないのでしょうか?

あるいはHibernateとJPAを見てみましょう。エンティティで標準的なList<Order>を定義し、それに対してビジネスロジックを記述します。しかし、実行時には、Hibernateはあなたのオブジェクトを静かにインターセプトし、標準のJava ArrayListを独自のPersistentBagプロキシに置き換えます。データベースセッションが閉じられた後にそのリストをイテレートしようとすると、アプリケーションは恐ろしいLazyInitializationExceptionでクラッシュします。

これは、標準的なデータ構造が不可視の方法でハイジャックされる、より不透明な実行時の魔術です。それに比べれば、Lombokは信じられないほど退屈です。生成されたコードは完全にコンパイル時に存在します。.classファイルを見ることができます。生成されたものを正確に確認でき、ゲッターが値を返した場合、それをステップ実行できます。もし私たちがJacksonやHibernateの巨大で不透明な実行時メカニズムを、それらが提供する開発速度のために許容するつもりなら、Lombokに対しても全く同じ実用主義を適用すべきです。AOPも同様に多くの落とし穴があるツールです。AWS SWF(Simple Workflow)Flow Frameworkの実装でTry-Catchブロックをデバッグしていた際、TryブロックとCatchブロックが文字通り異なるマシンで実行されていたことを思い出します。

将来のJDKリリースはLombokのコンパイラハックを完全に破壊するだろう

まず、アップグレードのパニックは誇張されています。あなたが初日からアップグレードを望んでいるのでない限り、Lombokは通常、メジャーリリースから数日以内にパッチが適用され、準備が整います。(私はすでにJDK 26をLombokで使用しています)。

しかし、最悪のシナリオが発生し、Lombokが将来のJDKから永久に禁止されたと仮定しましょう。その日が来たら、私の移行戦略は非常にシンプルです。

  • ソースコードに対してdelombokを実行する。
  • 生成されたボイラープレートコードをコミットし、依存関係を削除する。
  • 別のPRでJDKをアップグレードする。

実行時のロックインはゼロです。その仮説上の終末の日が来るまで、私は喜んで自分の時間を節約し、書く必要のないコードを書くことをスキップします。純粋さは理論上は素晴らしいですが、実用主義が機能を出荷します。