システム開発で思うところ

JavaEEを主にシステム開発をしながら思うところをツラツラを綴る

Pluggable Annotation Processing API Sample(実践編2)

前回の続き

vermeer.hatenablog.jp

今回はAnnotationProcessorを開発する際のテストのやり方です。一部、自分のライブラリの機能を使うところがありますが、それを除けば基本的に汎用的なものだと思います。

はじめに

前回の続きを想定しているので、以下のコードをクローンなりしてローカルのMavenリポジトリが作成されていることを前提としています。

Git

vermeerlab / apt-core / source / — Bitbucket

vermeerlab / apt-extend-sample / source / — Bitbucket

vermeerlab / apt-extend-sample-client / source / — Bitbucket

Maven

テストに必要なプロジェクトを追記します。
このプロジェクトがあることで任意のJavaコードをAnnotationProcessorの処理対象として指定することが出来るようになります。

<dependencies>
    <!-- for annotation processor start-->
    <dependency>
        <groupId>com.google.testing.compile</groupId>
        <artifactId>compile-testing</artifactId>
        <version>0.10</version>
        <scope>test</scope>
    </dependency>
    <!-- annotation processor end -->
</dependencies>

生成したコードの検証

テストコード

例示のサンプルは
vermeerlab / apt-extend-sample / source / — Bitbucket のものです。

f:id:vermeer-1977:20170819173222p:plain

@Test
public void 生成コードの検証() {
    Truth.assert_()
            .about(JavaSourceSubjectFactory.javaSource())
            .that(JavaFileObjects.forResource(Resources.getResource(
                    "test/Sample.java" ← 生成元になるコードのパス
            )))
            .processedWith(new AnnotationProcessorController(false)) ← 実行するAnnotationProcessor
            .compilesWithoutError()
            .and()
            .generatesSources(JavaFileObjects.forResource(Resources.getResource(
                    "test/SampleFactory.java" ← 生成されるコードの期待値と同じコードのパス
            )));
}

実行するAnnotationProcessor

テストや疎通確認時点ではAnnotationProcessorControllerの引数をtrueにしてDebugUtil#printで標準出力すれば、生成したコードの内容を確認することが出来ます。確認が終わればfalseにすれば標準出力はされなくなります。

補足

私は1つのコントローラーから任意のコマンドを実行するという方式なのでAnnotationProcessorControllerを指定しますが、一般的には生成処理毎にAnnotationProcessorを作成してサービスローダーに登録すると思います。その場合は、processedWithの引数に作成したProcessorを指定してください。

期待値となる生成コード

標準出力で生成されるコードの確認は可能ですが期待値検証にはなっていません。同値のコードを準備しておくことで継続性を担保できます。

なのですが実際には地味に手間取りました。ということで私の手順です。

  1. 期待値となるコードの「ファイルだけ」を作成する
  2. AnnotationProcessorController(true)にする
  3. テストを実行して生成コードを標準出力する(テストはエラー) f:id:vermeer-1977:20170819193958p:plain

  4. 意図通りの結果が出力されているか確認して意図通りになるまで生成ロジックを修正する。

  5. 標準出力されたコードを期待値となるコードファイルにコピー&ペーストしてコンパイルエラーが無いことを確認する。

  6. IDEではなくテキストエディタなどで「直接」コピー&ペーストして保存する

  7. テストが成功で終わることを確認する

私がはまったところ

期待値比較は完全一致

IDEで自動整形されていても内容が正しければ検証にて正常と評価されると思っていました。 テキスト比較で評価しているようで体裁も含めて完全に一致していないといけませんでした。

完全一致しているはずなのに…

はじめの一行目の空白行が足りなかったため標準出力の内容をテキストエディタでコピー&ペーストしているのにテストエラーが解消されないという事象でした。標準出力している内容をテキストエディタでコピー&ペーストしてもテストエラーになる場合は1行目に空白行を入れるといった「標準出力では気が付きにくい違い」がある可能性があります。

その他

格納場所はsrc/test/resources配下にクラスのパッケージと同じフォルダを作成して格納します。インプットとアウトプットともにパッケージ構成次第では同じ場所に格納されるので命名ルールに注意が必要です。

さいごに

こんな感じで手間はかかりましたが、以上が私の考えるAnnotationProcessorのテスト手順になります。
生成後に期待するコードを作成すると言いつつ、標準出力されている内容のコピー&ペーストというのも変な話ではありますが、そもそも生成したいコードはコード編集ロジックを組み立てる前に検討しています。そういう意味では検証結果に そのまま使用はしないけれども 期待するコード相当のものは作成しています。特にJavaPoetのようなライブラリを使ってコード編集をする場合、事前に検討もせず構築するのは結構無謀だと思います*1

追記

2017/8/19

ブログ記載時の記述に一致するブランチを作成してリンクを修正。

*1:私自身、このくらいなら出来るだろうと思ってJavaPoetでコード生成をしようとして、「あれ?どんなクラスを作るつもりだったっけ?」と無駄に時間を使ってしまいました。

Pluggable Annotation Processing API Sample(実践編1)

以前の記事の続きです。

vermeer.hatenablog.jp

予定としては この流れで作っておきたいツールがあるので それを作り切るまで 続けたいと思っています。

はじめに

開発環境

Java8、Maven、Netbeans8.2

コード

全てのリポジトリは以下のプロジェクトに格納しています。

https://bitbucket.org/account/user/vermeerlab/projects/AN

目指したところ

  • ライブラリ群として沢山のAPIを作成しておいて必要なものだけを使用するような仕組みがあると嬉しい
  • 統一した処理が出来るようにインターフェースを準備する

APIの作成と登録(基本)

基本となる機能や使い方についての説明です。

Git

vermeerlab / apt-core / source / — Bitbucket

実行するAPIを作成

実行クラスパスとしては「テスト」内なので違和感があるかもしれませんが、テスト実施と基本ライブラリにサンプルを持っておきたいと考えた結果、このような構成になっています。

対象を特定するAnnotationを作成

マーカーとなるAnnnotationインターフェース(TestScan.class)を作成します。このインターフェースを記載している要素が処理対象となります。

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface TestScan {

}

実行するコマンドクラスの作成

インターフェース:ProcessorCommandInterfaceを実装したクラスを作成します。ラウンド毎に実行するコマンドです。

public class ProcessorCommand implements ProcessorCommandInterface {

    @Override
    public Class<? extends Annotation> getTargetAnnotation() {
        return org.vermeerlab.apt.command.test.TestScan.class;
    }

    @Override
    public void execute(ProcessingEnvironment processingEnvironment, Element element, Boolean isDebug) {
        CommandValidator validator = CommandValidator.of(TestScan.class);
        validator.validate(TestScanValidation.of(element));

        ProcessingEnvironmentUtil util = ProcessingEnvironmentUtil.of(processingEnvironment);
        DebugUtil debugUtil = new DebugUtil(isDebug);
        debugUtil.print(util.getPackagePath(element));
        debugUtil.print(util.getTree(element).toString());

        TypeMirror typeMirror = element.asType();
        Boolean isSameType = util.isSameType(typeMirror, Object.class);
        debugUtil.print("isSameType=" + isSameType);
    }
}

メソッド:getTargetAnnotationの戻り値として設定した値が処理対象となるAnnotationインターフェースです。
今回はTestScan.classです。

メソッド:executeProcessorラウンドにて実行します。
今回は検証を行います。Javaコードの生成は行っていません*1

最後に実行するコマンドクラス(任意)の作成

インターフェース:PostProcessorCommandInterfaceを実装するクラスを作成します。
本クラスの作成は任意です。

用途としてはAnnotationで処理全体の結果を踏まえて何かをしたい時に使用します。

サンプルでは処理件数を標準出力しています。

public class PostProcessorCommand implements PostProcessorCommandInterface {

    private Integer count = 0;

    @Override
    public Class<? extends Annotation> getTargetAnnotation() {
        return org.vermeerlab.apt.command.test.TestScan.class;
    }

    @Override
    public void execute(ProcessingEnvironment processingEnvironment, Element element, Boolean isDebug) {
        this.count++;
    }

    @Override
    public void postProcess(ProcessingEnvironment processingEnvironment, Boolean isDebug) {
        DebugUtil debugUtil = new DebugUtil(isDebug);
        debugUtil.print("TestScan.class count = " + this.count.toString()
    }
}

検証クラス(任意)を作成

インターフェース:ValidationInterfaceを実装するクラスを作成します。
本クラスの作成は任意であり、またヘルパークラス用のものなので使用も任意です。

用途としてはAnnotationで取得した要素(Element)に対して何かしらの検証を行う際に使用する統一インターフェースです。

public class TestScanValidation implements ValidationInterface {

    private final Element element;

    private TestScanValidation(Element element) {
        this.element = element;
    }

    public static TestScanValidation of(Element element) {
        return new TestScanValidation(element);
    }

    @Override
    public ValidationResult validate() {
        ValidationResult result = ValidationResult.create();
        if (this.element.getSimpleName().toString().equals("ErrorTestTarget")) {
            result.append(ValidationResult.of(ValidationResultDetail.of("TestScanValidationError")));
        }
        return result;
    }
}

コマンドクラスのexecute()内でCommandValidator#validateの引数にすることで、複数の検証クラスを一度に実行できるヘルパーに使用することが出来ます。したがって、自分で検証の仕組みを設けたい場合は、使う必要もありません。

サンプルではクラス名によって検証を行っています。*2

上述のコードだと以下の部分です。

CommandValidator validator = CommandValidator.of(TestScan.class);
validator.validate(TestScanValidation.of(element));

validator.validateの引数は複数指定できるので、elementの種類毎に色々な検証をしたい場合にイテレーターパターンで検証をします。というか、それだけのヘルパーです。

作成したAPIを登録

実行クラスパスとして今回はテストクラスパス内のresources配下のprocessor-command.xmlに実行するコマンドとして作成したコマンドクラスを指定します。

メインパッケージのresourcesprocessor-command.xmlが存在しますが、これはライブラリの基本構造を示すということと、基本ライブラリをそのままForkして拡張することを鑑みた対処です。したがってファイル内に実行するコマンドクラスの指定をする記載もしていません。

コンパイル

コンパイルによりAnnotationProcessorが動いたように思えますが、実際はテストクラスにて実行されただけです。
今回のケースではコード生成をしていないのでtarget配下にコードが作成されることはありません。

APIの作成と登録(拡張)

基本となるライブラリを使用して独自のAnnotationProcessorライブラリを作成してみたいと思います。

実際に作ったコマンドは前回の記事でやったこととほぼ同じことをするものにしました*3

説明は基本編との差分のみにします。

Git

vermeerlab / apt-extend-sample / source / — Bitbucket

Maven

基本ライブラリを依存ライブラリとして追加します。
加えてAnnotationProcessorを使うときにお約束になるコンパイル時の指定を追記します*4

<dependency>
    <groupId>org.vermeerlab</groupId>
    <artifactId>annotation-processor-core</artifactId>
    <version>0.1.0</version>
</dependency><plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <!-- Disable annotation processing for ourselves. -->
        <compilerArgument>-proc:none</compilerArgument>
        <showDeprecation>true</showDeprecation>
    </configuration>
</plugin>

実行するAPIを作成

APIの作成と登録(基本)』ではテスト内に作成しましたが、今回はきちんとメインパッケージに実装をします。

コマンド実行クラス:SampleCommand.java

処理対象アノテーションTargetClass.javaTargetField.java

作成したAPIを登録

テストのためにテストパッケージのprocessor-command.xmlに登録をしています。

メインパッケージのresourcesprocessor-command.xmlを作成する必要もありませんし、もしファイルがあったとしても作成したコマンドを追記する必要はありません。

コンパイル

  • 生成のトリガーとなるクラス
package test;

import org.vermeerlab.annotation.processor.sample.TargetClass;
import org.vermeerlab.annotation.processor.sample.TargetField;

@TargetClass
public class Sample {

    @TargetField
    private final String _name;

    @TargetField
    private final String _desc;

    public Sample(String name, String desc) {
        _name = name;
        _desc = desc;
    }

    public String getName() {
        return _name;
    }

    public String getDesc() {
        return _desc;
    }
}
  • 生成されたクラス
package test;

import java.lang.String;

public final class SampleFactory {
  public static Sample create(String _name, String _desc) {
    return new Sample(_name,_desc);
  }
}

APIを利用するクライアント

作成したAPI群を使用するクライアント側プロジェクトです。

作成した拡張API群をフレームワークとして配布して それを使う側の手順になります。

Git

vermeerlab / apt-extend-sample-client / source / — Bitbucket

事前条件

拡張API群をMavenコンパイルしてローカルにMavenリポジトリが存在していること*5

Maven

pom.xml(関連個所のみ。お約束も忘れないようにということで明記)

<dependency>
    <groupId>org.vermeerlab</groupId>
    <artifactId>annotation-processor-extend-sample</artifactId>
    <version>0.1.0</version>
</dependency><plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.0</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <!-- Disable annotation processing for ourselves. -->
        <compilerArgument>-proc:none</compilerArgument>
        <showDeprecation>true</showDeprecation>
    </configuration>
</plugin>

実装

AnnotationProcessorの処理対象となるAnnotationインターフェース(@TargetClass)が設定されている生成トリガーとなるコードは、拡張APIのテストコードとほぼ同じなので省略します。

生成する予定のクラスを参照しているクラス(GeneratedUser.java)はリポジトリからクローンした直後は、コンパイルしていないということで まだコードが生成されていないので エラーになっています。

f:id:vermeer-1977:20170818225945p:plain

コンパイル

  • generated-sources/apt配下にクラスが作成されて コンパイル前にエラーになっていたクラスからエラーが消えました。

f:id:vermeer-1977:20170818230318p:plain

さいごに

メリット

使い方がシンプル

クライアント側はライブラリの依存だけとAnnotationProcessorを動かすための記述をすれば良いだけです。

ブラックボックスではない(と思う)

Processorのコマンドクラスをクライアント側で指定するので どういう操作をしようとしているのか分かるのでブラックボックスでは無いと思います。機能の提供側としては機能一覧としてprocessor-command.xmlに記載する内容を提示しておいて、利用側が必要なものを選択する、または不要なものを外すことで対応可能です。

デメリット

コマンドの記述が繁雑

実行ライブラリのクラスパスにprocessor-command.xmlを配置しないといけないません。つまり利用側が自分のresources配下に機能一覧を追記したprocessor-command.xmlを配置しないといけません。最低でも提供側が定義ファイルに記述する内容をルートパッケージのJavaDocなどに記載したり、手引きに書いておかないと、何をどうしたら良いのかわかりません*6

解消案としては、基本ライブラリのProcessorCommandManagerにてxmlからコマンドクラスのパス(文字列)を取得しているので、ハードコーディングをするということが挙げられるでしょうか。 *7

次回

今回はコマンド作成と登録と起動をメインに整理しました。次回はテストについて投稿したいと思っています。なお説明に使用するテストコードはすでに今回のプロジェクト内に入っているものを使う予定です。

追記

2017/8/19

ブログ記載時の記述に一致するブランチを作成してリンクを修正。

*1:基本となるライブラリへの依存ライブラリを最低限にしておきたかったためです

*2:単にテストケースの網羅性を高くするためだけのものです

*3:したがって、あえてコードの主要な記載はコピー&ペーストにしています

*4:こちらを忘れて毎回 コードが生成されているのにクラスが参照できなくて「何でだ!!」と頭を抱える

*5:本記事を上から順番に実施していたら出来ていると思います

*6:このあたりを良い感じにできれば良かったのですがクラスローダー周りの実装力が無かったため断念しました。

*7:個人的には このあたりについて自分なりの落としどころを考えて もう少し対応したいと思っていますが、とりあえず作ろうと思っているものを一通り作ってから改めて取り組もうと思っています。

【書評】現場で役立つシステム設計の原則

読みました。断片的に収集していた増田亨さん(@masuda220)の知識を理解するのに良い本だと思います。もしDDDについて調べていて増田さんのスライドなどを見て「もう少し詳しく知りたいかも」と思った人は読むことをお勧めします。

良かった

ロジックをenumを使って表現する

P60:
EnumStrategyを管理するという言い方になるのかな?確かにこういうのは分類と実装を意味ある塊に整理出来るので良さそうです。

調べたら、こういうのもありました。
【enum】メソッドの定義(3)−strategyパターンを使う方法 - THE HIRO Says

メソッドは必ずインスタンス変数を使う

P81:
インスタンス変数を使わない理由があるとしたら、そこには更なるクラス設計が出来るかもしれないと思って疑ってみる。

確かに引数だけを使ってインスタンス変数を使わないメソッドを作り始めると神Utilみたいなのを作りやすいです。また整理のやり方としては1つにまとめるべきかもしれないけど、往々にして「じゃぁ、これもこの概念にまとめておいた方が良いよね」となって設計の粒度が属人化することもあります。クラスは抽象的な概念で まとめるよりも関連するデータ(プロパティ)を中心に設計するという指針にしておけば「気がついたら巨大になっていた」「設計者によってクラスの粒度が大きく異なっている」というのは是正されるかもしれません。

P83:メソッドが全てのインスタンス変数を使うようになる
使うように「する」でも良いかもしれないです。環境変数の取得などもあるので「絶対」ではないですが、使っていない理由を整理していくと結果として「これは別クラスにしておこう」ということになるように思います。

スコープは可能な限りパッケージスコープに

P85:
IDEの補完においても余分な候補が出なくなるので公開窓口となるクラスやメソッド以外はパッケージスコープにしておくことについては賛成です。ただ、ドメインオブジェクトはフラットにどのレイヤーからでも参照することを前提に作ると考えると、パブリックが基本になりそうな気がしますが。

ドメインモデルとデータモデルは何が違うのか

P100:
カチッと分かったという感じではないけれど、何となく言わんとしようとしていることは分かったという感じです。1つ言えることは本書を読むまでの自分は どちらかというとデータモデルありきの発想だったので、何となくでも意識できるようになったのは良いことだと思います。

サービスクラスを分ける

P160:
参照系と登録系に分けるという発想は持っていませんでした。副作用のあるサービスと、副作用のないサービスはWebAPIのところでも触れられているように、ちゃんと分けておくと影響範囲調査などするときなど楽になるので、今後の実装で取り入れていきたいと思いました。

記録の変更を禁止する

P184:
以前、PostgreSqlの内部の仕組みの説明を聞いた記憶では、DBログとしては全て追記しておき それをサマリーする、というような話だったので 気が付いていないだけで低レベル実装としては更新では無くて追加をしています。また障害時もログ(履歴)+リカバリーポイントから復元します。DB自体がそういうアーキテクトであることから鑑みても変更というよりもコト履歴の赤黒追記でそれをサマリーするというのには賛成です。
ただ履歴のシーケンスの管理については別途考察が必要になる可能性は残るように思います。例えばサーバーを跨ったマイクロサービスなどの場合、順序性の保証をするためには何かしらの仕組みが必要で、その払い出し性能がネックになることは起こりえます。少なくとも全ての払い出しを1つのテーブルでやろうとしたら大量のリクエストでパンクします。接続ユーザーグループ毎にパーティションを作るとか そういう仕組みの検討が必要になる可能性があるかも、という話です。まぁそこまで非機能要件が厳しい場合、更新であっても別の検討が必要にはなっていると思いますが。

「記録の変更を禁止する」の一番の抵抗勢力は経験豊かな(?)DBAかもしれません。「マスタは頻繁に更新されないし、手で直すだけで対応できるんだから簡単でしょ?大丈夫大丈夫、更新日付のカラムを書き換えるべしっていうルールがあるから。ちょっとした修正なのにレコード追加とかシーケンス付番とか作業が増えるだけで事故の元だよ」と。当然そういう人は削除フラグ(もしくは削除日)も大好きです。

テーブル設計を優先して…

P192:
テーブル設計を優先してオブジェクトをそれに合わせるアプローチはロジックの整理に失敗する、というは、ずっとモヤモヤしていたところだったのでスッキリしました。

具体的には マッピング=形だけのDTO=あんまり良くないことでは?というのと、テーブルの関心事とドメインの関心事は違うから似て非なるものだから気にしすぎなくてよいのでは?というループです。考察の切欠はテーブル定義からドメインモデルを自動生成すると良いのでは?とボンヤリ考えていたところから。ただ自動生成をしても良いけれど、逆にその生成物があるためにドメインの検討の邪魔になるのでは?とも考え始めて…(ループ)。
私としては、違うものととらえるべきだということに整理が出来たので満足です*1

個人的な好み

書籍と私の好みの違いの整理。

状態遷移はStateパターン

P63:状態の遷移のルールをenumで管理

Stateパターンの方が個人的には好みです。

Enumだと状態遷移を全体として俯瞰して表現できるという考えもあると思うけど実際に見てみて私には分かりにくいものでした。状態遷移は状態遷移図とステートマシン図を使って検討・設計をしてテストで保証するというのが好みです。少なくとも、このEnumクラスでユーザーと仕様を共有するのはツライです *2。 このあたりは「状態を管理するクラス」を選択するのか「自分の次の状態は自分で管理する」のか、どちらを好ましいと思うのか、という違いかもしれません。前者は全体の俯瞰ができるメリットがあり、後者はクラスの責務が凝集しているけど全体の俯瞰は補助資料が必要です。

形式的な資料はかえって危険

P140:(P248にて補足あり)
形式的なドキュメントに「議事録」が入っているのは疑問です。意思決定や合意形成のアウトプットとして議事録は簡易的ではあるがエビデンスになりうるものと考えているからです。もちろん形式的なドキュメントの作成は私自身も必要最低限にしたいと思っているし、勘弁してほしいとすら思っています。ただし議事録・課題管理・QA表など経緯を把握できるドキュメントは後から参画したメンバーに仕様確定の経緯を深めてもらうために便利なので手抜きはしない方が良いです。とくにユーザーとの合意は特別扱いしておきたいです。ホワイトボードがそれにあたるというのであれば議事録相当の合意事項を箇条書きなりにして最終合意事項としてスナップを取っておかないと、打合せ非参加者が見た場合、重要なポイントが不明瞭になる可能性があります。 少なくとも 議事録の体裁にこだわる必要は無いとは思うけれど、ホワイトボードに書かれた検討メモや構成図をもって合意形成の確定とするのであれば、第三者が見ても誤認しないようなレベルの「まとめ」はしておいた方が良いでしょう。 *3

書籍の意図も、ホワイトボードがあるから議事録不要ではないとは思っています。ただ、「議事録作るの無駄」と整理すると事故が起きた時にシンドイですよ、ということを私は言いたかっただけです。ちなみに私は開発メンバーとの意思疎通をするときには、A3の紙か、ホワイトボードに書きながら話して、打合せが終わったら印刷して共有、というのを良くやっていましたので、その便利さは良く知っているつもりです。ただしその記載も責任の所在も全て「リーダーである私のみ」という前提でした。検討資料のインデックスはホワイトボード資料のファイル名か「私の記憶でいつくらいに、その話をしたか辿って探す」でした。開発チーム内であれば それでも良いですが、ユーザーとの合意事項を それと同等に捉えるのは個人的には抵抗があります*4

契約による設計

P165:
防御的プログラミングでのやり過ぎ懸念は分からなくはないけれど、そうではない契約による設計がイコールでシンプルになるというのは疑問です。ユースケース記述を例にしても、自分のユースケースを実行する事前条件の定義は、自分で一義的にするのが筋が良いと思いますし、仕様が漏れ出ていかないと思います。呼出側としても「何が返ってくるかわからないという前提でから様々な検証コードを書きます」とあるけれど、それは呼出側として後続処理を行うにあたって必要な「事前条件」の検証をしないといけないだけでは?と思います。
Nullの扱いについては、確かに課題ですが、それはプロジェクト全体で統一することであって、それがイコールで防御よりも契約に、とはイマイチつながらない気がする*5。個人的にはサービスの戻り値の型(つまりドメインオブジェクト)+例外+JavaDocというインターフェースを持って、基本的な約束事は満たされるようになると思っています。
ひょっとしたら私の理解が誤っている可能性がありますが そんな風に思いました。

あと「例外を使うのは、通常の使い方ではあまり起きない場合に限ります」も同様に疑問です。例外は通常云々ではなく「事前条件不正」(事前条件だけではないですが)というような主たる関心事を満たせない場合のルートだと思っています。そうじゃないと古式ゆかしきリターンコードで対応するのかな?という印象しかないです*6。BeanValidation(入力値検証)も事前条件だと考えますが検証不正時は非チェック例外で表現します。それを「通常の使い方ではあまり起きない場合」というのは違うのではないでしょうか?

書籍の例として「ゼロ除算」を挙げていましたが、ゼロを格納した変数(クラス)が除算に使用されるかどうかを呼出元は知りません。知っている=仕様が漏れ出していることになるのではないでしょうか? もし 呼出先としてゼロがもし渡されたときに どうしたら良いか分からず、呼出元に対応を強制するというのであればチェック例外で呼出元に対処を明確に促すべきではないでしょうか*7。もしくは非チェック例外&JavaDocで表現するというやり方もあります。*8

2017/8/19 追記
増田さんの趣旨もしくは その一端が以下のtwitterで言っていることだとしたら、私の「契約による設計」の書評はズレているように思います。いずれにしても、このあたりは具体的な実装および実現技術を踏まえないとイメージも難しい気がします。ボンヤリ「こういうことかな?」というのはあるのですが、私は言語化できていないです。

分析者が実装まで行う

P285:品質保証
「分析と設計が一体となった開発」についてのアンサーとして(かなり端折っていますが)分析者が実装まで行うというのは同意です*9。 それとは別アプローチとして、実験的にこういうのもあるのでは?と思うことを追記しておこうと思います。例えばペアプロ(どちらも実装ができる人+どちらかが仕様への理解が明るい)とかモブプロ(要件は分かるが実装は出来ない人も参加することを想定しているので厳密なモブプロとは違うけれど)とか、ユーザー自身も含めた分析者とドメインの理解が浅いけど手は動かせる実装者が一体になって開発するというような「仕組みの構築」も一案としてあるかなと思いました。
例えば、モブプロを通じてリファクタリングをしないと、開発スピードが だんだんと遅くなるという体験の共有も大切かもしれません。

だんだん開発スピードが遅くなっていくのをどうやってとめたら良かったんだろう? - Mitsuyuki.Shiiba

とはいえ、私が言っている案の方がSIerの現場だと非現実的な気がしていますが(ペアプロは出来るかな?)。

ちょっと残念?

勝手に私が期待していて物足りなかったところ。

画面とドメイン

P211:
「複数の関心事が混在している「何でも画面」を提供する場合は、ビュー専用のオブジェクトを…」について画面とドメインの連携で書かれていると思っていました。タスクベース画面はウィザード操作的であり業務習熟度が上がった担当者にとっては まどろっこしい入力になります。また高い習熟度は「関心事」として捉えられる範囲が広がるので「何でも画面」と一括りにもできないと思います。とはいえ全部のデザインについて1冊の書籍で網羅することを求めるのも違う気がするので、あくまで「個人的に物足りなかった」という感想です。

重箱の隅

本質に関係のないところです。

コーディング作法

Javaっぽくないかもと思ったりしたところ。この辺の作法は好みもあります。

if文の書き方

メソッドの行数を減らすために「{}」を使っていないような印象を受けました。スコープの範囲を明確にした方がバグが生まれにくいので個人的には好きではない書き方です。実際事故も起きています。行数を少なくすることは手段であって目的ではないと思います。

AppleがiOS7.0.6で修正したSSLバグの簡単な解説 - Qiita

3行は「{}」のあるif分岐すら実装できません。また「1行で書いてみました!」と過剰に行だけは少ないけれど、メソッドの仕事としては沢山の事をしている実装をされても困ります*10

私としては10行以内を目標に20行くらい、多くても50行というのが現実的なように思います。

参考:

もちろん行数を増やしたところで、上述の懸念は残ります。私は集中できる時間帯というのを「息を止めていられる時間」と考えています。なので後輩たちにメソッド実装をしてもらうときの基準として、上述の行数とあわせて「メソッドは軽く息を止めて実装の意図がすんなり分かるくらいの規模で」というようにしていました。コードが複雑でも短ければ許容できるし、長くても単純なボイラーだったら無理に分割しなくても良いかな、と。当然、個人差はありますが「コードは読み物」という意識は持ってもらえるかなと思っています。

2017/8/19 追記

増田さんのブログ引用
if文のブロックは必ずメソッドに抽出して(省略)
else句は原則使わないようにしているので、ほとんどのif文は、このような一行形式で記述でき(省略)
手ごたえを感じている書き方です。

条件ロジックは分割することで複雑度を減らしている&3行くらい、というこで私が事例に挙げたようなバグは混入し辛いということになるでしょうか。私としては本件は「重箱の隅」よりも「個人的な好み」に分類した方が良いところだったかな、と読み返して思いました。

ブロックの書き方

P58:
クラスのブロックはJava風だけど、static初期化ブロックが.Net風というか、初期化ブロック風。static初期化ブロックと初期化ブロックでは初期化の順番が違うので紛らわしいコーディングはしない方が良いと思います。*11

参考: 【Java】初期化ブロックについて - TASK NOTES

Enumの列挙子が小文字

P60:
Enumの列挙子が小文字始りになっていてpublicプロパティを直接参照している印象を受けます。列挙子は定数的な扱いなので全て大文字の方が好みです。

参考: Java列挙型メモ(Hishidama's Java enum Memo)

誤記?

P111
判断/加工/処理

P144
判断/加工/計算処理

おそらく「判断/加工/計算」の誤記だと思います。

ドメインを参照するレイヤー図

P150: ドメインロジックはアプリケーション層だけから使う、という整理だったかな? 少なくとも増田さんの以前のスライドでは、そうなっていなかったように思います。

www.slideshare.net

2017/8/19 追記

増田さんのブログ引用
処理の流れのイメージとしてこっちのほうがわかりやすいと思って、ドメインモデルへの矢印を、アプリケーション層だけに絞ってみました。

個人的には3層から使用できるということがドメインモデルのポイントだと思っていたのと、増田さんの3層からの矢印スライドを見て「ドメインってそういうことなのか~」と理解が深まったということもあって妙に拘ってしまいました。

さいごに

批判的なことも書きましたが総じて良い本だと思います。OJTの名のもとに師事する人(メンター)が現場の人だけだったという人は 一度 目を通しておくと良いと思います。また、なんとなくですが 3年くらい経験を積んだ人が読むと良いと思います。

増田さん自身による書評のまとめ
たくさんの書評、ありがとうございます | システム設計日記

*1:ということで自動生成ツールの作成は当面しない

*2:とはいえ本書籍はEmunの使い方集ではなく、こういう整理をしましょう、という本だと思うので あまり突っ込むのも野暮な気もする。

*3:少なくとも、私の経験では 最終アウトプットで合意を取っているからOKでしょ、というプロジェクトは経緯や考察への配慮が不足していて、結果として炎上プロジェクトになっていました。また、議事録・メールなどのエビデンスにより相手方の承認履歴があったことで資金回収が出来たという個人的な経験によるところも大きいです。

*4:別にお客様は神様です などと思いませんが

*5:lombokアノテーションもあるし

*6:であったとしても、本質的に満たそうとしている事前条件不正を呼出元に通知するという目的は同じです

*7:チェック例外の良し悪しは横に置いておきます

*8:私は非チェック例外派なので後者を選びますが

*9:というか、設計から実装まで出来るメンバーが集まっているのであればオブジェクト指向云々に関係なく、それなりに高い品質を担保できるとは思います。実際、初見メンバーで各社から集まってもらったチーム(自社で開発技術がある要員は実質私だけ)での開発をしましたが、設計力と実装力が一番ある人をプロト開発に、実装力はあるけれど経験年数が若い人をコアドメインの設計者に、そして私は方式検討と仕様課題と実装課題の検討にのみ注力することで、超短期間の開発であったけれども どうにか乗り切ることが出来た経験はあります。

*10:MSXの1画面プログラムみたいな。凄いとは思うけど写経してゲームはしたけど、ちっとも意味は分からなかった。Cのポインタ使いも同様。複雑な正規表現も同じく。ただ正規表現については、自分がもっと勉強をしないといけないと思います。

*11:実際は整形についてはIDE任せなので問題はないけれど

JavaParser

構文解析をするときに便利そう。構文木の書き換えも出来るみたい。

CIとかaptと組み合わせると面白い事ができるかもしれない。

例えばプロジェクト独自の静的解析を走行したい場合や独自フレームワークの作法を強制させたいときとか。

ただ実行速度がどのくらいのものか分からないので開発者のコンパイルというよりもCIで実行した方が良いとは思う(つまりaptとの組み合わせはしない方が良いと思う)。

JavaParser 使い方メモ - Qiita

Java の AST を解析できる javaparser がアツい!!! - tokuhirom's blog

入力値検証

BeanValidation

JavaEE使い方メモ(Bean Validation) - Qiita

私のBeanValidationの使い方(Java EE Advent Calendar 2013) — 裏紙

BeanValidationの相関バリデーションとそもそもの話 — 裏紙

4.1. 入力チェック — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.3.0.RELEASE documentation

入力チェックの実装と実行 - 考える場所

Bean ValidationのGroup sequenceは単項目チェック、相関チェックの順序指定で使うのは止めた方が良さそう - Qiita

Spring Bootで入力値の検証(バリデーション)の順番を制御する - かずきのBlog@hatena

DDDのメモ

概念

原本のエッセンス

[ 技術講座 ] Domain-Driven Designのエッセンス 第1回|オブジェクトの広場

[ 技術講座 ] Domain-Driven Designのエッセンス 第2回|オブジェクトの広場

[ 技術講座 ] Domain-Driven Designのエッセンス 第3回|オブジェクトの広場

書籍(購入済)

トランザクションスクリプト

ユースケース(アクション)中心にオブジェクト(メソッド)を組み立てるのがトランザクションスクリプト

いまさらきけない「ドメインモデル」と「トランザクションスクリプト」 - yvsu pron. yas

むむっ、私の思考はトランザクションスクリプトの可能性大かもしれない。

実装サンプル

DDD の Java EE 実装サンプル - Cargo Tracker を読み解く - Qiita

ドメイン駆動設計: IDDDに登場するサンプルコードのModule構成をまとめてみた - Qiita

2.4. アプリケーションのレイヤ化 — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.3.0.RELEASE documentation

構成

実装サンプルの中で個人的に一番しっくりくるのはTERASOLUNA
プロジェクト分割については好みの思考。

ValueObjectは、どの層からでも呼び出して良いフラットでパブリックなものだと思っていて、そのパッケージ構成って みんなどうしているのかな?と悩んで調べてみると、以前、DDDの勉強に参考にさせていただいた かとじゅん(@j5ik2o)さんのブログが参考になりそう。

Scalaコードでわかった気になるDDD | GREE Engineers' Blog

ドメインモデルはユビキタス言語でグルーピングする

net.gree.xxx.domain.hunter  
 Hunter, HunterName, HunterRank

net.gree.xxx.domain.monster
 Monster

net.gree.xxx.domain.item
 Item, ItemService

実装サンプルのフォルダ構成よりも、こちらの方がしっくりする。RepositoryEntityとの兼ね合いに悩みが生まれそうだけど、それは今後、自分で実装する中で考え直そうということで保留。

クラス

同じようなPOJOだけど色々分類がされていて、その違いを考えるのに参考になる。

【JavaBeans】BeanとDTOとEntityとVOとFormの違いって何? - Javaプログラマのはしくれダイアリー

JSF(ManagedBean)

作成単位

コンポーネント単位(呼出元となるxhtml単位)。
またはユースケース単位(呼出元となるxhtmlが複数にまたがるケース)

個人的なこだわり

少し調べて見ると「ManagedBeanは画面単位で作成します」という表現を見かけることがある。
多くの場合、画面におけるメインコンテンツが1つ(1面?)となる場合はコンポーネント=画面になるので画面単位という整理で基本的に問題ないとは思っているが、その整理だとテンプレートやコンポーネントを組み合わせて構築するJSFコンポーネント指向という仕組みとピタッと一致した感じが無いように思う。

ユースケース単位というのは「一画面で実現出来うるオペレーションだけれども、ユーザビリティを考えると複数画面に分割した方が良いケース」を意図している。 ウィザード操作のように一連の操作が単純で会話型で進められるようなものは、xhtml単位でManagedBeanを作成すると資産が増えすぎてしまう。増える事自体が悪いというつもりはないけれど操作の順序性など必要以上に発散させるのも良くないと考える。この場合は「一連の操作」の目的単位でManagedBeanを作成することで操作の見通しが良くなると考える。

遷移時の情報共有は@ConversationScoped

画面遷移時の情報共有というか受け渡しにはFlash*1という仕組みもあるけれど、それは使わない。

個人的なこだわり

同一フロー(もしくは会話)内の情報の生存期間を管理する仕組みを統一しておきたい。ちょっとした受け渡しだからFlashで、といった場合の「ちょっとした」が、本当に「ちょっとした」で終わる保証がどこにも無いから。改修があった場合、多くのケースでは既存の仕組みを残して対応をしたくなる。例えば、始めは画面1から画面2への単純な遷移、かもしれないけれど、もし画面2から画面1に戻ったり(加えて画面2の状態を画面1に反映させたり)という要件が発生した場合、結構しんどくなることが想定される。いやいや、そんな、、と思うところはあるけれどSIerの現場だと聖域を犯すべからずという感じで起こりやすい。ちゃんと仕組みも含めて会話が出来ることを永劫に保証できるというのであれば問題ないと思う。
複数画面を跨る情報は会話スコープのBeanに格納して読み出すという整理であれば、上述のケースが発生しても問題なく対応できる。

なおFlashはダメという話ではない。基底となるFWは極力「何でも出来る」が望ましいと思っている。その上で利用局面を考えて、それぞれの利用者として制約を設けて品質を担保するのが良いという話。

*1: FacesContext.getCurrentInstance().getExternalContext().getFlash()