www.slideshare.net
ドメインモデルの根拠とドメインモデル貧血症の対策について - Chatwork Creator's Note
DDD Alliance! RDRA流概念モデル - connpass
の資料(資料への直接リンクはしない)
Mavenプロジェクトを細かく分割することを前提にしたら、私が作成しているものは公開リポジトリが無いと利用者側*1にとって凄く不便だなと思って調べてみました。
さくっとは出来ないだろうなぁとは思いましたが案の定かなり苦戦しました。自分なりに 初めから やり直したので大丈夫な手順だとは思います*2。
Windows10、Java8、NetBeans8.2
コードの管理は今のところ基本的にBitBucket
を使っています。その中でMavenリポジトリはGitHub
にしました。理由は知見量が多かったことと、当初、BitBucketでやろうとしたのですが良く分からなかったというのが一番の理由です。あと後付け的なところはありますが、公開する資産についてはGitHubの方が多くの人にとって馴染みがあるかな?というところもあってGitHubをMavenリポジトリの置き場にすることにしました。
ブランチ名は「mvn-repo」
「save」ボタンを押すのを忘れずに
Webで調べるとユーザー名とパスワードを設定するやり方が多かったように思いますが、OAuthによる認証の方がセキュリティー的にみて良いと思います。
Token description は「maven-pass」。ただのラベルなので何でも大丈夫です。
チェックボックスは public_repo
user:email
だけ。必要最低限は この2つをチェックすれば良いようです*5。
認証キーが生成されます。
後から確認は出来ませんので ここでメモしておくのを忘れずに。忘れたときは再生成することになるので気を付けましょう。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.vermeerlab</groupId> <artifactId>maven-git-sample</artifactId> <version>0.1.0</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <github.global.server>github</github.global.server> <git.branchName>mvn-repo</git.branchName> <git.repositoryOwner>vermeer-1977</git.repositoryOwner> <git.repositoryName>maven</git.repositoryName> <git.isMerge>true</git.isMerge> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <version>2.8.2</version> <configuration> <altDeploymentRepository>internal.repo::default::file://${project.build.directory}/${git.branchName}</altDeploymentRepository> </configuration> </plugin> <plugin> <groupId>com.github.github</groupId> <artifactId>site-maven-plugin</artifactId> <version>0.12</version> <configuration> <!-- git commit message --> <message>Maven artifacts for ${project.version}</message> <noJekyll>true</noJekyll> <outputDirectory>${project.build.directory}/${git.branchName}</outputDirectory> <branch>refs/heads/${git.branchName}</branch> <includes> <include>**/*</include> </includes> <repositoryName>${git.repositoryName}</repositoryName> <repositoryOwner>${git.repositoryOwner}</repositoryOwner> <!-- true:履歴を残す false:直近バージョンのみ repositoryに残る --> <merge>${git.isMerge}</merge> </configuration> <executions> <execution> <goals> <goal>site</goal> </goals> <phase>deploy</phase> </execution> </executions> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.0</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.source}</target> <compilerArgs> <arg>-Xlint</arg> </compilerArgs> </configuration> </plugin> </plugins> </build> <distributionManagement> <repository> <id>internal.repo</id> <name>Temporary Staging Repository</name> <url>file://${project.build.directory}/${git.branchName}</url> </repository> </distributionManagement> </project>
ローカル保存先の distributionManagement
の url
の指定でリポジトリ名と同じフォルダを指定しています。任意のフォルダ名で良く、特に意味を持たせる必然は無いけれど pom内に複数個所指定するので 記述誤りを防ぐために同名にしました。
git.isMerge
がtrue
であればアップロードした資産の履歴を残します。SNAPSHOT
の場合は履歴を残すけど、そうじゃなくなったら履歴は不要というような指定をしたい場合に使用する想定かな?と思っています。
...\.m2\settings.xml
に格納されています。
NetBeansのMavenプロジェクトだったら、以下のものを編集。見た感じだとプロジェクト毎に存在するように見えますが 全てのプロジェクトで共通の資産です。どのプロジェクトのものを編集してもちゃんと反映されます*6。
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <servers> <server> <id>github</id> <password>hogehogehogehogehogehogehogehogehogehoge</password> </server> </servers> </settings>
hogehoge…
は先に取得した認証キーを設定してください。
mvn deploy
コマンドを実行します。
アップロードされたリポジトリのアドレスは
GitHub - vermeer-1977/maven at mvn-repo
ちなみに通常のビルドではアップロードはされません。
次の工程で参照するためのクラスを追加します。
public class Sample { public void testPrint() { System.out.println("maven-test-sample"); } }
改めて deploy
します。
public class Main { /** * @param args the command line arguments */ public static void main(String[] args) { new org.vermeerlab.maven.git.sample.Sample().testPrint(); } }
アップロードをした資産の取り込むための設定。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.vermeerlab</groupId> <artifactId>maven-git-client</artifactId> <version>0.1.0</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <github.global.server>github</github.global.server> <git.branchName>mvn-repo</git.branchName> <git.repositoryOwner>vermeer-1977</git.repositoryOwner> <git.repositoryName>maven</git.repositoryName> <git.isMerge>true</git.isMerge> </properties> <repositories> <repository> <id>github-maven</id> <url>https://raw.github.com/${git.repositoryOwner}/${git.repositoryName}/${git.branchName}/</url> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </repository> </repositories> <dependencies> <dependency> <groupId>${project.groupId}</groupId> <artifactId>maven-git-sample</artifactId> <version>0.1.0</version> </dependency> </dependencies> </project>
この流れだとローカルリポジトリを参照してしまいます。念のためローカルリポジトリの資産を削除して確認をすると良いと思います。ログをみたら、きちんとダウンロードをしていることが確認できます。
基本は以上です。
vermeer_etc / maven-git-sample — Bitbucket
vermeer_etc / maven-git-client — Bitbucket
親pomを持つことで重厚なpom記述を全てのプロジェクトに記載しなくて良くなります。
artifact単位でフォルダは作成されますが、意味のある単位としてリポジトリを分けておきたいケースがあります。その場合は、子pomの git.repositoryName
を指定します。
子pomで指定
<properties> <git.repositoryName>(変更したいリポジトリ名)</git.repositoryName> </properties>
リポジトリ名だけでなく、ライブラリ管理用のOrganization
を作成すると、製品毎に配布資産をグループ化することができます。
子pomで指定
<properties> <git.repositoryOwner>(変更したい組織名)</git.repositoryOwner> <git.repositoryName>(変更したいリポジトリ名)</git.repositoryName> </properties>
別ユーザー=認証キーが異なるアカウント管理配下のリポジトリを意図しています。例えばオーナーは別の人だけど配布資産のアップロード権限を持っている状態です。このケースはタグによる上書きだけでなく、対象権限の認証キーも併せて指定する必要があります。
★★あくまで理論上の話です*7。★★
別ユーザーや別組織のリポジトリにアップするためには、アップロード権限のある認証キーが必要です。
認証キーは別途入手できている前提として、いちいちsettings.xml
を書き換えるのは面倒です。ということで切り替えるというのが良いと思います。
settings.xml
にid
と認証キーを追記<server> <id>github</id> <password>hogehogehogehogehogehogehogehogehogehoge</password> </server> <!-- 追記 --> <server> <id>github-blog</id> <password>fugafugafugafugafugafugafugafugafugafuga</password> </server>
<properties> <github.global.server>github-blog</github.global.server> <git.repositoryOwner>(変更したい組織名)</git.repositoryOwner> <git.repositoryName>(変更したいリポジトリ名)</git.repositoryName> </properties>
もし基本構造が全く同じであれば指定は不要ですが リポジトリ名など明示的に変更が必要なところがあれば それも指定してください。
とりあえず現時点で私が思いつく懸念事項と対策案です。
上述していますがgit.isMerge
は基本的にtrue
にして子pom側で上書きをしないようにすることを推奨します。履歴を持つ分、リポジトリの容量が増えてしまいますが 間違ってアップロードしたときに履歴があれば戻すことが可能です*8
github.global.server
の指定は子pomで明示的にする親pomで宛先を指定しておくと子pomの記述量は減るのですが、上述の事例にあるような切り替えを検討した場合、いくつかの偶然が重なると(各種名称が重複していた&ユーザーとしても更新権限を持っていた、など)、間違った宛先のリポジトリを更新してしまうことが懸念されます。親pomには無効な値を記述しておけば、未指定の場合はアップロードが出来ないのでリスクは減るかと思います。また子pomに本記述があることが先頭で分かるので「これは公開リポジトリにアップロードする対象の資産だな」ということが明示的に分かりやすいように思います。
ただ、切り替えは基本的にしないというのであれば、親pomに記述した方が良いと思います。仕事が増えるとミスの原因も増えます。
子pom
<plugins> <plugin> <groupId>com.github.github</groupId> <artifactId>site-maven-plugin</artifactId> </plugin> </plugins>
のところを基本的にコメントアウトしておけば 間違ってdeploy
を実行してもアップロードはされません。リポジトリに資産をアップする頻度にもよりますが、頻繁でないのであれば、こういう運用もありだと思います。
子pomからローカルリポジトリを参照させたいので、子pomによるビルドの前に親pomのローカルリポジトリの作成またはコードを`git clone'してビルドをするのを忘れないようにしましょう。
ローカルリポジトリの相対位置をrelativePath
で指定しています。こうしておかないと子pomから親pomの参照ができないためエラーになります。今回は同一グループなので、このくらいの記述ですが別グループだと、こうはいかない可能性があります*9。基本的に親pomプロジェクトは同一グループにつくると思いますので、ご自身のグループを適当に作って親と子の資産を1つのグループとして扱うと良いでしょう。
<parent> <groupId>org.vermeerlab</groupId> <artifactId>maven-git-parent</artifactId> <version>0.1.0</version> <relativePath>../</relativePath> <!-- この指定 --> </parent>
vermeer_etc / maven-git-parent — Bitbucket
vermeer_etc / maven-git-child — Bitbucket
Github上に私設Mavenリポジトリをつくる - M12i.
GitHub の Pages を maven リポジトリとして使用するときのアカウント情報をセキュアにする(OAuth2Token) | KK.Kon の徒然メモ書き
http://synergian.github.io/wagon-git/usage.html
これでオレオレリポジトリが気軽に作れるようになりました。
目的としていた「細かいMavenプロジェクトを作っても、利用したいときにコードのダウンロードで地獄を見ない」ということが実現できそうです。
本題とは関係ないですが、目次を初めて使ってみました。これは良いですね。
*1:自分自身も含めて
*2:やり直して思うところとしてはGitHub側の認証キーのところが原因だったかも。もしくはGitPageの作成かな?
*3:作ってから思いましたが紛らわしいので違う名前にした方が分かりやすかったかも
*4:あぁ、いきなり分かりにくい
*5:上手くいかなかったのは、user:email にチェックしていなかったからダメだったかも
*6:反映というか「同じ」なんですけどね
*7:GitHub上で別ユーザーの作成も考えましたが、今回のためだけに作成するのは ちょっと違うかな と思ってやめました
*8:ミスをしない工夫も大事ですが、ミスはするもの という前提を仕組みに入れておくと心理的に安心です
*9:試してはいませんが理屈上はそのはずです
書評も書かせていただいた「現場で役立つシステム設計の原則 」の著者である増田さん(@masuda220 )の本には書けなかったこと、というのを聞いてきました。
会の内容としては、半分書評LT、半分増田さんのお話という構成でした。
www.slideshare.net
www.slideshare.net
www.slideshare.net
LTそのものへの感想というのは、ちょっと難しいのですが、書籍の礼賛に終始することなく、これは良い、これはどうだろう?とそれぞれのスピーカーの経験を感じられるものでとても良かったと思います。
Java勢としては @irofさんの登場が個人的に嬉しかったですね*2。あと「この本は読みやすいし、そうそうと分かった気になってしまう。でもそれだと良くない。ちゃんと自分で読み込まないとダメ」というのは同意です。*3
執筆にあたって「自分のやっていないことを書かない」を踏まえていたとのこと。そうなんですよね、机上の空論ではなく実践の足跡を記した書籍なので 読みやすいんですよね。
潔い解答でした(笑)。
E2Eで(ヒューリスティックテストを十分やって)品質は担保しているとの見解でした。要件の充足という意味だと それも解なんですよね。私も自分のシステム(分析・設計・実装・利用が私だけ)だと状態遷移図くらいを書いて、状態整理をしたら、あとはドキュメント作っていないですし。それが良いか悪いか、については契約云々があるので一概に正しいというつもりはありませんが、面倒なドキュメントを作らないで済むなら作りたくはないというのは本音です。
増田さんも そういう人なのかもしれませんが、私がまだSIerだったころ「あなたがシステムに触れるとバグが出る!!触らないでください!!」とメンバーに良く言われました。「いや、これが僕のお仕事なので…」といって問答無用でバグを検出しまくっていましたが(笑)。
ここは難しいところですが、リファクタリングを安全にするためにはテストがあると安心なんだけど、内部構造を大幅に見直すレベルのことをやっているときにテストコードがあると、テストコードメンテナンスで工数が結構必要になるんですよね。私なりの落としどころはtwitterでも言ったのですが
TDDは私の開発ペースにはあっていないようだ。
— vermeer (@_vermeer_) 2017年8月26日
考えつつ実装をして、実装が落ち着いたら、構造を整理して、整理が落ち着いたら疎通テストして、疎通テストが落ち着いたらリファクタリングして、リファクタリングしながらテストカバレッジを上げる。
今のところ、私にはこれが合っているようだ。
なんですよね。
そういうことを踏まえつつ、網羅性の担保をすべきかどうかは別にして、E2Eのテストツールについては、上手に導入できたら内部実装のリファクタリングも やりやすくなるから良いかもしれませんね*4。
そうですね。簡易DDD本ではなく、あくまで「増田さんの足跡と今の整理」というスタンスの書籍なので。個人的には掘り下げたらもっと面白かったかもなぁ、と思うところもありますが、過去のスライドで補完しているので、これはこれで1つの整理かな、と思います。
すべき、という制限事項というよりも「それが一番わかりやすかったら、その言葉を使うのが一番いい。一番わかりやすい言葉でサービスを構築するのがいい」ということを おっしゃっていたように思います。
終始書籍でも関心事と実装の距離感については扱っていたように思います。これは賛否あるところだとは思いますが、実際のところ @nunulkさんも言っていましたが、私もcssのclassをドメイン的なクラスに実装していて、見通しは良いと個人的には思っています。
自分がやっていないことを、さもやっているようには言わない、という指針通りというところでしょうか。個人的には「国際化対応も加味した仕組みで構築されている」という響きに惹かれてしまいますが、国際化対応をすることが常に正しいというのは確かに偏っているというか教科書的かもしれないという考え方もあるかもしれないですね。初回リリースのサービス利用ターゲットが日本人を想定していて、その上で評価が良かったら、国際化対応を別スプリントで実施するというのも1つの考え方かもしれません。少なくとも日本語対応のみに絞れは、その分、開発スピードは早くなるので。あと、増田さんが言っていたように「表示文言のみの変更」であれば、業務ロジックの変更とは異なり シンプルなので ドメイン破壊は起きえないでしょう。そういう割り切りもありますよ、というところでしょうか。
むしろ更新は不自然とすら仰っていました。トランザクション内での振る舞いという意味だと、そうなんですよねぇ。ダーディーリードなど どうなるんだろう?と、実装依存で気になるところが無いわけではないけれど、そのあたりについて、私はそこまで詳しくないので なんとも言えないところです。
おそらく許容されないと思いますが、後述で紹介する奥野さんの話を聞いたときに「ん?ってことは削除も更新も事実の追記なわけだから、DBへの操作はインサートだけにすべきってことかな?」とすら思いました。ちなみに容量と性能に問題が無ければ、今でも そう思っています。
リレーショナルで考えると、そうなるんだろうなぁ、と私も思います。ある時点までなかった関連(つまりNull)を持ち込むのは良くないっていうのは 以前 セミナーで奥野 幹也さんが言っていて それが設計原則ではあるんですよね。少なくとも私は そう理解しました。新たな関心事の追加というのであれば、カラム(要素)の追加ではなく、テーブル(関心事)の追加になりますよね、という整理です。
www.slideshare.net
たぶん、書かない・書けないということを前提に増田さんに質問をぶつけたわけではないと、私は思っています。指摘された方はJoinのコストのことを気にされていたのではないかな?と思います。私としては「非正規化は性能問題が観察されてからで良いと思う」という意見です。追加したい関心事が本来は存在すべき情報だったけど漏れていたということとであればカラム追加(つまりデフォルト値がある)、そうではなく新たな概念であればテーブル追加というのが私の理解です。少なくとも「たった1つの要素で1対1だからカラム追加で良いよね」ということであれば、テーブルの面積が大きくなりやすいアンチパターンな気がします。「たったこれくらい」はアリの穴の香りがするフレーズと思っています。
役割がシンプルな部品を作ることと、部品の組み合わせによりサービスを構築することがあるとしたら、まず前者を満たしましょうというのが、書籍において統一した思想というところでしょうか。WebAPIは外部インターフェースなので個人的には最小単位の担保はサービス側で良いかなぁという印象ですが実経験が数少ないので何とも言えません。ただ最小単位でAPIを作っておき、利用側からの性能面や利便性の具体的な要望に応じて組み合わせるという設計思想であれば「良かれと思って作ったけれど使われないサービス」は少なくなるかな、とは思います。
別にDDDに限らず、アジャイルやウォーターフォールに限らず、エキスパートは忙しいので会話することは難しいし、新しいサービスだったらエキスパートすらいないというのは納得(新しくなくてもいないこともある)。エキスパートがいないことを前提に振る舞う方が現実的というのも納得。
プロトを操作してもらうことは、開発側による仕様確認のお願い事ではなく、「触りたいな」「これが実現されたら、〇〇が良くなるだろうな」ということを早く体験したいとユーザー自身が思っていますか?ということかな。
121名のアンケートで75%が好意的だったのは驚きだったとのことです。書籍による紹介が「原理原則はこうであり、それ以外はダメ」というような大上段に構えたものではなく「このくらいからなら、できるかも」とイメージしやすい書籍だったと思います。*5
そうなんですね。まだ 私は その違いが分かっていないようです。似て非なるとは思いますが、値オブジェクトなど踏み込めるところもあるかな?とは思いますが、たぶん、そういうところではないんでしょうね。
実験場所ですので、これが正解というわけではありませんよ。
失敗をする方が多いからこそ、小さな実験をしていきましょう。無駄なことはありませんよ。
@irofさんがやったみたいに、色々とやりすぎくらいのことをやってみたら分かってくることもあります。
簡単にフィードバックや結果が出ると思わないこと。自分自身も含めて、周囲も変化するためには時間が必要。
自分自身も少しずつ変っているもの。
成果物(コード)で測定を。昨日のコードよりも今日のコードの方が良くなっていることを実感できていますか?
裁量範囲が広くなるのは良いこと。
上述(リファクタリングの邪魔に…)の通り。
経験的に言えるのは意外と完全一致というのは少ないと思うということ。似て非なるものなので、それだったら流用など考えずに その場その場で作った方が良い。
Entityは2つの関心事(識別・集約ルート)がある。識別はDBの主キーという意味ではなく人の関心事の発見という意味で大事。集約ルートについては、なんでもかんでも1つに集約するのは反対。(関連によって分割するのが大事?)
値オブジェクトではなく、類似計算ロジックの共通化については あまりしていない。コードの重複を気にしすぎずベタで実装している。(「ロジックの重複の排除」よりも「ドメインの重複の排除」に注力しているということでしょうか?)
まず、一応、1つに集約しているインターフェース(なんでも管理画面)については分割を提案する。その上で見えるところ(外部インターフェース)はドメインエキスパートの考えを優先しても、バックヤードのサービスを細かく分割するなどしてバランスをとっている。
計算ロジックを値とロジックで1つにまとめて外に出す、というように小さいまとまりを少しずつクラスにして外に出すことを繰り返していくと徐々にコードが変わってくる。 また、変更の単位に注目して、それをドメインとして まとめるところを繰り返していくと良いのでは?
今回のセミナーの趣旨とはずれますが、この書籍が面白かった方や「システム開発かぁ、一通りプログラムは書けるようになったけど、さて…」という方に私のお勧めの書籍を紹介しておこうと思います。
以前の記事になりますが、若手の方が目を通しておいたらよろしいのでは?と思う書籍です。
これに追加して、悶々と考えるネタになる本として、以下が個人的にお勧めです。私自身が開発プロセスというチームにいたときに、システムアーキテクトってなんなんだろう?どういうことを考えないといけないんだろう?フレームワークってどうやって考えていくものなんだろう?と思ったときに手にした本です。正直、好みの分かれる本だと思います。
著者の萩原正義さんのお話をJJUG CCC 2013 Fall(R1-4 分散データ技術の再考)にて聞いたことがあります*6。すごく難しかったです。でも聞いているだけでなんだか賢くなった気になりました(笑)
増田さんにご挨拶できて良かったです。自分の書評を改めて読んで少々突っかかり気味な表現など失礼なところもあったと思っていて せめて得体のしれない匿名な状態は解消をしておきたかったので。
あと今回の会には全く関係ないのですが、学生時代の先輩に偶然会ったのには驚きました。卒業以来くらいになるでしょうか。会計システムの会社に就職をされたのは記憶していたので、今もそっち系なのかな?と思いましたが、twitter(Motohiro Imura (@imunew) | Twitter)を拝見させていただいたところ、かなり毛色の違う分野のようです。そして結構コアな感じのことをされているようで凄いと思います。
良い刺激を受けた日になりました。いやー、良い日でした!
*1:公開されている資料で、私が見つけたもののみです
*2:私は、@irofさんといえば「コードは読み物」というフレーズとともにインプットされています
*3:ちなみに、私も夏の実家への帰省の往復で2回読んで、書評にあたって付けた付箋のところを読んで、みたいなことをしました。
*4:E2Eテストツールを使ったことはないので塩梅が分かりませんが、網羅性の保証をすべてE2Eテストツールで満たそうとすると、それはそれで地獄が待っている気がします
*6:実際には、ずいぶん後から気が付いたことなのですが。本棚の整理をしている時に「あれ?この名前はどこかで見たことがある気がする」と調べたら「あーあの人か!」と。
前回の続き
今回はAnnotationProcessorを開発する際のテストのやり方です。一部、自分のライブラリの機能を使うところがありますが、それを除けば基本的に汎用的なものだと思います。
前回の続きを想定しているので、以下のコードをクローンなりしてローカルのMavenリポジトリが作成されていることを前提としています。
vermeerlab / apt-core / source / — Bitbucket
vermeerlab / apt-extend-sample / source / — Bitbucket
vermeerlab / apt-extend-sample-client / source / — Bitbucket
テストに必要なプロジェクトを追記します。
このプロジェクトがあることで任意の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
のものです。
@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" ← 生成されるコードの期待値と同じコードのパス ))); }
テストや疎通確認時点ではAnnotationProcessorController
の引数をtrue
にしてDebugUtil#print
で標準出力すれば、生成したコードの内容を確認することが出来ます。確認が終わればfalse
にすれば標準出力はされなくなります。
私は1つのコントローラーから任意のコマンドを実行するという方式なのでAnnotationProcessorController
を指定しますが、一般的には生成処理毎にAnnotationProcessorを作成してサービスローダーに登録すると思います。その場合は、processedWith
の引数に作成したProcessorを指定してください。
標準出力で生成されるコードの確認は可能ですが期待値検証にはなっていません。同値のコードを準備しておくことで継続性を担保できます。
なのですが実際には地味に手間取りました。ということで私の手順です。
テストを実行して生成コードを標準出力する(テストはエラー)
意図通りの結果が出力されているか確認して意図通りになるまで生成ロジックを修正する。
標準出力されたコードを期待値となるコードファイルにコピー&ペーストしてコンパイルエラーが無いことを確認する。
IDEで自動整形されていても内容が正しければ検証にて正常と評価されると思っていました。 テキスト比較で評価しているようで体裁も含めて完全に一致していないといけませんでした。
はじめの一行目の空白行が足りなかったため標準出力の内容をテキストエディタでコピー&ペーストしているのにテストエラーが解消されないという事象でした。標準出力している内容をテキストエディタでコピー&ペーストしてもテストエラーになる場合は1行目に空白行を入れるといった「標準出力では気が付きにくい違い」がある可能性があります。
格納場所はsrc/test/resources
配下にクラスのパッケージと同じフォルダを作成して格納します。インプットとアウトプットともにパッケージ構成次第では同じ場所に格納されるので命名ルールに注意が必要です。
こんな感じで手間はかかりましたが、以上が私の考えるAnnotationProcessorのテスト手順になります。
生成後に期待するコードを作成すると言いつつ、標準出力されている内容のコピー&ペーストというのも変な話ではありますが、そもそも生成したいコードはコード編集ロジックを組み立てる前に検討しています。そういう意味では検証結果に そのまま使用はしないけれども 期待するコード相当のものは作成しています。特にJavaPoet
のようなライブラリを使ってコード編集をする場合、事前に検討もせず構築するのは結構無謀だと思います*1。
ブログ記載時の記述に一致するブランチを作成してリンクを修正。
AnnotationProcessorを使ったツールのリンク
プロパティファイルからEnumを生成 - システム開発で思うところ
*1:私自身、このくらいなら出来るだろうと思ってJavaPoetでコード生成をしようとして、「あれ?どんなクラスを作るつもりだったっけ?」と無駄に時間を使ってしまいました。
以前の記事の続きです。
予定としては この流れで作っておきたいツールがあるので それを作り切るまで 続けたいと思っています。
Java8、Maven、Netbeans8.2
全てのリポジトリは以下のプロジェクトに格納しています。
https://bitbucket.org/account/user/vermeerlab/projects/AN
基本となる機能や使い方についての説明です。
vermeerlab / apt-core / source / — Bitbucket
実行クラスパスとしては「テスト」内なので違和感があるかもしれませんが、テスト実施と基本ライブラリにサンプルを持っておきたいと考えた結果、このような構成になっています。
マーカーとなる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
です。
メソッド:execute
をProcessorラウンドにて実行します。
今回は検証を行います。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の種類毎に色々な検証をしたい場合にイテレーターパターンで検証をします。というか、それだけのヘルパーです。
実行クラスパスとして今回はテストクラスパス内のresources
配下のprocessor-command.xml
に実行するコマンドとして作成したコマンドクラスを指定します。
メインパッケージのresources
にprocessor-command.xml
が存在しますが、これはライブラリの基本構造を示すということと、基本ライブラリをそのままForkして拡張することを鑑みた対処です。したがってファイル内に実行するコマンドクラスの指定をする記載もしていません。
コンパイルによりAnnotationProcessor
が動いたように思えますが、実際はテストクラスにて実行されただけです。
今回のケースではコード生成をしていないのでtarget
配下にコードが作成されることはありません。
基本となるライブラリを使用して独自のAnnotationProcessor
ライブラリを作成してみたいと思います。
実際に作ったコマンドは前回の記事でやったこととほぼ同じことをするものにしました*3。
説明は基本編との差分のみにします。
vermeerlab / apt-extend-sample / source / — Bitbucket
基本ライブラリを依存ライブラリとして追加します。
加えて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の作成と登録(基本)』ではテスト内に作成しましたが、今回はきちんとメインパッケージに実装をします。
コマンド実行クラス:SampleCommand.java
処理対象アノテーション:TargetClass.java
、TargetField.java
テストのためにテストパッケージのprocessor-command.xml
に登録をしています。
メインパッケージのresources
にprocessor-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群をフレームワークとして配布して それを使う側の手順になります。
vermeerlab / apt-extend-sample-client / source / — Bitbucket
拡張API群をMavenでコンパイルしてローカルにMavenリポジトリが存在していること*5。
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
)はリポジトリからクローンした直後は、コンパイルしていないということで まだコードが生成されていないので エラーになっています。
generated-sources/apt
配下にクラスが作成されて コンパイル前にエラーになっていたクラスからエラーが消えました。
クライアント側はライブラリの依存だけとAnnotationProcessor
を動かすための記述をすれば良いだけです。
Processorのコマンドクラスをクライアント側で指定するので どういう操作をしようとしているのか分かるのでブラックボックスでは無いと思います。機能の提供側としては機能一覧としてprocessor-command.xml
に記載する内容を提示しておいて、利用側が必要なものを選択する、または不要なものを外すことで対応可能です。
実行ライブラリのクラスパスにprocessor-command.xml
を配置しないといけないません。つまり利用側が自分のresources
配下に機能一覧を追記したprocessor-command.xml
を配置しないといけません。最低でも提供側が定義ファイルに記述する内容をルートパッケージのJavaDocなどに記載したり、手引きに書いておかないと、何をどうしたら良いのかわかりません*6。
解消案としては、基本ライブラリのProcessorCommandManager
にてxmlからコマンドクラスのパス(文字列)を取得しているので、ハードコーディングをするということが挙げられるでしょうか。
*7
今回はコマンド作成と登録と起動をメインに整理しました。次回はテストについて投稿したいと思っています。なお説明に使用するテストコードはすでに今回のプロジェクト内に入っているものを使う予定です。
ブログ記載時の記述に一致するブランチを作成してリンクを修正。
*1:基本となるライブラリへの依存ライブラリを最低限にしておきたかったためです
*2:単にテストケースの網羅性を高くするためだけのものです
*3:したがって、あえてコードの主要な記載はコピー&ペーストにしています
*4:こちらを忘れて毎回 コードが生成されているのにクラスが参照できなくて「何でだ!!」と頭を抱える
*5:本記事を上から順番に実施していたら出来ていると思います
*6:このあたりを良い感じにできれば良かったのですがクラスローダー周りの実装力が無かったため断念しました。
*7:個人的には このあたりについて自分なりの落としどころを考えて もう少し対応したいと思っていますが、とりあえず作ろうと思っているものを一通り作ってから改めて取り組もうと思っています。
読みました。断片的に収集していた増田亨さん(@masuda220)の知識を理解するのに良い本だと思います。もしDDDについて調べていて増田さんのスライドなどを見て「もう少し詳しく知りたいかも」と思った人は読むことをお勧めします。
P60:
EnumでStrategy
を管理するという言い方になるのかな?確かにこういうのは分類と実装を意味ある塊に整理出来るので良さそうです。
調べたら、こういうのもありました。
【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。
書籍と私の好みの違いの整理。
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で言っていることだとしたら、私の「契約による設計」の書評はズレているように思います。いずれにしても、このあたりは具体的な実装および実現技術を踏まえないとイメージも難しい気がします。ボンヤリ「こういうことかな?」というのはあるのですが、私は言語化できていないです。
処理の順番ではなく、もっと宣言的に書きたいんだよなあ。たとえば、ガード節は、メソッドの外側でアノテーションで宣言するとか、メインの記述の後に、制約として追記するとか。
— 増田 亨. (@masuda220) 2017年8月17日
2017/8/28 追記
入力値検証はBeanValidationにしてドメインロジックに混入しないことにするってことかな?
P285:品質保証
「分析と設計が一体となった開発」についてのアンサーとして(かなり端折っていますが)分析者が実装まで行うというのは同意です*9。
それとは別アプローチとして、実験的にこういうのもあるのでは?と思うことを追記しておこうと思います。例えばペアプロ(どちらも実装ができる人+どちらかが仕様への理解が明るい)とかモブプロ(要件は分かるが実装は出来ない人も参加することを想定しているので厳密なモブプロとは違うけれど)とか、ユーザー自身も含めた分析者とドメインの理解が浅いけど手は動かせる実装者が一体になって開発するというような「仕組みの構築」も一案としてあるかなと思いました。
例えば、モブプロを通じてリファクタリングをしないと、開発スピードが だんだんと遅くなるという体験の共有も大切かもしれません。
だんだん開発スピードが遅くなっていくのをどうやってとめたら良かったんだろう? - Mitsuyuki.Shiiba
とはいえ、私が言っている案の方がSIerの現場だと非現実的な気がしていますが(ペアプロは出来るかな?)。
勝手に私が期待していて物足りなかったところ。
P211:
「複数の関心事が混在している「何でも画面」を提供する場合は、ビュー専用のオブジェクトを…」について画面とドメインの連携で書かれていると思っていました。タスクベース画面はウィザード操作的であり業務習熟度が上がった担当者にとっては まどろっこしい入力になります。また高い習熟度は「関心事」として捉えられる範囲が広がるので「何でも画面」と一括りにもできないと思います。とはいえ全部のデザインについて1冊の書籍で網羅することを求めるのも違う気がするので、あくまで「個人的に物足りなかった」という感想です。
本質に関係のないところです。
Javaっぽくないかもと思ったりしたところ。この辺の作法は好みもあります。
メソッドの行数を減らすために「{}」を使っていないような印象を受けました。スコープの範囲を明確にした方がバグが生まれにくいので個人的には好きではない書き方です。実際事故も起きています。行数を少なくすることは手段であって目的ではないと思います。
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
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:別にお客様は神様です などと思いませんが
*6:であったとしても、本質的に満たそうとしている事前条件不正を呼出元に通知するという目的は同じです
*7:チェック例外の良し悪しは横に置いておきます
*8:私は非チェック例外派なので後者を選びますが
*9:というか、設計から実装まで出来るメンバーが集まっているのであればオブジェクト指向云々に関係なく、それなりに高い品質を担保できるとは思います。実際、初見メンバーで各社から集まってもらったチーム(自社で開発技術がある要員は実質私だけ)での開発をしましたが、設計力と実装力が一番ある人をプロト開発に、実装力はあるけれど経験年数が若い人をコアドメインの設計者に、そして私は方式検討と仕様課題と実装課題の検討にのみ注力することで、超短期間の開発であったけれども どうにか乗り切ることが出来た経験はあります。
*10:MSXの1画面プログラムみたいな。凄いとは思うけど写経してゲームはしたけど、ちっとも意味は分からなかった。Cのポインタ使いも同様。複雑な正規表現も同じく。ただ正規表現については、自分がもっと勉強をしないといけないと思います。