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を返すから状態管理が楽になるよ、ってことか。
『現場で役立つシステム設計の原則』の画面とドメインオブジェクトを一致させる章を読んだが異文化に衝撃を受けた
異文化すぎて理解に苦しむ箇所もいくつかあったが、もちろん面白い箇所もあった。
タスクベースの画面いいよね
たしかに。しかも後の方で「とはいえなんでもできる画面の需要が尽きないのもわかるから、そういう時はドメインオブジェクトは分けつつもそれらを協調させるドメインオブジェクトを一つ切るといいよ」と書いてあって、さらに納得感が増した。 使えるようになるために学習が必要だけど使えるようになると1画面でなんでもできる、ってのはたしかに(特に社内ツールとかで)尽きない需要だと思う。
どこまでは「論理ビュー」で、どこからが「物理ビュー」なのか?
ここが自分ではよくわからなかった。読書会を一緒にやった同僚たちも首をかしげていた。 論理ビューとはプログラミング言語で出力を担保するビュー情報のこと、物理ビューとはテンプレートエンジンで出力を担保するビュー情報のことのようだ。
筆者いわく
- 段落の論理構造は
String[]
と表現できる論理ビュー - 「
<p>
タグとか、段落の先頭を全角一文字で字下げするといった物理的な手段」は物理ビュー - 場合によって出し分ける文言は論理ビュー(0件なら「見つかりませんでした」、1件なら「1件みつかりました」など)
- HTMLのclass属性は論理ビュー
あ、class属性はアプリケーションコードで持って良いんだ、と驚いた。 それまでの3つはたとえばHTMLでもJSONでも必要な情報だし、むしろどちらでも使える情報だからビューよりのドメインオブジェクトに持っておくのは筋が良いと思う。 そもそもビュー専用のオブジェクトが存在するのはよくあるパターンだし、全然理解できる。
でもclassはHTMLでしか必要ない。
その後で「画面を表示するロジックにif文が入り込み始めたら、要注意です」とあるが、たとえばclass属性の出し分け条件をドメインオブジェクトに寄せたとして、特定の条件の時にDOM自体を出したり出さなかったりする時はどうするんだろう。空の内容を描画しちゃって display: none;
はあまり筋が良いと思えない。
自分でこの本の内容を実践する時、どうするかなぁ。 ビューから全くif文がないこと、はコード品質の合格条件には入れられない気がする。
Clojureで書けば全部Clojureだからビューだろうがなんだろうが簡単に単体テストを書けるんだけど、それとこれとはちょっと話のレイヤーが違うからそれで解決したことにしちゃうのは反則技っぽいし、もうちょっとぼんやり考えよう。
その後
上の説明の後は、コードも段落分けすることだとか、プロパティをグルーピングすることなどが載っていた。そういう意味でのキレイさの話。
今から購入を伴うメディアサイトを作るとしたら、どんなアーキテクチャを採用するか
> **NOTE** 思考実験なので想像に過ぎないが、より良いアイデアをお持ちの方は、ぜひご教授ください。
想像してみる。
今はだれもがスマホを持っていて、SNSを読み、バズった記事にアクセスが殺到する。
死にゆくサービスも多くある一方で、生き残ったサービスはその場しのぎに書いた負債の利子の返済に苦しめられる。
RDBのスケールアップの限界と戦う。
これからもスマホをはじめとしたデバイスからの通信量は増え続けるだろう。
そんな今の時代、購入を伴うメディアサイトを立てるとしたら、どんな構成を採用すべきか?
> 購入を伴うとは、ここでは求人への応募や賃貸の内見応募、ECでの商品購入、定期購読料の契約、飲食店の予約などの、サイトの収益の源となる行動のこととする。
> つまりいわゆるニュースサイトのような広告収益モデルではないメディアサイトの話。
## やりたきこと
スパイクアクセスに耐えられること。
言語の流行り廃りに耐えられること。
開発速度を保ち続けられること。
## Wordpressはどうだろう?
この記事を書きはじめてすぐは、自前で全部書くならどんな構成にするか?としか考えられていなかったが、そういえばWordpressというものがあったなと思いついたので考えてみる。
ここではWordpressを取り扱うが、実際にはメディアサイトの雛形となるいろいろなパッケージ全般が考慮対象だろう。
### スパイクアクセスに耐えられるか?
ググったらあっさり出てきた。
https://engineer.blog.lancers.jp/2017/03/awsでwordpressのスケールアウト/
AWS上で動かすためにいろいろ引っぺがしたら良いらしい。なるほどー。
http://xn--o9j0bk9rwb2ftig7dzeb2881pquxd.com/rentalserver/post-512/
こんなのもあるらしい。これだけベンダー依存するのはちょっと心配な気はする。
### 言語の流行り廃りに耐えられるか?
PHPくらい枯れた言語とWordpressみたいな古株パッケージなら、バージョンアップをサボらなければしばらくはなんとかなりそうに思える。
機能が増え過ぎて、もうこれWordpressっていうより半分以上自前のPHPサイトだよ、ってなった時、それでもバージョンアップに耐えられるコードを書く必要はありそうだが、それはどの言語でも必要なコストだ。
あるとすれば、Wordpressとそれ以外のコードを疎結合にして別々にバージョンアップできるようにしておいた方がよい気がする。
たとえばコンバージョンにからむ部分はWordpressとは別のサブドメインやURLに切り出しておけば良さそう。
あるいはログイン機構は別に持って、他のサービスが認証基盤にアクセスしに行く、とか。
### 開発速度を保ち続けられるか?
正直まだPHPを書いた経験が少ないのでなんとも言えない。
…が、PHPはテンプレートエンジンとして作られたのだし、今は型システムやパッケージマネージャーもあって立派な言語になったけど、あんまり巨大なシステムを書くのにマッチする言語ではないような気はする。
が、単なる経験不足と好みを孕んでる確信があるので、何で書くかはあまり大事じゃないんだろう。
探せばJavaやRubyのメディアサイトのパッケージだってあるだろうし。
## 自前
こっちは最初に、自分ならどうするだろうかと考えた方の選択肢だ。
とはいえ上で考えたことでだいたい話は終わってしまう。
メディア部分も自分で書いて、言語が廃れても逃げ出せるように小さいサブシステムに保つ。
必要ならRDB以外も検討する。
### 管理画面をどうするか
ここまで考えて思いついたのだが、Wordpressにはセットで付いていた管理画面はどうしようか。
別に我々は必ずセットにする必要などないので、別システムに持っておいても問題ないが…
ここでDBを共有してしまってはサブシステムを小さく保ちたいのに変に結合してしまう。
ここは、メディアサイト本体にAPIを用意して、管理者の認証認可とAPIをキックするだけの管理サイトを用意するのはどうだろう。
それなら、外に公開するサイトには特定のURLを404で返すように設定しておいて、内部向けに用意した1つのインスタンスだけはすべてのURLを公開するようにしてサブシステム間の結合を疎に保てそうだ。
こうしておけばDBを1つのサブシステムで占有できるし、サブシステムを小さく保てるから保守も書き換えもやりやすそうだ。
関心ごとが違うサブシステムを適切に疎結合にするならば、ログインが必要ならそのまわりも外に出せるし、お金を扱うのも外に出せる。
ただし各サービスを別々に互いのエラーに寛容に作らねばならないのは、開発スピードをある程度落としそうだ。
未来に抱え込む負債を減らせるなら、そのコストは充分ペイすると思うが…どうなんだろう。
結局は想像だ。
レガシーコードをなんとかして読み解く方法
レガシーコードをリプレイスするタスクで、レガシーコードが全然読めなくて時間を浪費しきってもまだ何もわからなくて詰んだ。 先輩エンジニアに相談したら即席コードリーディング会を開く声掛けをしてもらえて、どうレガシーコードを追うのか見ることができた。
今日はそのときの学びを書いておく。
タスク内容
n年モノのメディア系PHPコードのごく一部のビュー(リンク集的なパーツ)の描画ロジックを別システムにリプレイスする。
学び
全部を追わない
何より全部を追うとツラすぎるし、得られる完璧な仕様もコストに見合わない。
外部仕様をざっくり理解する
今回はHTMLがアウトプットになるので、どんなHTMLがどのコードから出てくるのか、その末端から理解すると良いということがわかった。 リンクに必要なURLやタイトルがどういうオブジェクトから生成されているのかをまず把握する。 そして次にそのオブジェクトがどの別のオブジェクトから生成されているのかを把握していく。でも深入りはしない。
行間を読む
正直、今となっては命名が微妙だったり不要な引数を受け取っていたり、必ずある値しか来ないのにif文が書かれていたりすることは全然ありえる。 そうなった時に今までの歴史を知っている人に聞くことはもちろん大事だけど、それとは別に「当時やりたかったことを察してあげる」ことも大事だと学んだ。
問題の解決を意図しながらも、しばしば他の問題を生じさせてしまうような技法
と書かれている。たとえあるコードがアンチパターンを踏んでいたとしても、特定の状況を解決したといえるはず。 それをよりスマートな方法で実現して、今お金を稼いでくれているコードへの弔いにしよう。
関数の引数を追わない
これをやり始めると無限に違う箇所を読み続けて、自分が何者だったのかも忘れてしまう。 僕は今回これを何度もやらかして時間も精神力も浪費した。
先輩エンジニア氏は、外部仕様をざっくり把握するにあたって関数の引数が現れたら、その時に呼ばれている関数だけを調べてなんとなくそのオブジェクトの役割を理解すると言っていた。 たしかに、一応オブジェクト指向で書かれているんだからあるオブジェクトのすべての振る舞いではなくて必要な振る舞いだけを調べればいいはずだよな。 それに、リプレイス対象の関数が(当時必要だったかもしれないけど)今や不必要な大きなオブジェクト(HttpRequestみたいなやつ)を引数に受け取ってしまっている可能性は全然想像できる。そうだった時に、引数を先に追うと必要な処理に必要ない情報ばかり掴まされることになる。
ざっくり分かったらとりあえず書き始める
なんとなくどんな条件の時にどんなビューが返ればいいのか分かってきたら、もう書き始める。 これでちょっとずつキレイなコードを吐いて、脳内に覚えておかなければいけない仕様を減らす。で、またオカワリする。
道は遠いが、僕はまだレガシーコードと出会ってそう時間が経っていない。 単にレガシーコードを書くスキルレベルが1なのだ。
ありがたみと滅せよという気持ちを内在させながら、その念を飼い慣らしていこう。