継承か保有か
public class Price { private Long price; public Price(Long price) { this.price = price; }; public add(Price another) { return new Price(this.price + another.price); } }
みたいな Price
クラスがあった時に、
public class Yen { private Price price; public Yen(Price price) { this.price = price; }; public Yen add(yen) { return new Yen(this.price.add(yen.price)); }; } //... Yen yen = new Yen(new Price(10)); yen.add(anotherYen);
を定義するのか
public class Yen extends Price { // ... } // ...
を定義するか。
最初に考えたのは、「それぞれここがしんどいなー」だった
ここがしんどい継承
継承だと、子クラスのインスタンスを使う側は親クラスのドメインを知らないと結局どんなメッセージを打てるのかわからない。 複雑なアプリケーションならより一層、親子関係にあるすべてのクラスのドメインが果たす責務に詳しくならなければ...。
ここがしんどい保有
こっちがわりと有用なのではないかと思っていた。 親クラスのデフォルト実装を公開するのに、毎回関数を定義しなければいけないけど、そのかわり親クラスの振る舞いを一部しか知らなくて良い。
そもそも役割が違うのでは?
委譲か継承か、それが問題だ – Where is the mate to the Sock ?
やっぱりそうらしい。 ていうか社内の情報共有ツールで他の人が書いた日報のツッコミでこのブログを見つけた。弊社良いな。
いや、そもそも設計思想が違うのか??
Priceを継承したYenは、通貨単位の「円」はPriceの振る舞いを拡張したものだというドメインの理解を主張している。 一方でPriceを保有するYenは、通貨単位の「円」はPriceを自身の属性の一部だと主張している。
どっちがいいとかではなく、ドメインの理解が違うんだろうな。
Clojure/conj 2017 上映会 #1 に参加してきた
に参加してきた。 開催者様、会場の準備、ピザとコーラ、そしてそもそも開催自体していただいたこと、ありがとうございました! みたことある人がチラホラいる感じだった。
Rich Hickeyの話をちょっと再生しては訳が入る形式だったので、正直言ってRich Hickeyが何言ってるかは5%くらいしかわからなかったけど話についていけた。
Rich Hickeyについて
Rich Hickey、突然現代に現れた未来人だと思ってたけど大学卒業後18年間ふつうに(?)開発者やってた。 Rich Hickeyは 「Situated」なプログラムをイメージしている。ビジネス要件で簡単に自分が心血を注いだアルゴリズムが無意味になるのはよくあること。
現実世界は対応を迫られる不整合に満ちている
現実のアプリケーションでは純粋なアルゴリズムはほんのすこしで、残りはほとんど情報処理用のプログラムである。 しかもその純粋なアルゴリズムも、ビジネス要件で簡単に翻る。 情報処理というのは、たとえば外部ライブラリを使用したり、DBと通信したり、別システムと連携したりすることを指す。
これに時間軸を加えれば、システムが対応しなければいけない変化には「ライブラリがメンテされなくなった」「DB製品が変わる」「システムのI/Fが変わる」「要件が変わる」ことも含まれると分かる。
RichはORMでRDBMSのプロトコルをラップしてあたかもプログラミング言語の世界のプロトコルであるかのように使えるのは努力の方向性が違うと言っている。 んー、Datomicならいいのか?どういうORMを想定しているんだろう。
現実の複雑さ
talk-transcripts/00.29.49.png at master · matthiasn/talk-transcripts · GitHub
上の図がわかりやすい。
Rich Hickeyは上の図で型を間違えることを些細な問題と表現しているが... Clojureを触っていてもMapがくるかListがくるかそれとは別の何かが来るか気にしなければいけない。 とはいえそれは瑣末な問題で、プログラミング言語のレイヤーで解決しないことにした、ということかな。 今仕事でRubyを触っているけど、Rubyはメッセージ中心だから型が何かは気にしない、という思想があるのに対してClojureはそもそもそんなところ瑣末な問題だから気にしないわ、と切り捨てているのが、設計思想の違いを感じて面白いと思う。
Pythonとかの他の動的型付け言語はどんな思想で作られてるんだろう。
Immutabilityの背景
RichはPLOP(PLace Oriented Programming)という言葉を出している。 PLOPはイミュータビリティと相反するプログラミング手法で、メモリやディスクのアドレスと結合したプログラミング。 しかしそれではイミュータブルにできない。メモリのアドレスの中身に入ってる情報は刻一刻と変わる。
Clojureを支えるイミュータブルなシーケンスが以下らしい。
clojure/PersistentHashMap.java at master · clojure/clojure · GitHub
全然知らなかった。
情報は変化し続ける
静的型付け言語では、情報を操作するのがめんどくさすぎる。 クラスはデータのコンテナが意味を持ってしまったものだが、これを使ってEffectiveなプログラムを書くには情報を一つ一つクラス化しなければいけない。 具象を必要以上に持ち込んでしまったのが問題だ。
うーん...。 ドメインが多くて複雑なら、その分だけ扱う意味が増えてクラスが増えるのは自然なことだと認識してたけど...。 ていうか意味のないデータ構造だけでは、結局プログラマーは巨大なシーケンスのネストしたハッシュマップを指差して「ここにはユーザーのデータを変換したものが入ってるんだよ」とか話すことになる気がする。
巨大なシーケンスだとしても、その連想配列のキーに適切な名前をつければいいじゃない、ってことか、な?
Parochialismへの批判
ある異なるドメイン領域でユーザーをなんと呼ぶかが一致しないことは問題だという。 それは当たり前では...?
いや、そういうことじゃないか。 あるデータが持つ意味をクラスとして表現するのではなく、データのキーとすればいいでしょ、という話か。
結局ネットワーク越しに他のシステムに届くのはクラスではなくコレクションオブジェクト。 じゃあ自分のソースコード内でもそれと同じように書けばいいでしょって話か。
まとまらない... 読み直さなきゃ... Rich Hickeyが何言ってるかわからないから反論もできないのが悔しすぎる。
参考
Rich Hickeyのプレゼンのスクリプト。 github.com 今回の勉強会で使われた、上記プレゼンテーションの訳。 https://gitpitch.com/k2n/effective-programs-ja#/
Node.jsの抽象構文木を操作してソースコードを生成する
「ソースコード生成してみたい!けど、そういや1年くらい前にASTって言葉を知ってから何もしてなかったな」、と思ってASTを書いてそいつを食わせてソースを生成してみた。
もともとはさいきょうのスタブサーバーを作りたいねって話から始まったからそういう感じのリポジトリ名の以下リポジトリに実際に今夜書いたコードが乗っかってる。
構文木ってなに?
プログラムの構造を、(Javascriptの場合)JSON形式にパースしたもの。 何段階かに分かれているっぽい。
- もとのソースコード: ソースコードそのまま。
- トークン:
['let', 'hoge', '=', 'fuga', '(', '1', ')']
みたいな、実際に書いてある文字列を適切な箇所で分割したリスト。 - 木構造: それをもとにもとのソースコードを生成できるような連想配列。
詳しくは以下のブログエントリーで。詳しい記事ありがとうございます。
どうやってASTを生成するの?
ASTの仕様を知ってそれを満たすJSONを構築すれば、それをASTからソースに変換するライブラリに食わせればコード生成できる。 ソースコード ⇔ ASTのライブラリはJS界にいくつかあるみたいで、今回はEsprimaを使うことにした。 Babelもトランスパイラー?だから同じような機構を持ってるみたいだけど、特に理由なくEsprimaにした。
JavascriptのASTの構造はestreeという標準があるらしい。
けど、それを読むより実際にソースコードから生成されるASTを見たほうが早いなってことで以下で試す。
http://esprima.org/demo/parse.html
こいつに
let express = require('express');
みたいなのを食わせると
{ "type": "Program", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "express" }, "init": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "require" }, "arguments": [ { "type": "Literal", "value": "express", "raw": "'express'" } ] } } ], "kind": "let" } ], "sourceType": "script" }
みたいな木構造にバラしてくれる。 逆にこいつを、対応したコード生成機に食わせればソースコードが生成されるってわけよ。
これを使えば、カッコとかカンマとかシングルクォートとか改行とか気にせずに、オブジェクトをガツガツ組み立てていけば思うままのソースを生成できる。
これを世に出してGithubのStarを稼ぐんだ、という野望を胸に書くコードは、心なしかいつもよりギラギラして見えた。
3人チームで1日モブプロしたら、これはすごく圧縮された先行投資だとわかった
最近ペアプロの威力を実感する出来事があった。 2人の知識レベルが高まり合い、自分事感のあるソースコードが積み重ねられていくのを体験した。
そこで次はモブプロもやってみたいなぁと思っていたが、仕事で無理に導入しても自分事感の薄い対象を直す会になっちゃうしなぁ... と思っていた。 そんな今日このごろ、今のプロジェクトチームの3人のエンジニアが全員使わなきゃいけない、そこそこゴツいクラスがまだ存在しないということに気づき、3人で設計することに。
ここぞ、ということで最初2人で、その後すぐに3人で、ほぼ1日中ドライバーを交代しながらソースコードを書いてみた。 そんな1日の記録。
ルール
- 3人でやる。
- 他プロジェクトのチームメンバーにも声をかけて、よかったら見に来てねというオープン型。
- 15分でドライバーを交代する。
- 大きなディスプレイの横にホワイトボードも置いておき、必要ならそれを使って議論する。
やってみたら楽しい〜〜〜!でもめちゃめちゃ疲れる!
とにかくずっと考え続けることになる。 たとえナビゲーターだったとしても全く楽ではない。なんならむしろしんどい。 ナビゲーターは、ドライバーが進むべき道を考えて、他のチームメンバーが気分を害さない言葉で、論理的に自分の意図を説明しなければいけない。 他のメンバーが喋った内容を自分の考えと論理的に突き合わせて、より良い選択肢を選ばなければいけない。
今日のモブプロの間は必死過ぎて、メールもチャットも、ましてやTwitterも全く見なかった。 なんならトイレもちょっと我慢してた。 それくらい集中してたから、普段一人で黙々とやってる仕事を数時間に圧縮したような時間だった。
それは他のメンバーもそうだったみたいで、1、2時間経った時点で休憩をとったら全員ぐったりしてた。
うまくいったのか?
いったと思う。 1人で考えるには複雑すぎるし、そもそも3人ともが必要な機能なので全員で認識をあわせながらすすめておけば、今後それぞれが機能開発をする時に全く意図のブレがない状態でコードを書けるはず。そういう先行投資になったと思う。 ただ、1日の終わりにスタンディングKPTをやった感じ、今後すべてをモブプロでやる必要はなさそうだね、という話になっている。 つまり設計みたいなアウトラインを決める大事なところはみんなでやって、要求が明確だけど実装が難しいところはペアプロで、単に書けば良いだけのところは1人でやると良さそう、という話だ。
たしかにそうだと思う。
今後直した方が良いところは?
15分は短かったわ。 あとは、方針を見失うと全員が路頭に迷うから、「今はこのクラスのこの関数のI/Oを議論する時間です」とか「今はテストでI/Oを表現する時間です」とかの、「今何の時間だっけ?」を決して忘れてはいけないと思った。 そしてその「今この時間」を定義するのがカンバンなのであった。 終わってから「カンバンのチケット用意しませんか?」って話になって用意したら、実際何をやればいいか明確に定義できた。 良い。
「ドメイン駆動設計のためのオブジェクト指向プログラミングハンズオン」第2回に参加した結果、増田さんからPRにコメントをもらえた
お忙しいところ、増田さんのご厚意に甘えて図々しくPRを見てもらえないかお願いしたら、ちゃんとコメントをもらえた。 勉強になった。
対象のプルリクは以下。
あらかじめ、書いていて疑問に思った箇所をこちらで注記してあって、それに対する返答という形でコメントをいただけた。
ドメインオブジェクトがことごとく toString()
を実装することになる(がそれでいいのか...?)
良い。
自身を文字列表現するために、toString()を持つことが多い
たしかに、必要ないけどとりあえず用意したものは一つもなかった。 今まであんまり要らなかったのは、賢いオブジェクトを作り込むような設計ができていなかったからなのかな。
assertってアリなのか?
アリ。
メッセージは今回、
assert price.compareTo(BigDecimal.ZERO) >= 0 : "price is too small.";
みたいなものにしていたけど、本来はもう少し雄弁にした方が良いとのこと。少なくとも渡された値を表示はした方が良さそう。
計算結果ドメインオブジェクトを作って、そのコンストラクタに計算を任せる設計はアリなのか?
悩ましい。(が、ナシではないという意味だと解釈した)
今回のアプリケーションのクラス関係では、 Gross と GrossPerSubscriptionが、双方向に参照してしまっている。 GrossPerSubscription のコンストラクタには、Gross ではなく、Yen を渡すと解消できそうだとコメントいただいた。
ドメインオブジェクトのプロパティを文字列として返却する、ビュー用の関数が生える(のはアリか?)
一方、ドメインオブジェクトの設計が、(無意識に)ビューに引きずられがちになるという、ちょっといやな臭いを感じます。
たしかに!!!! 本当はもっとオブジェクト同士の関係を表す関数を生やしていきたかったが、うまいこと発想を膨らませられずビュー用のロジックばかり生やすことになってしまった。
DDD本、読む機運が今までになく高まっている。
「ドメイン駆動設計のためのオブジェクト指向プログラミングハンズオン」第2回でユーザー定義型を自分で作ってみた
ちょうど弊社内の同僚と輪読会をしていた書籍の著者であるところの増田さんがやるハンズオンがあると聞いて行ってきた。
第一回はこちら。動画で見てもなかなかおもしろかった。
アソビューについて
www.asoview.com の運営会社。
椅子がキャンプ用のやつで、会社特有感出てて面白い。
テーマは「型」
型とは、オブジェクトにおいて有効な値および演算を定義するもの = ストラウストラップ先生の受け売り。
ユーザー定義型を作って、モデルと実装を一致させるハンズオン
前説自体は、よくあるオブジェクト指向の概念の説明。 コードの臭いとか、リファクタリングの話とか。
ネタは「購入の平均単価」。
このビューを表現するユーザー定義型を実装しよう。 以下は、やりながら感じたことの箇条書き。
自分がどんなロジックで生成されるかは、誰が知るべき?
売上高 / 購入量
で導出される「平均単価」のインスタンス生成方法は、誰が知るべき? コンストラクタの引数か?
だとすると、
売上高.割る(購入量);
するための divide
メソッドの中身は、ただのインスタンス生成になってしまう...。
その場では、「割り算処理とはAveragePriceを返すことである」と表現できるかなと思って、それで終わりにした。
とはいえ計算には基本データ型が必要になる
Price + Price
とは書けないので足し算を実装する必要があるが、そのためにはせっかくユーザー定義型を作ったのに基本データ型に変換する必要が出てきてしまう。 それではせっかくラップした基本データ型が外部に露出してしまって、隠蔽した意味がなくなってしまう。 結局内部のデータ型が変更されたら変更が広がってしまう...。
結局この疑問はハンズオン中に解消できなくて、最後に増田さんに質問しに行った。 そしたら
- 「自分と同じクラスのインスタンスを引数に取るなら、引数インスタンスのprivateなプロパティにアクセスできるから、その性質を利用して計算する」
- 「あるオブジェクトをIntegerやLongに直す関数は、Javaのお作法といえるくらい一般的な手法だから、そういうメソッドを生やしてしまう」
とのことだった。1つめの、同じクラス内でならprivateプロパティにアクセスできる、というのは今まで知らなかった! privateという言葉の直感には反する挙動だけど、基本データ型を露出しないというルールを実現するには筋の良い手段だと思った。
同じ机の人が全員違うドメインロジックを想像する、という事実自体が大事
クラス数が3個の人もいれば15個の人もいる。 しかもそれらは比較的小さなクラス。
すると、それぞれのメンバーがどれくらい違うドメインモデルを想像しているのかが明確になった。 増田さんいわく、それらはどれが正解ってわけではなくて、理解が異なっていることが分かることが大事だという。
たしかに、実プロジェクトでこれくらい小さなクラスとパッケージの集合を作れたら、仕様の認識合わせがすごく楽そうだ。
懇親会
懇親会にもお邪魔して、アソビューの社員の方に話を伺えた。 Java(SpringBoot)で増田さん流のDDDを実現しようとしている会社として、どんなことをしているのか気になったので質問してきた。
アソビューは最初からJavaだったのか?どのタイミングで事業の開発スピードとDDDを両立したのか?
最初からJavaだったけど、お金になるとわかった時点でDDD化を検討しはじめて、今粛々と書き直しているところ。 いうても巨大なシステムなので、まだまだ道半ばではあるが、すでにDDD化されたサブシステムの変更コストが軽くなっているのは実感している。
フロントとバックで共通のドメインが出てくるんじゃないかと思うが、共通モジュール化する?
個人ユーザー向けシステムと出展企業向けシステムで同じ言葉を使うとしても、実態は異なるドメインなので、ドメインは共通化させないで始めると思う。 もし、「これだけは確実に同じドメインでしょ」というようなものが出てきたら、その時に初めて共通化を検討する。
オブジェクト指向言語で、自分のインスタンス変数に値をセット/更新するメソッドを許せるか
<?php class SearchClient { public function execute() { setupParameters(); $this->resultSet = /* どこから出てきたのかわからない値を使って処理をする */ } private function setupParameters() { $this->params = /* なんかやばい処理 */; } }
みたいなコードを許せるか?という話である。 例示したコードは前処理的な役目が強調されているが、それに限らずいろんなメソッドからいろんなプロパティがゴリゴリ更新されるパターン全般を指す。
最初は「必要なときもあるはずだからそのユースケースを考えよう」という記事にする予定だったが、書いてるうちに許せない気持ちになってきたのでそういう記事にする。 以下では、自分で思いついたこういう処理が必要なユースケースを述べ、さらにそれに対する反論を述べる形式で進める。
初期化時点では必要ないけど特定の処理を開始する時には必要な処理がある
それは責務が複数混じったタタリ神なので、これまで君臨してくれたことに感謝をしつつタタリの元を切り離そう。
初期化すべきプロパティがたくさんあるから仕方ない
そんなに抱え込んで、タタリ神でないといえるのか? いえるしても任意のタイミングで呼べてしまう関数で初期化するのはタタリ神の元だから全部コンストラクタでやって、その後プロパティは変更しない方が良い。
そうはいっても状態を管理しないといけないんだから仕方ない
うーん。 毎回新しいインスタンス作れよ、と漠然とは思うけど、それでワークする具体的なケースがイメージできない...。 他のプロダクトはどうしてるんだろう。 状態管理のプラクティスとかあるのかな。
あったわ。
なるほど、デザパタね。不勉強がバレたけど、子要素のインスタンスを毎回生成して、そいつは決められたルールでOutputを返すから状態管理が楽になるよ、ってことか。