その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

arnoldというJavaのbuilderライブラリを書いた

今時builderライブラリも無いだろという感じですが,書いたのです.

github.com

Motivationにも書いているのですが,普通に作ったJavaのbuilderは不完全なインスタンスを作成できる可能性を孕んでいます.注意深くコードを書けば問題ないかもしれませんが,えてして我々 (主語が大きい) はミスを犯しがちです.そしてNPEがやってきて……我々はしぬ.

というわけで今回作ったのがarnoldです.
端的にいうと「必要なすべての要素を取りこぼさないbuilder」であり,「そのジェネレータ (annotation processor)」でもあります.Basic usageにあるように,

import net.moznion.arnold.annotation.ArnoldBuilder;
import net.moznion.arnold.annotation.Required;

@ArnoldBuilder
public class Example {
    @Required
    private String foo;
    @Required
    private int bar;
    @Required
    private double buz;
}

というようにclassに @ArnoldBuilder を付与することでinstantiate対象のclassであることを示し,builderでbuildしたいフィールドについては @Required を付与することで必要なフィールドであることを示すことができます.そしてプロジェクトをビルド *1 するとAnnotation Processorが走り,builder classが自動生成されます.
自動生成されたbuilderはこのように使うことができます.

Example example = new ExampleBuilder().foo("foo")
                                      .bar(42)
                                      .buz(2.71828)
                                      .build();

このBuilderは分解して書いてみると

ExampleBuilder exampleBuilder = new ExampleBuilder();
ExampleBuilder1 exampleBuilder1 = exampleBuilder.foo("foo");
ExampleBuilder2 exampleBuilder2 = exampleBuilder1.bar(42);
ExampleBuilder3 exampleBuilder3 = exampleBuilder2.buz(2.71828);
Example example = exampleBuilder3.build();

というふうになっており,つまり

  • ExampleBuilderfoo を受け取って ExampleBuilder1 を返すsetterを提供する
  • ExampleBuilder1bar を受け取って ExampleBuilder2 を返すsetterを提供する
  • ExampleBuilder2buz を受け取って ExampleBuilder3 を返すsetterを提供する
  • ExampleBuilder2build() を提供する

というようなコードをAnnotation Processorが一式生成するという挙動となっています.なかなか富豪的ですね.

このように,1つのbuilder (もはやbuilderではない気もしますが) が高々1つの要素を受け取り次のbuilderを返却し,終端のbuilderが build() して所望のclassのインスタンスをinstanciateする,という仕組みにすることで

  • 「build途中の不完全な状態のインスタンス」は何らかのbuilder classになるので,build対象のインスタンスをinstantiateすることは出来ない
  • 必須なフィールドがすべて揃ったらその時点でbuilderはbuild()を提供し,ようやくbuild対象のインスタンスをinstantiate出来るようになる

という安全な挙動を実現しています.


今後の課題としてはprivate classのinstantiateができないというものがあり,これはなんとかしたい *2


それはそうと今回Javaのコード生成には square/javapoet を使ってみたんですが,使いやすく,なおかつパワフルで良いですね.
現場からは以上です.ぜひご利用下さい.

*1:紛らわしいですね

*2:これなんとかなるんですかね?