雑多に技術メモと他色々

主に自分用な技術メモが多くなる気がする。他色々が書かれるかどうかは不明。

Lombok修飾したNestedな内部クラスを使ってビルド失敗した時の解決法

最近ハマったlombokでビルド失敗するケースの解決法。
どうも内部クラスの扱いには若干問題があるらしいので、メモっとく。

環境

問題の概要

Lombok使ったコード書いた→Mavenビルドした→なんかコンパイル失敗するんだけど…
出力されるのは下記のようなエラーメッセージ。

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.6.0:compile (default-compile) on project lombok-test: Compilation failure: Compilation failure:
[ERROR] /C:/hogehoge/lombok-test/src/main/java/com/sample/lombok_test/NestedInner.java:[20,10] シンボルを見つけられません
[ERROR] シンボル:   クラス Data
[ERROR] 場所: クラス com.sample.lombok_test.NestedInner
[ERROR] /C:/hogehoge/workspace/lombok-test/src/main/java/com/sample/lombok_test/NestedInner.java:[21,10] シンボルを見つけられません
[ERROR] シンボル:   クラス Builder
[ERROR] 場所: クラス com.sample.lombok_test.NestedInner

いやいやDataとBuilder見つからないってことは無いでしょ、lombokちゃんと引き込んでるしというのが第1印象。
ちなみにLombokインストールしたEclipseでコード書いてる間は怒られず、mavenビルドなんかを実行したタイミングで問題が発覚する。
Eclipseが怒ってくれないのはECJと純正コンパイラの処理の違いだろうか。

問題が発生するサンプルコード

下記のようにLombok修飾された内部クラスをネストして、同クラス内でLombok生成された処理にもアクセスしたコードを書いた場合に問題が発生した。

package com.sample.lombok_test;

import com.sample.lombok_test.NestedInner.Nested1.Nested2;

import lombok.Builder;
import lombok.Data;

public class NestedInner {

	public static Nested1 createNestedObj() {
		return Nested1.builder()
				.name("Nested1Name")
				.nested(Nested2.builder()
						.name("Nested2Name")
						.build())
				.build();
	}

	@Data
	@Builder
	public static class Nested1 {
		private String name;
		private Nested2 nested;

		@Data
		@Builder
		public static class Nested2 {
			private String name;
		}
	}
}

StackOverflowあたり見ると、Nestedなクラスに対してはLombokは正常処理しないケースが潜在しているみたい。
java - Javac fails to compile annotations on static nested classes that have a public enum - Stack Overflow
importing nested static method breaks compilation · Issue #1249 · rzwitserloot/lombok · GitHub
import云々のコメントが多くimport com.sample.lombok_test.NestedInner.Nested1.Nested2;するようなケースがアカンのかな。
公式でもバグ報告としてIssueがいくつか上がっていたり、「lombok cannot fix this.」と言われてたり状況がつかみにくい。

最初ググったときは「maven-compiler-pluginのバージョンあげろ」とか「lombokのバージョン下げなくちゃダメじゃね?」とか出てきたけど、結局問題点が別のところにあって解決まで2,3時間くらい彷徨う羽目になった…

解決方法

1. 内部クラスを外に切り出す

内部クラスを抱えてアクセスもしているのが問題で、Lombok修飾されたクラス定義を外部に切り出せばあっさり解決する。
こんな感じでデータを持つクラスを外出しするのが解決法の一つ。

package com.sample.lombok_test;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class Nested1 {

	private String name;
	private Nested2 nested;

	@Data
	@Builder
	public static class Nested2 {
		private String name;
	}
}

2. 内部クラスをネストしない

内部クラスのネストを解消するとimport文に内部クラスが登場しなくなる。試してみた限りこれでも解決できた。
直接の内部クラスはimport不要だが、内部クラスの内部クラスはダメだってことか。

package com.sample.lombok_test;

import lombok.Builder;
import lombok.Data;

public class NestedInner {

	public static Nested1 createNestedObj() {
		return Nested1.builder()
				.name("Nested1Name")
				.nested(Nested2.builder()
						.name("Nested2Name")
						.build())
				.build();
	}

	@Data
	@Builder
	public static class Nested1 {
		private String name;
		private Nested2 nested;

	}

	@Data
	@Builder
	public static class Nested2 {
		private String name;
	}
}

3. GetterやSetterや他諸々を全部自作する

Lombok周りでエラー出ているなら、Lombok利用を諦めればそりゃ解決する。でも別の問題発生してるよね系。
前述の手段が取れない理由はそうそうないと思うので、基本的にこっちには倒れないように。

規約化などの雑感

同じような問題引かないようにするために、コーディングルールをどう定めるべきか。

  • 上記のような問題点があることを表明し「この問題を引かないようにコーディングすべし」というルールにする(やり方は各自考えられるよね系)。
  • Lombok修飾した内部クラスをネストする場合は1段までという規約にする(内部クラスの内部クラスにしない)。
  • Lombok修飾したクラスは専用のデータ定義クラスに切り出すことを必須とする(Lombok修飾したクラスを処理が書かれた場所で内部クラスにしない)。
  • 内部クラスをネストする場合は1段までという規約にする。
  • 内部クラスを作るなという規約にする。

プロジェクトの状況、担当者のレベル、人数の規模感などを考慮してどれかを規約化しておく形かな。
上から順に規約読んだ人が理解したり考えたりすることが多く、下に行くほど考えることは単純で盲目的に従う系の規約(おまじないとも言う)にしてみた。
個人的に一内部クラス使った方がわかりやすくなるケースも一部あると思ってるので、一律禁止はやりすぎだと思う。
データ定義クラスとして必ず切り出してね、あたりが現実的か。

まとめ(感想)

  • Lombokとても便利だけど乱用するとバグを引くときは引く
  • バグがあることに気がついたらそれを引かないようにするための規約でもWikiでもノウハウ化に落としこむ
  • 単純なバグにぶつかっただけでも解析するのに数時間コースは結構ある(だから情報残す)
  • Google先生は時にあらぬ方向の解決手段を提案するものと再認識(エラーメッセージ英語で検索すりゃいい解決法出るでしょと舐めてた)