getterにロジックを書かない方がいい

getterにはロジックを書かない方がいい。

理由は、単純なgetterとロジックを含むgetterの判別が利用者側からつきにくくなるから。

例えばsalePrice, saleStart, saleEndという3つのフィールドがJavaBeansの中にあるとする。このとき、getSalePrice()が単純にsalePriceを返すのか、あるいは内部で現在日時とsaleStart, saleEndを比較し、有効な場合のみsalePriceを返すのか、Javadocをみたり実装をみたりしないとわからない。

コードを書くときにJavadocをみたり実装をみたりするのはいいとして、コードを読むときにも利用するgetterのJavadocや実装をみなければいけないようなコードはいいコードとはいえない。

getterにロジックを書く代わりに独自メソッドを用意する

いいコードにするには、getterにロジックを書く代わりに専用のメソッドを用意する。

先程のgetSalePrice()の例でいえば、computeSalePrice()getValidSalePrice()のような名前でメソッドを用意する。

private String salePrice;
private LocalDateTime saleStart;
private LocalDateTime saleEnd;

public String computeSalePrice() {
    if (salePrice == null || saleStart == null || saleEnd == null) {
        return null;
    }

    var now = LocalDateTime.now();
    if (now.compareTo(saleStart) >= 0 && now.compareTo(saleEnd) <= 0) {
        return salePrice;
    } else {
        return null;
    }
}

フレームワークやライブラリの都合で、JavaBeansにgetterとsetterを書かざるを得ない場合

フレームワークやライブラリの都合で、JavaBeansにgetterとsetterを書かざるを得ない場合は、getterと独自メソッドを併用するしかない。

ただJavaBeansを利用する側が誤ってgetterを利用しないようにしなければならない。getterはあくまでフレームワーク/ライブラリ用に定義しているだけであって、人間が利用してはいけない。

誤ってgetterを利用するのを防ぐために@Deprecatedアノテーションをgetterに付与する。

private String salePrice;
private LocalDateTime saleStart;
private LocalDateTime saleEnd;

/**
 * <pre>
 * フレームワーク/ライブラリ用に定義する。
 * 開発者はcomputeSalePrice()を利用する。
 * </pre>
 * @see #computeSalePrice
 */
@Deprecated
public String getSalePrice() {
    return this.salePrice;
}

public String computeSalePrice() {
    略
}

このようにしておけば、getterを利用した際にIDEが警告を出してくれるし、CIでSpotBugs等によりミスを検知することも可能となる。

Lombokのgetterを上書きする

現在のLTSはJava11だが、recordが未対応のこともあり、実際のJavaの開発においては、Lombokを利用していることが多いと思う。

getter/setterを求めるフレームワークに応じるために、Lombokの@Data@Getterをclassに付与していても、独自メソッドを用意するフィールドに対しては、getterをあえて定義した上で@Deprecatedを付与すればよい。

@Data
public class Product {

    private String salePrice; // @Dataによりsetterが生成される。getterは自分で定義したものが利用される
    private LocalDateTime saleStart; // @Dataによりgetter/setterが生成される
    private LocalDateTime saleEnd; // @Dataによりgetter/setterが生成される

    /**
     * <pre>
     * フレームワーク/ライブラリ用に定義する。
     * 開発者はcomputeSalePrice()を利用する。
     * </pre>
     * @see #computeSalePrice
     */
    @Deprecated
    public String getSalePrice() {
        return this.salePrice;
    }

    public String computeSalePrice() {
        略
    }

}

参考

こちらの記事(JavaBeans 規約に従いつつカプセル化もしたいのですが)を参考にした。

全てこちらの記事通りにしているわけではないが、以下の部分が特に参考になった。

以前システム設計の増田さんの講演をお聴きする機会があったんですが、そこで「アクセッサはドメインオブジェクトのロジックではない。だからアクセッサは禁止している。*3」というようなことを話されていました。ここで前述の通り「ライブラリ・フレームワークの都合上 getter/setter が必要になる場合があるはずだけどどうするんだろう」と疑問に思ったのですが、なんと getter/setter に deprecated を付けることで、ライブラリ・フレームワークに対応しつつプロダクトコードからの使用を事実上禁止しているということでした。これはなるほど!と思ったものです。