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

Javaで主にシステム開発をしながら思うところをツラツラを綴る。主に自分向けのメモ。EE関連の情報が少なく自分自身がそういう情報があったら良いなぁということで他の人の参考になれば幸い

Pluggable Annotation Processing API でコード生成

vermeer.hatenablog.jp

で紹介したツールのコード生成のベースとなっているプロジェクトについて紹介したいと思います。

概要

本プロジェクトを使用することでのメリット

  • AnnotationProcessorを統一的に操作できます
  • 定義用XMLで外部からパラメータを指定できます
  • 各Elementの操作のヘルパーが準備されています
  • テストが統一的に行えます

拡張のやり方の詳細については、Sampleとして、READMEに既出の「プロパティファイルからEnumを生成する」の実装内部の説明をしていますので、そちらをご確認いただければと思います。

maven/org/vermeerlab/annotation-processor-javapoet at mvn-repo · vermeerlab/maven · GitHub

Code

コード生成するプロジェクト自体を拡張したい場合は、こちらのコードを参照してください。

BitBucket

コンパイル時にコード生成すると何が嬉しい?

自動生成というと、いわゆる「超高速開発ツール」などと言われる、エクセルなどから作成したコードを使用するパターンがあると思います。

あくまで私の過去の経験ですが、ツールによる自動生成で課題に思ったのは「ドキュメント(もしくは それに準ずる資産)と 生成されるコードの版数が一致しないこと」です。

開発者がドキュメントから必ずコードを生成してくれれば良いのですが、手に届くところにコードがあるということで、どうしても直接コードを修正してしまうことがあります。

そうなってしまう理由は色々とありますが、いずれにしても、こうなると結構しんどくて、結局 コードが正しいとならざるをえず「ドキュメントは後回しになっている自動生成してはいけない機能」みたいな謎ノウハウが生まれます*1

AnnotationProcessorによるコード生成は、参照資産と常に一致した状態を保てるところが 大きなメリットです。

AnnotationProcessorにより自動生成したコードは、実質「クラスファイル」と同レベルです。 targetフォルダに、生成したコードとして確認することはできますが、コンパイル都度 再作成されるためです。

アイディアとして

今回、私はプロパティファイルからEnumクラスを作成しましたが、エクセルを入力資産に指定することも可能だと思います。

よく揶揄されるように思う*2 SIer+自動生成 ですが、AnnotationProcessorという仕組みと組み合わせると、Java標準の技術の範疇でビルドプロセスの中に組み込めるので色々と面白いことができるような気がします。

自動生成ツールと別アプローチとして結構有効だと思いますので、SIerの共通部品とかフレームワークとか開発支援とか言われるチームの方は、一度 検討されてみては?と思います。

懸念

資産からコードを生成した上でコンパイルをするため、資産が増えてくると コンパイルする時間が長くなって無視できなくなることが懸念されます。

思いつく対処としては、以下など考えられます。

プロジェクトを分けて分割コンパイル

単純ですが、それなりに効果はあると思います。

例えば 更新頻度が高い資産と、低い資産を分けるというような やり方はどうでしょうか?

更新管理ファイルを保持する

ファイルのタイムスタンプ比較するための更新管理ファイルを設けて、AnnotationProcessorの#initで読み込むようにすれば クローン後の初めの一回は重いかもしれませんが2回目以降は早くなることが期待できます。

さいごに

AnnotationProcessor + maven + classloader(リフレクション)で踏める地雷は大体踏んだと思っています。

本ブログを読まれて「こんなことできるのかな?」ということがありましたら、出来る範囲でお答えしたいと思いますので、気軽にご連絡いただければと思います。

*1:まだだそれを「管理」出来ていれば幸せ

*2:と私が思っているだけかもしれませんが

プロパティファイルからEnumを生成

Pluggable Annotation Processing APIを使用して、プロパティファイルからEnumクラスを生成するプロジェクトです.

ビルドの都度、自動で生成するので常にプロパティファイルとEnumの整合性が取れた状態が保てます.

vermeer.hatenablog.jp

の続きであり、私が作成したいと思っていたプロジェクトでもあります.

概要

本プロジェクトを使用することで以下のことが実現できます.

  • コンパイル都度、自動生成するので常にPropertiesファイルとEnumの整合性が取れた状態が保てます
  • Propertiesファイルへの参照を統一的に行えます
  • Propertiesファイルを参照する実装がタイプセーフになります
  • 実装中にPropertiesの値を確認できます(IDEによるJavaDoc参照を経由)
  • 国際化対応(実装中、稼働中)の制御ができます
  • 指定した置換文字数がProperties値の内容と相違している場合は実行時例外にします

使い方

詳細の使い方は、プロジェクトのREADMEを参照してください.

maven/org/vermeerlab/annotation-processor-command-propertyfile-enum at mvn-repo · vermeerlab/maven · GitHub

プロジェクトのCode

BitBucket

以下の紹介は本ツールの作成解説のようなもの。

似たようなものを作るのであれば参考になるかと思います。

vermeer.hatenablog.jp

さいごに

当初、Pluggable Annotation Processing APIを使ったツールとして コンパイル都度、ResourceファイルからEnumを自動生成するものを作っていて、それを紹介する形で 実装サンプルとして紹介できれば と思って記事を書き始めました。

そのまま それを紹介をしようと思ったのですが

  • 1つの記事で伝えることが多くなりすぎる
  • 大きな1つのプロジェクトよりも段階的に説明をした方が読み手にも伝わりやすいし、自分の整理にもなるのでは?

と思って「Pluggable Annotation Processing API Sample(実践編…)」として 当初のプロジェクトのパッケージを分割しつつ 記事を書くことにしたのですが、それが迷走の入り口だったというか、整理中に気が付いたことや見直したいことが どんどんと発生して記事の途中に それ以外のことを掲載することになってしまいました。

結果、まとまりのない状態になり 書き手である私自身も振り返りにくい状態になってしまいました。

始めから焦らず ちゃんと作りきってから その解説という風にすれば良かったな、と。。

予想よりも躓いたのは、AnnotationProcessorとMavenとClassLoaderの組み合わせでした*1

動的にあれこれしなければ、そこまで困らなかったと思いますが、結果として色々と勉強になったので良かったかな、と思っています。

*1:単体のテストではうまくいくのに、結合テストをするとダメだったり、テストではうまくいくのに プロジェクトを参照するクライアントアプリ側で実行しようとしたらダメだったり。

【考察】設計について(2)

要件定義からテスト設計までの流れで個人的に思うところのメモ(2)

自分の経験の整理

vermeer.hatenablog.jp

の続き

いきなりモデリングはやめよう

(また?)

理由

モデリングのような抽象度の高いことよりも、個別具体のところから整理していった方がユーザーにとっても開発側にとっても分かりやすいと思うから。

モノを明確にする

まずは業務フローという「アクター」と「コト」の整理。

しかも、ユーザーにとって分かりやすい表現に留めてシステム設計的な色合いの極力ないもの。

次は「モノ」の整理をしましょう。

取っ掛かりとして簡易

「こういうコトがしたい」に対して、「どういうモノを通じて」という肉付けをしていくのは ユーザーにとってイメージやすいと思います。

イメージしやすいということは 共有・共感もやりやすいです。

既存システムや既存運用がある場合、実際に使用している資産(帳票とか画面とか)を題材にして、先に作成をした業務フローとの関連付けを行います。

新規の場合は 先に作成した業務フローを実現するために必要となりそうな項目のラフスケッチを作成します。

「こういうモノがいるよね」を明確にすることが目的です。

必要なモノに対して、必要そうな項目を列挙しながら、すべての業務フローを総なめ出来れば良いと思います。

レイアウトなどのデザインは考えなくていいです。というよりも、考えない方が良いです。

ユーザーにとって分かりやすい表現

業務用語など含めて ただ要素を列挙しているだけなので 何よりもユーザーにとって分かりやすい表現です。

ユーザーにとって分かりやすいけど、開発側にとって曖昧であったり分かりにくい内容は 質問する事で、ユーザーにとっても「そうそう そうだね。業務担当外の人からすると そういう表現だと分かりやすいですね」となって*1、フロー(コト・動詞)に関連する名詞(モノ)について、ユーザーと開発側に知識が共有・蓄積していきます。

ファンクションポイントが明確になる

いわゆるファンクションポイント(入力・出力・記憶)の基本情報が把握できるので、システムで実現したい目的の目的語が明確になります。

「この結果の根拠って どこからやってくるんでしたっけ?」「この結果は 誰の(どこの)何をするときに使うんでしたっけ」を把握することが大事です。

目的語のバリエーションについて 業務フロー(動き)だけでは分かりにくい実態が見えてきます。

帳票や画面についてはレイアウトではなく要素が重要です。似て非なる目的のものは、別物として扱った方が良いでしょう。既存システムがある場合、出力項目名称は同じでも意味の違う帳票や 意味が同じだけれどレイアウトが異なる帳票について「なんで これは違うの?」ということは フローよりも実物を対象に会話をする中で明確になりやすいです。

Note

とにかく一気に!

業務フローを作業量の分母として、とにかく一気に早く「モノとの関連」を整理することが大事だと思います。

要件の整理は時間をかけすぎると 要望を盛り込み始めてしまいます。要件は漏れなく整理しないといけませんが、初心に描いた「これはやりたい」は業務フローとして作成されています。このフェーズは「やりたいこと」の整理ではなく、「実現するために必要なもの」の整理です。設計の時間を経ることで「この要件が満たされるのであれば、これも出来るよね?」と発散していっている可能性があります。

「このシステムの意義と実現したいこと」をブレなく意識して取り組む熱量を維持できる時間は そんなに長くはありません。

少なくとも一通り早くスルーすることを第一の目標とすることをお勧めします。

デザインは考えないこと

レイアウトデザインや画面遷移のようなプロセス分割は考えないことです。

個別具体なものは あくまで「モノ」を整理するためのインプットであって アウトプットではありません。

業務フロー(コト)におけるモノとして必要な要素と緩い要素の集合の関連の整理に留めて、1つ1つに時間をかけるよりも「フローとの違和感のなさ」「モノの表現の共有」を優先することを常に意識することが大事です。

ユーザーと開発側のコミュニケーションにおいて デザインやレイアウトを除外して、モノの整理をするのは地味ですが 結構な胆力がいるかもしれません。

どうしても帳票や画面など「具体的な作成したいもの」を目にすると それに注意を向けてしまいますが、それは良くありません。もし、ユーザーと開発側の整理の中で デザイン に関する事で紛糾することがあったら いったん、その題材は保留して 次のモノの整理をするなど 少し時間を空けるなどした方が良いと思います。

ただし、既存システムがあって デザインに業務的な意味がある場合、開発側は議論をせずヒアリングに徹して デザインの裏にある本音の収集に努めてください。

実現可能性の担保が目的ではない

業務フローに対する実体の洗い出しと それを通じて個別具体な用語の共有が目的です。

整理したモノが実現対象ではありません。この点については ユーザー側にも十分理解しておいてもらうことが大切です。毎回の会議都度 枕詞のようにいうくらいしておいても良いでしょう。それくらいでも気が付いたら「これは対応される出力資産だと思っていた」と言われかねません。同様に開発側も実現可否を匂わせるようなことを不用意に言わないことです。重複になってしまいますが、そういうこともあって 変に実現性を匂わせることになりかねないので デザインに関することを ここで深掘りしないことが大事だと考えます。

向き合うのはシステム

(前回の考察と同じですが、大事だと思うので繰り返し)

ユーザーの要求に応じてシステムを構築するのがスタートかもしれませんが、最終的にはシステムが作り出す付加価値に向き合うことです。

この付加価値は得られるサービスからの直接的な便益だけではなくて 運用コスト(改修コスト)や開発コスト(品質など)も含めた話です。

ユーザーの要求を全てに行き渡らせると多分実現不可能なシステムになります。ここで言う「全て」というのは、例えばデータ保存、通信方式、分散制御について ユーザーが「要求」してくることがありますが それをそのまま受け止めると実現できるものも出来なくなります。

そういうことを言いそうだなぁ、と思ったときに ユーザーに 現時点で あなたたちが注力すべきは システムの目的ですよ という拠り所として 業務フロー+モノ(何をもって目的が達成されるのか)を活用すると良いと思います。

Hint

モノの整理の成果物は 業務フローとペアとなる項目名とその説明の表があれば十分だと思っています*2。あとは項目名の列挙だと扱いにくいので分類・識別となる表題があると良いでしょう。

(同様に業務フローは図示せずとも 表で表現しても良いと思っています。)

項目名についても、短縮しすぎて全て「名前」に集約するくらいだったら「顧客名」「ユーザー名」「操作者名」と一見似ているんだけど違いがあると分かる表現の方が良いと思います。また、端的に意味を示す単語を考えるくらいなら だらだらと意味を並べたような項目名でも良いと思います。とにかく大事なのは違いがあるのなら そうとわかるようになっていれば良いので 早さを優先した方が良いです。

モノからコトを検証する

とにかく、早く一旦はやりきりましょう、という理由は1回1回の密度よりも 観点を変えた繰り返し回数を増やしたいためです。

コトからモノを整理したら、次にモノからコトの関係を検証・整理します。言い換えたり、より良い説明ができたり、 不足または不要な項目の発見が必ずあります。

モノを通じて具体的に考える中で 作成済みの業務フローとは 似て非なるフローが必要になりそうだ、ということが往々にして見つかります。また同様に始めは違う業務フローと思っていたけれど本質的には同じじゃないか?という業務フローも見つかるかもしれません。

設計は上下左右色々と角度を変えてみると、どんどん肉厚でしっかりしたものになると思います。

モノとモノを整理する

ここまでくると、アクター(主語)・業務フロー(述語)・モノ(目的語)、を通じてユーザーと開発側で同じストーリーを話せるようになっているのではないかと思います。

少なくとも それを手助けする資産は手元にあります。

次の工程にむけて モノとモノの関連やグルーピングを簡単にします。

規模にもよるとは思いますが、基本的にすべてのモノを何の分類もせず扱うことは難しいです(単純に記憶しきれないというレベルを含めて)。

ユーザーと開発側で理解しやすい表現に分類をしてください。

分類は意味で行うだけでなく、たとえば組織という要素も加味するようにすると良いでしょう。

「お客様の情報だから、これはお客様管理だね」とすると、巨大な集約になりかねません。「クレーム対応部門 お客様情報」「お歳暮部門 お客様情報」という感じで 個々の項目で見た場合、重複があっても良いです。そのあたりの正規化のようなことよりも、意味と役割を優先してください。場合によっては表題に対して項目名を追加してください。

課題は無いの?

あります。

上述のアプローチは「データ中心アプローチ」に近しいものだと考えています。

データ中心アプローチの欠点としては、データ項目の網羅的な洗い出しが分析の基本となるため、特に大規模システムでは分析に時間がかかることが挙げられます。 アーキテクトの審美眼(P43)より

という指摘は もっともだと思います。

その点については、マイクロサービスのように「組織」を整理の基準の1つとして盛り込むことで、網羅的な項目洗い出しの範囲を限定することで多少は軽減出来るのではないか と考えています。

データの分析を網羅的な全体のデータ項目とすると、常に全体を把握しなくてはならず 分析が直列的になってしまいますが、業務フロー(つまり組織としての実行プロセス)をペアに検討すれば 、業務フローを中心とした作業分割ができて組織毎に並列的に分析ができると考えます*3


ここまでで、業務フロー(コト)とモノのベースが整理できました。

作成された成果物は、業務フローと それに関連する項目名が列挙された表 です。

UMLなどの〇〇図みたいなものではありません。

古式ゆかしきものかもしれませんが、ユーザーと開発側の共有資産として意味があり、困ったときの原点回帰するためのドキュメントです。


まだモデルは設計しない

まだ(?)モデル設計はしません。

ここからは あまり賛同を得られることも難しいだろうと 逃げの一手を先に言っておきたいと思います。

テーブルの物理設計をしよう

モノの情報と そのグルーピングを元に「テーブルの物理設計」をすることをお勧めしたいと思います。

なぜ物理設計から?

概念設計(モデリング)のようなトップダウンアプローチには分析者(設計者)の力量による影響が大きく出てしまうからです。

ブレの無い分析が行える点もデータ中心アプローチの特長の1つです。
アーキテクトの審美眼(P42)より

というところに私は共感しています。

限られた物理的・人的リソースの中で やりきるのであれば 私はこの「ブレの無い(もしくは少ない)」アプローチを選択した方が良いと考えます。

既存のシステムがあったとしても、ユーザーが そのシステム内部の分析者足りうる存在であるとは言えません。時としてユーザーが分析者として振る舞うがゆえに炎上することすらあります。あくまでそのシステムは様々な制約の元で構築されたものであって これから構築するシステムに適合するアーキテクトでは無いからです。

それをするくらいなら、ボトムアップアプローチで具体的なテーブル定義をしてしまった方が まだマシだと考えます。

概念的・論理的な整理を全くしないでテーブル定義は作成できないかもしれませんが、少なくとも成果物はDDL(もしくは それを出力するツールのデータ)として、それ以外の成果物はスキップして良いと思います。

変更への心理抵抗を減らしたい

概念→論理→物理 という設計をしましょう、というのが王道だというのは分かっていますが、私の経験として、形式美になっていることが多いように思います。

むしろ、ウォーターフォールといわれる開発において、このプロセスを歩むがゆえに 工程として物理設計まで確定したテーブル設計を変更することに対して、心理的な抵抗が生まれているとすら思っています*4

開発する側としては 実際のテーブルに対してSQLを実行できれば、プロトタイプの実装や 性能検証ができるので開発の選択枝が増えることは好ましいと思っています。

際限なくテーブル設計を変更しても良いということはありませんが、机上で実装をしない人たちによる喧々諤々よりも 実装を通じてリファクタリングをする方が 結果として無駄が無いというのが私の実感です。

そんなこと出来るの?

一定の割り切りさえすれば出来ると考えています。

モノの整理ができていれば、ラフであるにせよ 業務フローの実現に必要な要素は列挙されています。

項目の属性を集約としてのテーブル(エンティティ)と関連をもって正規化をすれば「草案として」のテーブル設計は出来ると考えています。

割り切りとしては、関連の有無、項目の有無に絞って、項目の桁数は暫定とする、というようなことです。

Hint

とはいえ、テーブル設計そのものってどういう感じでやったらいいの?というところがあると思います。

個人的には「楽々ERDレッスン」がお勧めです。

レシートからデータ設計を試みる、というような身近な個別具体的を用いた事例などが記載されていて 取っ掛かりとして良いと思います。

本書のER図について、私の考えと異なるものもありますが、大事なことはモノがあればテーブル設計はできる、ということです。

ちなみに

前回と同じく、このまま設計・実装をしていくと、フローを中心とした旧来型と言われる開発手法になると思いますので、簡単に手続き型のシステムが出来上がります。

私はDDDとかオブジェクト指向とか良いと思っています。

でもドメインの知識(システムに関連するコトとモノ)を整理せず それに取り組んでいないでしょうか?

コトとモノを実現するだけで事足りる開発に使う手法として適当でしょうか?

それでも、DDDとかオブジェクト指向ってやりたいです?*5

さいごに

改めて言いますが、私はDDDとかオブジェクト指向とか良いと思っています。

でも、それはシステムの価値を向上させるための手法であって、やりたいことの蒸留器ではないと考えます。

やりたいこと(関心事・ドメイン)の源泉とは別です。

やりたいことの主語は、ユーザーです。やりたいことが明確になった上で、関心事(ドメイン)の整理がスタートするのではないでしょうか?

私の経験としては、途中参画をして*6ドキュメントは整っているような気はするんだけど、しっくりこない感覚を覚えたプロジェクトは、このあたりが原因だったように思います。

開発側は開発する事に目が向くため実装をしていくとアリの目になりがちです。そういう時に生きてくる鳥の目ドキュメントが業務フローとテーブル定義だと思います。

さて、今回はこのくらいで終わろうかと。

*7は、何を整理しようかな。モデリング的な話かなぁ*8

*1:前回と同じ表現ですね

*2:例えばIPO

*3:実際の大規模だと、こんな大げさに言わなくてもドメイン別のチームがあってやっているとは思いますが

*4:ウォーターフォールがダメでアジャイル万歳と言っているわけではないです

*5:…というと、オブジェクト指向とか 止めといた方が良い、となってしまいますね。

*6:といってもSIerの参画なんて、ほとんど「途中参画」ですけどね

*7:1週間後か、1か月後か、1年後か分かりませんが

*8:モデリングとか言っていますが、辞書的定義とか良く分かっていません。とりあえずUMLのような設計図法を用いて行っている設計をモデリング、と言っていると思ってもらえればと思います。

【考察】設計について(1)

要件定義からテスト設計までの流れで個人的に思うところのメモ

自分の経験の整理

いきなりモデリングは止めよう

理由

モデリングをしていると、仕事をやった気持ちになるけれど 実現してほしいことに近づいているか?というと そもそも近づくべき目的を共有していなかったということになりがちだと思うから。

なんとなく分かったつもりで抽象度の高い整理にむけて手を動かすのは良くない。

ユースケース図も同じ。

まず業務フローを作ろう

抽象度の高いところよりも、まず個別具体的なところを整理することが大事だと思う。

ユーザーにとって大事なのはモデリングでもオブジェクト指向でもアジャイルでもDDDでもなくて「問題を解決してくれること」。

ユースケース図とかモデルで分かった気持ちになる前に、その対象となる「ユーザーが実現したいこと」をちゃんと認識合わせから始めた方が良い。

この時点では ER図とかも、考えなくていいと思っている。

集中すべきは「あなたは何をどのようにしたい(しているの)」だけ。

みんなで問題や解決したいことの対象についての「ストーリー」を共有することもしないで モデリングとかやってはいけないと思う。

業務フローというと堅苦しくてドキュメンテーションな臭いがするというのであれば、システムで実現したい(もしくは している)ことのラフスケッチという言い方でも良い。

取っ掛かりとして簡易

モノコト分析、でコト(フロー)は、ユーザーにとっても話やすいところなので 仕様共有の取っ掛かりとして ちょうど良い。

  • 今はこんな感じで、これをこういう風にしたい(ユーザー)

  • それは何故ですか?(開発)

  • こういう問題を感じているから(ユーザー)

ひょっとしたら「今はこんな感じ」で終わるかもしれませんが まずは そこからスタート。

ユーザーにとって分かりやすい表現

ユーザー自身にとって分かりやすい表現なので、少なくとも その意味での意思不通にはなっていない状態です。

*1

ユーザーにとって分かりやすいけど、開発側にとって曖昧であったり分かりにくい内容は 質問する事で、ユーザーにとっても「そうそう そうだね。業務担当外の人からすると そういう表現だと分かりやすいですね」となって、ユーザーと開発側にとって分かりやすい表現(ストーリー)になっていきます。

アクターが明確になる(を明確にする)

アクター*2を把握することで システムで実現したい目的の主語が明確になります。

「これ、だれ(なに)のために やっているんでしたっけ?」というときに 拠り所になります。

アクターの属性(権限とか)によって フロー(動き)だけでは分かりにくい「なぜ」が見えます。

アクターが増えるとシステムの規模は激増(激減)もありうるので、ちゃんとやりましょう*3

やりたいことを共有する

ユースケースの整理(事前条件・事後条件の整理)やモデリング(システムの抽象化・データ構造*4)をしていると「あれ?これ 何をやりたいんだっけ?」ということになりがちです。

設計中は「やりたいことを整理している(もしくは できてる)」と思っていますが、いざユーザーと会話すると お互いに分かった気持ちになっているだけで 何を機能仕様として決定しているのか、やりたいことは充足しているのか ということが 明確になっていない状態だったということは起こりえます。

そういう時に「業務フローとして整理した〇〇は ユースケースとしてこういう風に整理しました。」と照合をしていけば 何を決めたのか ということが明確になります。

「フローにて課題と思われる〇〇は こうすることで解消できます」ということも あるかもしれませんね。

ユーザーと協力関係を築ける

相互理解は協力関係を構築するための第一歩。

地味かもしれないけど、このはじめの一歩をサボると、ユーザー・開発側相互に「彼らは良く分かっていない」と陰口を言う敵対関係にすらなってしまいます。

議論はすべきですが、敵対は無価値です。

Note

現行踏襲は業務フローではない

現行踏襲(=旧システムを見ておいて)は業務フローではありません。それは調査依頼といいます。分析ですらありません。調査依頼でもないかもしれません。旧システムの設計を実装から書き起こすという仕事です。良し悪しもありませんし、意思も目的もありません。ただの作業です。作業は設計とはいいません。工数としてお金をもらって作業はしますが、それを設計というのは間違っていると思いますし、やったとしても その作成物は転記をした以上の価値も責任もありません。

現行システム調査を開発側したとしても 現行業務フローを作成して そこでユーザー自身の表現として精査して合意形成をしないと炎上すること必至です。

ちなみに ちょっとだけ言い方を変えた「現行通りの結果が出ること」も業務フローではありません。*5

完全な(?)マイグレーションは別かもしれませんが、これについては別枠なものだと思うので割愛します*6

御用聞きではない

業務フローの整理は ユーザーのやりたいことを共有しているだけで 御用聞きではないです。

あくまで スタートラインと目的(目標ではない)の共有です。

「初期段階の状態を共有しただけ」ということをユーザーとも共有しておかないと 後でこじれます。

機能要件・非機能要件の確定でもないです。

旧システムがあると、システム化自体はできるんだろうと思ってしまうかもしれないけれど クラサバからWebシステムだと随分と作法が変わります。

ありがちなのがダイアログ。

例えばWebシステムでダイアログがフローの中に入ってくると工数が想定以上に跳ね上がります。

SPAなら出来ますよ!とかいう 出来る出来ないの話ではなくて、チーム体制や開発フレームワークなど全体(お金も含めた)の話です。

向き合うのはシステム

ユーザーの要求に応じてシステムを構築するのがスタートかもしれませんが、最終的にはシステムが作り出す付加価値に向き合うことを念頭にしておいた方が良いと思います。

この付加価値は得られるサービスからの直接的な便益だけではなくて 運用コスト(改修コスト)や開発コスト(品質など)も含めた話です。

ユーザーの要求を全てに行き渡らせると多分炎上します。ここで言う「全て」というのは、例えば ユーザーはプログラミングレベルの実装まで「要求」を出してくることがありますが それを そのまま受け取めると炎上するということを指しています*7

そういうことを言いそうだなぁ、と思ったときに ユーザーに あなたたちが注力すべきは システムの目的ですよ という拠り所として ユーザーに理解しやすい表現である業務フローは良い素材です。

Hint

私が実際にやっていた行動に近しいアプローチ手法かも、と思ったのは

マジカランドへようこそ! - マジカランド - 業務フローが誰でも簡単に書ける魔法のカード「マジカ!」

これ自体を使ったことは無いけれど、私の仕様の落とし込みに近い印象。

子供っぽいと思うかもしれないけれど、押さえるべき観点は押さえられていると思います。

過去の経験として

前提:私の立場は開発チームというよりも間接部門的なチームにいたので当事者ではないです。

大規模なシステム再構築プロジェクトの話

「ストーリーを共有することもしないで モデリング」なんてするの?と思うかもしれないですが 業務フローを作らずに、現行システムをベースにユースケース記述(図)を大々的に整理をしているプロジェクトがありました。

並行してモデリングもしていました。

たしかにユースケースモデリングを相互にブラッシュアップするというのは正しいアプローチだと思います。

でも、そもそも何について、どういうことを実現するために それに取り組んでいるのか ということの足場や問題点をユーザーと開発側で共有をしていない中では できるものも出来ないんじゃないだろうか?と私は思いました。

開発側に非常に頭の切れる人がいたけれど、逆にその人が頭が切れるがゆえに 他のメンバー(チーム)との認識している世界が大きく異なっていたようにも思います。ユーザーの基準がその「できる人」基準になってしまって、他が付いて行っていないという感じです。

地味ではあるし、双方にとって「当たり前」かもしれないけれど、それを再構築することが目的の開発ということで 初心に戻って 業務フローから丁寧に整理すれば良いのに、、と思っていました。

特に個人的には、既存システムのリプレースの時こそ 当たり前を整理することを怠らないことが大事だと思います。そぎ落とすにしろ 付加価値をつけるにしろ、現行という土台を壊すなり 見直すなりした先に次期システムと言われるものがあるわけですから。

結局、私は途中で離れたけど、少なくとも一旦は頓挫したんじゃないかなぁ、たしか。。

ちなみに

ただし 多分、このまま設計・実装をしていくと、フローを中心とした旧来型と言われる開発手法になると思いますので、簡単に手続き型のシステムが出来上がります。

手続き型は、事実ややりたいことの積み上げ型(ボトムアップ)だと考えています。取っ掛かりもあり 上述のような業務フローを作成するという前提を含めて ある程度 大きく間違ったところに行きつかないと思っています。

そういう意味では良い手法だとすら思っています。

そこにトップダウン的な意味でのオブジェクト指向といわれるようなモデリングユースケースによる整理で味付けをしていくと、保守や変更に強い(仕様が複数個所に分散していない・分散化しやすい構造)に近づいていくので更に良くなる、という感じです。 *8

ニュアンスとしては、ユーザーの要求を整理して、それをシステムにするとボトムアップで終わるんだけど、向き合う相手をシステムそのものとしたら ユーザーの要求に対して 何かしらの整理をトップダウンでする必要がありますよね?という感じです。

小さいとか使い捨てとか言われるシステムが手続き型でも良いんじゃないの?と言われるのは そういう意味からだと思っています。継続成長をさせるようなシステムは「ユーザー要望そのまんま」だけでは足りなくて プラスアルファとしてシステムとしての柔軟性や拡張性を持たせる必要がありますよね、という感じです。

さいごに

手続き型について、非常に規模が大きすぎて構築するだけでも泣きそうになる場合、確実に構築できる手段として選ばれているケースもあるように思います。やれSIerだ、IT土方だと 揶揄されるところかもしれませんが、ある意味では規模に対して思考停止して手を動かそう。。と涙目状態な開発案件という言い方も出来なくはないです。言い方は良くないですが「開発手法そのものが万人に理解できる」という訴求力です。それってエンジニアリングなの?という指摘はもっともだと思いますが、札束で顔を殴れる人たちにとって 理解不能であるというよりも 理解できるということは それだけでも価値があるということだと思います。

私自身は開発側(作り手側)として、積み上げた事実に対して 自分達の考えをトップダウンで盛り込みながら成長するシステムや開発(手法も含めて)にかかわる方が楽しいだろうと思っていますが*9

手続き型でシステムで実現したい目的のひな型となるものを整理した上で、本来目指したいシステムをリファクタリングする、というのをバランスよく満たすと プロジェクトの進捗・チームのレベルアップ・リリース後も含めたシステムの成長 がイイ感じになるんじゃなかろうか?と夢想しつつ、 とりあえず、本日の思いつき整理はここまでにしようかと思います。

*1:モデリングとかコンポーネント図とか 私がユーザーだったらどうでも良い表現です。ちゃんと 私の求めていることを分かっていますか?これを一番分かってほしい

*2:UML的な意味です。つまり内部・外部のシステムも含みます

*3:極論ですが、同じようなフローでもアクターによってシステム内部の挙動が全く違う ということは起こりえます

*4:モデリングとERは厳密には違いますが

*5:現行踏襲とすぐ言うユーザーは、合意形成の責任は取らないと宣言していると ほぼ同値なので 逃げられるなら逃げた方が無難です。もしくは責任に対する対価を請求額に計上しておいた方が良いでしょう。

*6:どこかの市とどこかのベンダーが訴訟をしていますが、そこについて私なりに思うところはありますが 割愛します

*7:素人が半端な知識で大工に口出しすると家が崩壊するのと同じ。良い職人は そこで施工主に「ダメだよ」と諭してくれます。でも、基礎工事をサボっているのを素人なりに見て取って「あれ大丈夫なの?」というのは重要です。このあたりの塩梅は難しいところですが、ニュアンスとしては こんな感じです。

*8:何をもってトップダウンと表現するのか?というところについては文脈によって結構違ってきます。たとえば「アーキテクトの審美眼」だと手続き型をボトムアップと表現してて、「現場で役立つシステム設計」だとオブジェクト指向の方をボトムアップと表現しています。どちらも それぞれの整理においては「正しい表現」だと 私は思っています。ここでは「アーキテクトの審美眼」側の意味で言っています。

*9:まぁ、一人開発ですから、関係ないんですけどね

FactoryはEnumで実装すると良さそう

Enum活用への提案

ヒントになったのは「現場で役立つシステム設計の原則」。

書評でも良かったところとして挙げた「ロジックをenumを使って表現する」のところ。

vermeer.hatenablog.jp

Enumにロジックを表現する、という例としては Strategyがあるようです。

【enum】メソッドの定義(3)−strategyパターンを使う方法 - THE HIRO Says

StrategyをEnumに持たせてはいけない理由 - にょきにょきブログ

後者では、気を付けておくべきこと、ということとしてEnumのStrategyを説明されているようですが、懸念されているところについては Factoryであれば問題ないように思います(たぶん)。

想定ケース

画面から入力された値に応じたクラスを生成したい。

生成されるクラス

生成クラス用インターフェース

public interface GenderInterface {

    public void action();
}

具象クラス

public class Male implements GenderInterface {

    @Override
    public void action() {
        System.out.println("Male!!");
    }

}
public class Female implements GenderInterface {

    @Override
    public void action() {
        System.out.println("Female!!");
    }

}

通常のFactory(Enumは列挙子のみ)

Enum

public enum GenderType {
    MALE, FEMALE
}

Factory

    public static GenderInterface create(GenderType gender) {

        switch (gender) {
            case MALE:
                return new Male();
            case FEMALE:
                return new Female();
            default:
                throw new IllegalArgumentException("gender code invalid");
        }
    }
}

使用例

public void orthodoxFactoryMethod() {
    GenderFactory.create(GenderType.valueOf("MALE")).action();
    GenderFactory.create(GenderType.valueOf("FEMALE")).action();
}

所感

画面側の値をEnumの列挙子名に変換することが必要になるので これだったら、そもそもEnumを使用しない方が良いと思う。

通常のFactory(EnumにStrategy)

Enumに判定用の定数とStrategyクラスを設定して、Factoryクラスは別に作成したケース*1

StrategyをEnumで保持していないところを除けば、今回の実験前の私の実装に近いものです。

Enum

public enum GenderTypeWithStrategy {
    MALE(0, new Male()),
    FEMALE(1, new Female());

    private final Integer genderCode;
    private final GenderInterface gender;

    private GenderTypeWithStrategy(Integer genderCode, GenderInterface gender) {
        this.genderCode = genderCode;
        this.gender = gender;
    }

    public Integer genderCode() {
        return this.genderCode;
    }

    public GenderInterface getGender() {
        return this.gender;
    }

}

Factory

public class GenderEnumStrategyFactory {

    public static GenderInterface create(Integer genderCode) {

        if (Objects.equals(GenderTypeWithStrategy.MALE.genderCode(), genderCode)) {
            return GenderTypeWithStrategy.MALE.getGender();
        }

        if (Objects.equals(GenderTypeWithStrategy.FEMALE.genderCode(), genderCode)) {
            return GenderTypeWithStrategy.FEMALE.getGender();
        }

        throw new IllegalArgumentException("gender code invalid");
    }
}

使用例

public void enumStrategyFactoryMethod() {
    GenderEnumStrategyFactory.create(0).action();
    GenderEnumStrategyFactory.create(1).action();
}

所感

Enumで区分値とStrategyを管理するので、情報の集約は出来ている感じ。

ただ Factoryの方をもう少しすっきり実装したい。

今回の実験前だと、私はreturnのところに 各インスタンス生成ロジックを記述していました。

EnumでFactory(パラメータ無し)

Enum Factory

public enum Gender {
    MALE(0, new Male()),
    FEMALE(1, new Female());

    private final Integer genderCode;
    private final GenderInterface gender;

    private Gender(Integer genderCode, GenderInterface gender) {
        this.genderCode = genderCode;
        this.gender = gender;
    }

    public static GenderInterface of(Integer genderCode) {
        for (Gender genderType : Gender.values()) {
            if (Objects.equals(genderType.genderCode, genderCode)) {
                return genderType.gender;
            }
        }
        throw new IllegalArgumentException("gender code invalid");
    }
}

使用例

public void enumFactoryMethod() {
    Gender.of(0).action();
    Gender.of(1).action();
}

所感

情報と振る舞いが集約された。

区分値が増えても生成ロジック部分(of)に手を加えなくて良い。

EnumでFactory(パラメータあり)

(2017/11/4 追記)

生成されるクラス

生成クラス用インターフェース

public interface SexInterface {

    public <T extends SexInterface> T create(String name);

    public void action();
}

具象クラス

public class Man implements SexInterface {

    private String name;

    protected Man() {
    }

    protected Man(String name) {
        this.name = name;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends SexInterface> T create(String name) {
        return (T) new Man(name);
    }

    @Override
    public void action() {
        System.out.println("Man:" + this.name);
    }

}
public class Woman implements SexInterface {

    private String name;

    protected Woman() {
    }

    protected Woman(String name) {
        this.name = name;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T extends SexInterface> T create(String name) {
        return (T) new Woman(name);
    }

    @Override
    public void action() {
        System.out.println("Woman:" + this.name);
    }

}

Enum Factory

public enum Sex {
    MAN(0, new Man()),
    WOMAN(1, new Woman());

    private final Integer sexCode;
    private final SexInterface sex;

    private Sex(Integer sexCode, SexInterface sex) {
        this.sexCode = sexCode;
        this.sex = sex;
    }

    public static SexInterface of(Integer sexCode, String name) {
        for (Sex sexType : Sex.values()) {
            if (Objects.equals(sexType.sexCode, sexCode)) {
                return sexType.sex.create(name);
            }
        }
        throw new IllegalArgumentException("sex code invalid");
    }

    public boolean isAssignableFrom(SexInterface sex) {
        return this.sex.getClass().isAssignableFrom(sex.getClass());
    }

}

使用例

public void enumFactoryMethodWithParam() {
    Sex.of(0, "taro").action();
    Sex.of(1, "hanako").action();
}

所感

生成時に情報を付与してインスタンスを生成することもできます。

どちらかというと、パラメータを使っている この例の方が よりFactory感がある気がしますので追記しました*2

追記(2017/11/5)

メソッドisAssignableFrom を追加しました*3

比較ロジックもEnum側で実装しておけば、コード内の比較ロジックも読みやすくなるかな?と思います。

例えば男女インスタンスをリストに格納して、あとから逐次判定をしたいときとかに使うイメージです。

ドメイン情報の漏れ出しにつながりやすいところだとは思いますが、個々のロジックに比較を実装するよりは統一的な操作を提供するやり方の方が前向きな気がします。

少なくとも、このやり方であれば 区分値の追加時にEnum列挙子の追加だけで実装側に比較ロジックを実装する必要はなくなります。

インスタンス型判定のメソッド

生成したインスタンスを比較したい場合に使用する共通的なメソッドです。

使用例

public void isAssignableFrom() {
    SexInterface man = Sex.of(0, "taro");
    SexInterface woman = Sex.of(1, "hanako");

    Assert.assertThat(Sex.MAN.isAssignableFrom(man), is(true));
    Assert.assertThat(Sex.WOMAN.isAssignableFrom(woman), is(true));
}

Code

https://bitbucket.org/vermeer_etc/enum-factory

さいごに

EnumでFactoryを実装しようと思った動機としては、区分値判定を減らしたいな、もしくは集約させたいな、と思ったからです。

言い方は良くないですが、集約すること自体を目的とした実験をしてみよう、というのを動機として やってみたら悪くは無さそうな実装例だったので公開したという感じです。

*1:Factoryクラス名が変なのは違いを表現するためなのでスルーしてください

*2:というか、自分の実装の実験としてはパラメータ有りである必要があっただけですが それを失念していただけです

*3:あとtypoも…

Optionalへの考察

nullを安全に扱うために使用するOptionalの使い方について、自分なりの指針を整理しておこうかなっと。

戻り値にのみ使用する

以上、おわり。

いや、、それだけではだめですよね。。

使用側に通知するため

使用側にとって戻り値がnull可能性の有無が分かれば防御的実装の有無について判断がやりやすくなるでしょう。結果としてNPEの発生リスクを軽減させることができるようになります。

多分、これがOptionalの基本指針だと思っています。

引数に使うのはダメなの?

使わない設計思想が良さそうに考えています。

nullかもしれないのだったらオーバーロードを使おう

引数がnullかもしれないというのであれば、明確に使用しないケースのメソッドを定義する方が使用側にとって分かりやすいメソッドになると思います。

Optionalを引数にしてしまうと、引数指定が汎用的な巨大なメソッドを作りやすくなってしまう。結果として、巨大でなんでもできるメソッドを1つになってしまって、使用側にとって分かりにくい状態になりそう。

でも型だよね?

型なんだから引数にOptionalを使ってもいいじゃないか、というところについての考察。

具体的には、戻り値をそのまま次のメソッドの引数に指定したいケースがあるよね、というところについての考察でもあります。

確かに、そういうことはやりたいのですが、でもそれはロジックが外に漏れだしつつある傾向なのかも。

「受け取ったものを何も操作せずに他に渡すだけ」となっているのでは?

「至る処で対象の戻り値に対して ifPresentisPresentがあって、しかもその分岐内の実装が類似」となっているのでは?

こういうケースの場合は、そもそもクラスにまとめた方が良いのかもしれません。

また、nullについては 例外と同じように、それを検知した時点で 塗りつぶさず対応をすることを繰り返しておけば 不意のNPEに悩まされるリスクは減るのではないかなと思っています。

参考

【Java】Optionalの正しい使い方を学ぶ #Java - Qiita

ちゃんと読んでいるわけではありません。

とりあえず、指針に則って実装してみて、なんかやりにくいなと思ったら指針の見直しのための読むためのリンクです。

2024.2.12 追記

https://blogs.oracle.com/otnjp/post/recipes-for-using-the-optional-class-as-its-meant-to-be-used-ja

さいごに

「お前はできてんのか?」

いえ出来ていないですよ(笑)

この指針に則って今後リファクタリングをしよう、という希望を込めた意思表明みたいなものです。