2022/11/29

UMTPモデリングフォーラムのパネル討論の感想

先週11/24に行われたUMTPモデリングフォーラムのパネル討論だけを聞いた。
ラフな感想をメモ。

【参考】
MF2022プログラム - UMTP 特定非営利活動法人UMLモデリング推進協議会

30、50、100人の壁の正体|山本 正喜 / Chatwork CEO|note

データモデリングの立場の中山さん、渡辺さんに対し、ドメイン駆動設計の増田さん、スクラムの原田さんという割りと立場や意見も異なる人達の討論だった。
話は色々あったが、その中で気になった点は2つある。

1つは、少人数のベンチャー企業から大人数の大企業へ進化する時にどんな壁があるのか?

スパン・オブ・コントロールの原則通り、1人の社長が統率できる人数はせいぜい5~7人まで。
そこから20名、50名、100名、300名と増えるごとに、ピラミッドのような階層構造を持つ組織になっていく。
すると、必ずその会社特有の業務システムが必要になってくる。

興味深かったのは、30名未満の少人数ベンチャー企業が、従業員数よりも多い個数のSaaSを運用している会社があるよ、という話。
おそらく、10名未満の少人数ベンチャー企業であれば、自社だけに作り込んだ業務システムは不要であり、SaaSを導入するだけで十分やっていけるだろう。
しかし、事業や業務がどんどん拡大していき、従業員も増えるが、それ以上に業務が複雑化して、その業務を支えるSaaSを次々に導入して運用してしまった。
しかし、SaaSにインポートするデータは、各事務員がExcelで作りこんで準備して取り込んで修正する作業を行っている、と。
これは、いわゆる「人間バッチ」と同じですね、と。

「人間バッチ」と揶揄する理由は、本来は事業や業務を回すための業務システムを独自に作って自動化する必要があるのに、それをさぼって、事務員が労力をかけて業務データをExcelで作り込んでSaaSに流し込むという手作業をこなっているからだろう。
結局、今はどんな企業であれ、税務会計だけでなく、事業や業務を回すためのシステムも必要になっているわけだ。

そんな状況ではデータモデリングがとても威力を発揮すると思う。
なぜならば、業務を回すための仕組みを分析するには、業務フローだけでなく、事務員が作り込んでいるExcelデータを抽象化したデータモデルが必要になってくるからだ。
そのデータモデルさえきちんと分析できれば、業務フローも画面もバッチも帳票もほぼ決まってしまう。
業務分析とデータモデリングは表裏一体と思う。

もう一つは、データモデリングが業務分析、ビジネスモデルの分析でとても有用なのに、なぜその技術が普及せず、軽んじられているのか?

パネラーの方も色々話されていたが、僕は本当の原因が他にあるような気がした。
単にIT技術者や事務員にデータモデリングの知識が足りないから、だけではないだろう。
データモデリングを習得するモチベーションがそもそもないことに真因があるだろう。

特に昨今のアジャイル開発に興味を持つ人であれば、古臭いデータモデリングよりもドメイン駆動設計の方に惹かれるだろう。
実際、増田さんが主催されるドメイン駆動設計の勉強会にはたくさんの開発者が集まるのに、データモデリングの勉強会はあまり開催されていないし、開催されたとしても正直あまり人は集まらない。

それはなぜなのか?
その理由は僕もわからない。
このテーマは今後も考えてみたい。


| | コメント (0)

2022/11/20

ComparableとComparatorの違いは何か

ComparableとComparatorの違いを混同していた。
自分用のラフなメモ書き。

【参考】
Java:任意の順番でのソート ? サイゼントの技術ブログ

compareToでJavaのソートは自由自在! 一からお伝えします

Java - オブジェクトの比較(==、equals、Comparable、Comparator)

Java Comparatorメモ(Hishidama's Java Comparator Memo)


結論は、
java.lang.Comparableは、Arrays.sort(Object[]), Collections.sort(List)で使う。
ソートを行いたいクラスにComparatorを明示的に実装する場合に使われる。

java.util.Comparatorは、ラムダ式やstream.sorted()で使う。
Comparatorは、ソートしたいクラスからソートのロジック部分を分離したいときに使われる。

違いは、Comparatorインターフェイスではなく、Comparatorはオブジェクトとして作成する。

class Book implements Comparable{} は、
処理の中で自分のクラス(Book)のソート順を定義します。
comparaTo()をオーバーライドして定義します。

//java.lang.Comparableは、自分のクラスと比較してソートする
public class Book implements Comparable{
// タイトル
private String title;
// 値段
private int price;
// 発行日
private LocalDate dateOfIssue;
@Override
public int compareTo(Book book){
return title.compareTo(book.title);
}
}
Collections.sort(bookList);

つまり、Comparableを格納しているListをソートする際は、Collections.sort(List)/reverse(List)を使うのが基本。
AA.compareTo(BB) => java.lang.Comparable を連想する。

たとえば、String#compareTo(String)、Integer#compareTo(Integer)になる。
Comparable.compareToのコツは、判断結果は小さい・等しい・大きいの3種類だけ。
値や文字列が等しいか、どちらが大きいかを判断するだけ。

オブジェクトや文字列や数値のListやSetを直接ソートしたい場合は、Comparableを継承して、compareToメソッドを実装する。
その方が楽。

一方、class BookTitleComparator(ソート用クラス) implements Comparator{} は、
処理の中で何かのクラス(ソートさせたいクラス)のソート順を定義します。
compare()をオーバーライドして定義します。

//java.util.Comparatorは、任意のクラスをソートできる
public class BookTitleComparator implements Comparator {
@Override
public int compare(Book book1, Book book2){
return book1.getTitle().compareTo(book2.getTitle());
}
}
bookList.sort(new BookTitleComparator());
または
bookList.sort((book1, book2) -> book1.getTitle().compareTo(book2.getTitle()));

ストリームAPIでは、基本はComparatorを使う場合が多い。
なぜなら、ソート処理にラムダ式を頻繁に使うから。

こんなことを調べてみると、ComparableはComparatorよりも古いAPIなのだろう。
Java8以後でラムダ式が生まれたので、Comparatorはその後に定義されたのだろう。

実際、java.lang.Comparableなので、import文は不要。
一方、java.util.Comparatorなので、必ずimport文は必要になる。

| | コメント (0)

Javaのsumはreduceで置き換えられる

JavaのストリームAPIとして、sum()とreduce()をよく混同していた。
JDKのsum()の定義を読んだら、Javaのsumはreduceで置き換えられると分かった。
以下は自分用のラフなメモ書き。

IntStream (Java SE 11 & JDK 11 )

Javaのreduceの使い方は2種類ある: プログラマの思索

Java Optionalメモ(Hishidama's Java8 Optional Memo)

一般に、sumはこんな感じで使われる。
int total = items.stream().mapToInt(Item::getPrice).sum();

sumはIntStreamだけで定義されているのに、Streamでも使えると混乱していた。
だから、map()の後に使えるのか、mapToInt()の後に使えるのか混乱していた。
理由は、数値配列型のStreamでしか使えないからだろう。

もう一つは、sumの戻り値がなぜOptional型ではないのか?
averageの戻り値はOptionalDoubleだし、他の統計処理メソッドもOptionalが多いのに、なぜsumだけがint型が許されるのか?

(引用開始)
sum
int sum()
このストリーム内の要素の合計を返します。 これはリダクションの特殊な場合であり、次と同等になります。

return reduce(0, Integer::sum);

これは終端操作です。

戻り値:
このストリームの要素の合計
(引用終了)

上記の定義を読むと、sumは、 return reduce(0, Integer::sum); と同じなので、必ず初期値0が設定されているので、NULLになる可能性はなく、Optional型はなくてもいい。
sumの戻り値の型はIntegerが正しいが、オートボクシングされてint型もコンパイルOKになる。

つまり、
int total = items.stream().mapToInt(Item::getPrice).reduce(0, Integer::sum);

int total = items.stream().mapToInt(Item::getPrice).sum();
は同じ。

sumをreduceで書き換える時、こんな感じでもOK。

int total = items.stream().map(Item::getPrice).reduce((x,y) -> x+y).orElse(0);
int total = items.stream().map(Item::getPrice).reduce((x,y) -> x+y).get();
int total = items.stream().map(Item::getPrice).reduce(0, (x,y) -> x+y);

reduce(op)の戻り値はOptionalになる。
だから、Optional演算子orElse, getが必要。

一方、reduce(0, op)の戻り値はIntegerになる。
だから、戻り値はオートボクシングされてint型もOK。

IntStream.mapToInt(ToIntFunction).sum()で覚える。
Stream.map(Function,Optional>).reduce(bo)で覚える。

ちょっとややこしいのは、averageの場合、戻り値=OptionalDoubleになる。
なぜならば、DoubleStreamでaverageが定義されているから。
Optionalではない点に注意。

DoubleStream (Java SE 11 & JDK 11 )

こんな例かな。
double avg = items.stream().mapToInt(Item::getPrice).average().getAsDouble();
double avg = items.stream().mapToInt(Item::getPrice).average().orElse(0.0);

OptionalDoubleをOptionalへ変換できるか?という記事もあった。

Convert OptionalDouble to Optional - Stack Overflow

下記の記事が分かりやすい。
JavaのOptionalは、ScalaのOptionクラスに相当する。
Optional型で扱えば、空かどうかの判定を自動判定してくれるメリットがある。

Java Optionalメモ(Hishidama's Java8 Optional Memo)

「OptionalとOptionalInt等の相互変換は出来ないようだ。(継承関係も無いし、変換に使えそうなメソッドも無い)」とのことなので、OptionalとOptionalは明確に異なる。
たぶん、IntStreamの場合とStreamの場合で異なるのだろう。

| | コメント (0)

2022/11/16

XPエクストリームプログラミングは偉大だ~時代がその設計思想に追いついた

XPエクストリームプログラミング入門をオンラインで聞いた。
改めて、XPエクストリームプログラミングは偉大だ、時代がその設計思想に追いついた、と思った。
ディスカッションの内容から感じたことをラフなメモ。

【参考】
XPエクストリームプログラミング入門 - connpass

ITの地殻変動はどこで起きているのか?~チケット駆動開発はなぜ生まれたのか: プログラマの思索

僕は、「XPエクストリーム・プログラミング入門―ソフトウェア開発の究極の手法 」の第1版の方が好きだ。
理由は、荒削りだが内容はとてもシンプルで、当初考えていた直感的な思いが直接的に表現されているからだ。

- eXtreme Programmingの魅力を探るにある「Embrace Change - 変化ヲ抱擁セヨ」のグラフが一番好きだ。

勉強会の内容は放談会みたいで面白かった。

XPはプラクティスありきではない。
プラクティスは具体的な実践方法。
プラクティスは価値を実現したものの一つ。
しかし、価値は抽象的すぎるので、プラクティスと価値の間に原則を置いて、プラクティスと価値を橋でつなぐ。
そういう絵がXPではよく出るが、その意味がようやく分かった。

XPのプラクティスは、そのテーマ単体だけで一冊の本になる。
たとえば、リファクタリングなら、リファクタリング(第2版)
テスト駆動開発なら、テスト駆動開発実践テスト駆動開発
継続的インテグレーションなら、継続的インテグレーション入門継続的デリバリー 信頼できるソフトウェアリリースのためのビルド・テスト・デプロイメントの自動化
見積もり手法や計画ゲームなら、アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~
ふりかえりなら、アジャイルレトロスペクティブズ 強いチームを育てる「ふりかえり」の手引き
シンプルな設計なら、エリック・エヴァンスのドメイン駆動設計
つまり、それぞれのテーマはとても奥深いのだ。

たぶん、それらのテーマは重要である、とケント・ベックは直感的に感じていたのだろう。
それを言語化して形式知としてプラクティスで取り上げたのはすごいと改めて思う。

福井さんいわく。
最初はスクラムは好きではなかった。
XPは具体的なのに、スクラムではプロセスはしっかりしているが、実際に実践しようとすると中身が分からない。
でも、今はスクラムは好きですよ、と。
スクラムはスクラムマスターの存在が凄く大きい、と。

アジャイル開発は自動車の運転のようなもの。
到着先は分かっていて、その道順が分かっていても、不確定要素があり、ハンドル操作で変化を受け入れながら進める。
つまり、運転は変化が全くない動作ではないし、変化を受け入れる動作範囲に落ち着くようにする。

時代がアジャイルにやっと追いついた。
アジャイラーは当初は周囲と戦っていた。
いかに導入するか、いかに普及させるか、に注力していた。
しかし、今はお客様からも、アジャイル開発を導入したいと言われる。

WF型開発の権化だったPMBOKがアジャイル開発の考え方を取り入れて、PMBOKの最新版でごっそり変わったのも大きいね、と。
実際、PMBOKは従来の分厚い数多くのマネジメント技法の知識体系だったのに、アジャイル開発の考え方を具現化して、価値や原則が主体のマネジメント体系に変わろうとしている。

アジャイル開発を支える技術が揃ってきたのも重要だろう。
特にクラウドが普及したおかげで、すぐにサーバーを立ち上げて、実際に動かしてみて、動かしながら作っていくのができるようになった。
それもコストをあまり掛けずに、個人ですら開発できるようになった。
つまり、継続的インテグレーション、継続的デプロイ、リファクタリング、テスト駆動、短期リリースなどを支える技術が揃ってきたおかげで、アジャイル開発を実践しやすくなった、と。

一方で、SIがアジャイル開発に追いついていないように思える、と。
発注者は自社で内製開発がしたいので、アジャイル開発を自然に受け入れやすい。
しかし、SIは受託なので、既に自分たちの標準プロセスを持っているし、人月単価のビジネスモデルもあるから、いきなりアジャイル開発に変換するのは難しい。

僕がXPやアジャイル開発に惹かれる最大の理由は、IT業界のきつい仕事から脱出できるための救いとして捉えていた面があったからだと思う。
多重請負の人月単価のビジネスモデルの中で、大量のプログラマや技術者をまるで仕掛在庫みたいに扱って、変動するバッファみたいに扱う手法がどうしても慣れなかった。
アジャイル開発は人重視であり、技術者の専門性を活かしながらチームでアウトプットを出していく、という思想に引かれていたのだと思う。
IT技術者として専門性を高めていくと自然にアジャイル開発に収れんされていくはずだ、と思っていた。
今もその思いは変わらない。


| | コメント (0)

2022/11/14

JavaのモジュールシステムでSPIとDIを実現するやり方

JavaGold黒本を読んでいたら、JavaのモジュールシステムでSPIとDIを実現するやり方をやっと理解できた。
理解できたことをラフなメモ。

【参考】
Javaのモジュールシステムの考え方をまとめてみた: プログラマの思索

クラウド上の開発がJavaに与えた影響は何なのか: プログラマの思索

Javaのモジュールシステムは複雑性をより増している: プログラマの思索

Java 9のモジュール機能「サービス(SPI)」と既存ライブラリの共存 (1/2)|CodeZine(コードジン)

DI(依存性の注入)とは依存性を注入するということである、、? - Qiita

module-info.javaに下記のような記述がある。
Sampleモジュールはどんな構造を持っているのか?

module Sample {
exports test;
uses test.Hello;
}

Sampleモジュールは、testパッケージを公開しており、test.Helloインターフェイスの実装クラスを利用する。
つまり、Sample単体ではコンパイルできるけれど、実装クラスがセットでなければ動作しない。

SPI(Service Provider Interface)を実装したHelloモジュールがない場合、コンパイルエラーにならない。
実際、ServiceLoader.load()は空のServiceLoaderインスタンスを返却するだけでスキップされる。
こんなプログラムになるだろう。

ServiceLoader loader = ServiceLoader.load(Hello.class);
Iterator iter = loader.iterator();
while (iter.hasNext()) {
Hello obj = iter.next();
System.out.println(obj.sayHello());
}

つまり、META-INF/services/helloというテキストファイルから読み込む時に、実装クラスが書いてなければ、ループ処理は空回りするだけ。

では、test.Helloインターフェイスの実装クラスtest.impl.HelloImplを持つHelloモジュールはどんな構造を持つのか?
たぶんこんな構造だろう。

module Hello {
exports test.impl;
requires Sample;
provides test.Hello with test.impl.HelloImpl;
}

Helloモジュールは、test.implパッケージを公開して、Helloの実装クラスHelloImplを提供する。
この時、HelloモジュールがSampleモジュールを呼び出し、Sampleへの依存関係を持つ点が重要。
なぜならば、test.Helloを利用するには、定義されているSampleモジュールをImportする必要があるから。

すると、SampleとHelloは依存性の注入(DI:Dependency Injection)を実現していることになる。
なぜならば、SPIの定義より、クライアント側Sampleはライブラリ側Helloの実装内容を利用しているのに、プログラムの依存関係としては、HelloからSampleへ逆向きの依存関係を持っているからだ。

つまり、普通は、Sample(利用側) --> Hello(提供側) になるはずだが、Sample(利用側) <-- Hello(提供側) なので、依存関係が逆転している。

まとめると、
Sample --> testパッケージ --> test.Helloインターフェイス
Hello --> Sample --> test.Helloインターフェイス
Hello --> test.impl.HelloImplクラス
という依存関係を持つ。

DIが実現されていることによってどんなメリットがあるのか?

まず、test.Helloインターフェイスを実装した具象クラスは、Sampleモジュールのモジュールパスに含める必要はない。
なぜならば、SPIの実装は後から提供されるので、実装に依存する必要はないからだ。
つまり、最初にIFだけ定義して公開しておけば、SPIの実装側ライブラリは後から実装すればいい。
IFという約束事だけ定義して、具体的な中身は後から開発できるので、開発スケジュールを調整しやすく、仕様変更にも柔軟に対応できる。

次に、test.Helloインターフェイスを実装するモジュールは、Sampleモジュールを再コンパイルする必要はない。
なぜならば、SPIの拡張ポイントとなるインターフェイスは変わらない限り、実装クラスに依存しないからだ。
つまり、SPIを利用する側のSampleモジュールと、実装クラスを提供するHelloは独立してコンパイルできる。

CやJavaなどでは、リプレース時にリコンパイルが必要だったりして、開発環境を準備したり、リリースするときは割と面倒だが、リコンパイルが不要であれば、過去にコンパイルしたモジュールをそのまま利用すればいい。

このモジュールシステムのサンプルから、僕はJDBCドライバの実装を連想した。
JDBCドライバでは、JDBCドライバのIFは既に定義されているが、各RDBごとにJDBCドライバの実装は異なる。
つまり、ユーザ側はJDBCドライバのIFを使ってプログラムを組めば良く、各RDBの実装の違いを意識する必要はない。
後から、利用するRDBのJDBCドライバを組み込めばいい。

2000年代にDIのアイデアが出た時はすごい設計技法のようなイメージだったが、2020年代の今となっては枯れた技術の一つにすぎない。
しかし、こういう依存関係を逆転する技術を利用して、プログラムの複雑性を手懐けようとする設計手法は、今後もその必要性は変わらないと思う。

| | コメント (0)

«astahとExcelの間でマインドマップをやり取りする方法はコピペだけ