きしだのはてな このページをアンテナに追加 RSSフィード

2013-07-30(火) JavaでIDEのアクセッサ生成よりlombokを使ったほうがいい理由

[]JavaIDEのアクセッサ生成よりlombokを使ったほうがいい理由 12:09 JavaでIDEのアクセッサ生成よりlombokを使ったほうがいい理由を含むブックマーク

lombokは、JavaでのアクセッサやtoString、equalsなどボイラープレートなコードをコンパイル時に生成してくれるライブラリです。

ただ、こういったコードの生成は、IDEを使えば自動で行えるので、わざわざlombokを導入するまでもないと考えることもできますが、ぼくはlombokを導入するべきだと考えて、lombokを使うようにしました。

このとき「lombokを導入するべき」と考えた理由を書いておきます。


lombokとは

lombokは冒頭でも書いたように、Javaのアクセッサなどを生成してくれるライブラリです。

Project Lombok


import lombok.*;

@Setter @Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class LombokSample {
    private String foo;
    private int bar;
    
    public static void main(String[] args) {
        LombokSample ls = new LombokSample("ee", 12);
        System.out.println(ls.getFoo());
        System.out.println(ls);
    }
}

このように、アノテーションを付加すればアクセッサやコンストラクタ、toStringメソッドなどを生成してくれます。

f:id:nowokay:20130730105113p:image


NetBeansでの導入

Mavenプロジェクトの場合は、次のようなdependencyを付け加えれば、特になにもせずにlombok対応できます。

<dependency> 
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>0.12.0</version>
  <scope>provided</scope>
</dependency>

Antプロジェクトの場合は、ダウンロードサイトからダウンロードしたlombok.jarをライブラリに追加して、プロジェクトプロパティの「ビルド > コンパイル」カテゴリにある「エディタでの注釈処理を有効にする」のチェックをいれます。

f:id:nowokay:20130730105114p:image


実は、lombokの存在はもっと前から知っていたのですが、エディタ対応しているとは知らず「エディタ上でエラーになるんじゃ意味ないよな」と避けていたのです。いくらコンパイル時にとおっても、エディタ上でエラーになっていては作業できないですから。ということで、エディタ対応しているとわかって即導入しました。


lombokを使ったほうがいい理由

では、lombokを使ったほうがいい理由ですが、一言でいえば「ミスがなくなる」です。

ぼくはアクセッサがらみのミスを2度経験して、lombok導入を決めました。

どのようなミスかということになりますが、まずミスとして「作成時ミス」「変更時ミス」「削除時ミス」という分類ができます。このうちの「作成時ミス」はIDEでの生成でもなくすことができますが、「変更時ミス」「削除時ミス」はなかなかなくすことができません。

それぞれのミスについてみてみます。


変更時ミスの回避

生成したフィールド、アクセッサの名前を変更するとき、フィールドをアクセッサを別々にリファクタリング機能で変更したとき、名前を打ち間違えてエラーが発生するということがありました。Javaコード内では動作として問題がなかったのですが、JSFでの式言語での動きで気づきにくい不具合が発生していました。

まあ、これはフィールド、アクセッサをセットで名前変更するリファクタリング機能を使えばよかっただけなのですが、だれか他の人が同じミスをすることも考えれるので、lombokを導入するきっかけになりました。

アクセッサ以外にもtoStringやequalsなど変更時に手が回らないことが考えられる部分はあります。このようなところでもフィールドの同期が自動的にとれるlombokを使うとミスが減らせます。


削除時ミスの回避

2人でコードを書いていたあるとき、少しロジックの入ったgetterが単純なgetterに変更されてしまうことがありました。共同作業者に聞いてみると、フィールドを複数追加してアクセッサを作るとき、アクセッサの並び順がフィールド記述順にしたいため、一度すべてのアクセッサを消して生成しなおしたということでした。そのときにロジックが入っているものを一緒に消してしまった、と。

たしかに、このロジックを自分で書いていなければ、やりがちです。


Javaのアクセッサの難点は、特別なロジックの入っているアクセッサが単純なアクセッサの森に隠されてしまうことです。一緒に削除したというのも、結局アクセッサの森にロジックがまぎれてしまっているためです。lombokによって不要な森が消えることで、作業性があがります。


適正なカバレッジ

これは副作用だったのですが、アクセッサコードがなくなることで、テストカバレッジの数字が実態に近づきました。

それまでカバレッジの分母にアクセッサが含まれていたため、テスト中で使われないアクセッサの分だけカバレッジがさがり、また逆にアクセッサを多く使っているだけで実ロジックはあまり通ってないテストを書くことでカバレッジがあがるような、あまり実態に即しない数字が出ていました。

lombokでアクセッサコードがなくなることで、実際のロジックコードをベースとしたカバレッジデータに近づきました。


@ToStringでの循環参照に注意する

lombokを使ったことで実行時不具合が出るということが、少なくともひとつ考えられます。

toStringの循環参照の問題です。


データベースのマッピングでは、次のように明細データをもつクラス構成をとることがあります。

    @ToString
    class Foo{
        List<FooDetail> details;
    }
    @ToString
    class FooDetail{
        Foo parent;
    }

このとき、toStringで文字列化が行われると、toStringが循環的に呼び出されてスタックオーバーフローします。


次のように@ToStringアノテーションにexclude属性を指定して、循環参照を切っておく必要があります。

    @ToString(exclude = "parent")
    class FooDetail{
        Foo parent;
    }

これが問題なのは、自動生成するようなtoStringが呼び出されるのは正常系コードではなく例外系コードであるということです。それも、自分で書いたコードで呼び出すのではなく、例外メッセージの中で使われることが多くあります。

そうすると、テストしにくく、なんらか別の問題が出たときに顕在化するという、ちょっとやっかいな問題になります。


フィールド名変更が少し面倒

実のところ、lombokを導入すると、アクセッサ付フィールドの名前変更が面倒になります。

というのは、フィールド名を変更すると自動的にアクセッサも名前がかわるのですが、自動生成されたアクセッサを利用している部分までリファクタリング機能が及ばないからです。

IDEの対応の問題なので、NetBeans以外では問題ないかもしれませんが。IntelliJプラグインの説明に「adds support for most features.」とあるので、対応しているのかも。


で、現状どうしているかというと、名前変更するフィールドのアクセッサをIDEでの生成でわざわざ作って、その上でフィールド名をリファクタリング機能で変更し、その後でアクセッサを消す、というちょっとナンセンスなことをやっています。


それでもlombokは有効

まあ、いくつか難点がありますが、lombokはJavaのボイラープレートコードを減らし、作業量や潜在的なミスを減らしてくれる、よいツールです。

特に理由がなければ導入したほうがいいのではないかと思います。

kikutaro777kikutaro777 2013/07/30 17:28 最後に書いてある「フィールド名の変更」ですが、僕は、NetBeans左下のナビゲータでアクセサを右クリックして「リファクタリング」してます。こうすると、まず使ってるところは全部書き換わるので、そのあとに定義元のフィールド名だけを変えてます。少しだけ楽です。

あと、アクセサ使ってる「使用状況を検索」する場合にも、左下のナビゲータから行うほうが安全だったり(^^;

それと地味なメリットですが納品時に「すべてのメソッドにコメントを!」とか求められるようなケースではアクセサでのコメント記述が軽減されますね(^^;逆に言うとJavaDocでアクセサメソッドが出てこないので微妙かもしれませんが。

nowokaynowokay 2013/07/30 19:46 「使用状況を検索」ではナビゲータつかってたけど、リファクタリングもできるのですね。それは気づかなかった。
アクセッサメソッドにコメントついてくれたらすごくうれしいですよね。

xuweixuwei 2013/07/30 21:47 自分もlombok好きで結構使ったことあるんですが、ToString以外の残念情報としては、内部的に無理やりな変換してるので
「eclipseのコンパイラだとコンパイルできるのに、コマンドラインからjavacやると死ぬようになる」ということがわりと起きます(最新のversion0.12.0で確認)
そうすると、jenkinsでビルドが難しくなったり・・・

nowokaynowokay 2013/07/31 02:16 普通にjavacでコンパイルしてますが、コンパイル失敗するようなことはないですね、いまのところ。まあ、数千行のコードですけど。
どういうパターンで死ぬんでしょう?

xuweixuwei 2013/07/31 15:22 はっきりjavacが死ぬパターン切り出せてないんですけど、valを多用すると簡単に死ぬ気がします。
@Setter や @Getter などのメソッド生成系なら発生しないのかも?

nowokaynowokay 2013/08/01 04:56 あぁ、たしかにvalは使ってないですね。

アノテーションプロセッサが通ったあとの通常処理は普通のJavaコードのコンパイルなのですけど、コンパイラ本体で死ぬとは思えないんですよねぇ。とするとAPTで死んでると考えれるんですけど、Lombok APTはECJでもjavacでも同じものを使うので共通のはずで、それが片側で落ちるとなると、メモリ割り当て不足かなーと思ったり。
@Setter @Getterに比べて型推論はメモリ食いそうだし。

xuweixuwei 2013/08/01 07:06 いやこんな感じで、大概ヌルポとかで死ぬので、メモリ関連ではないと思うんですよね。

https://gist.github.com/xuwei-k/6126569

lombokの内部アーキテクチャ詳しくは把握してないですけど、このあたり

https://github.com/rzwitserloot/lombok/tree/v0.12.0/src/core/lombok/eclipse
https://github.com/rzwitserloot/lombok/tree/v0.12.0/src/core/lombok/javac

で、「javacとeclipseのコンパイラで違う処理を行う部分がある程度あって、そのせいで片方で失敗するという現象が起きる」
という予想なのですが

nowokaynowokay 2013/08/01 19:01 あぁ、中でコンパイラ別の処理があるんですね。しかしそうなるとval使いにくいですねぇ。