私が関わった設計の失敗と反省のあれこれ小話
最近自分がたびたび担当している領域に、1つ新しい機能を入れることになりました。
そこで設計担当として全般的に携わったのですが、まあお世辞にも及第点の進行とはならずお粗末な感じでした。ただ、学びも多大だったので反省もかねて振り返ってみたいと思います。未熟者なので、読んでイライラしないで、「ワイにもこういう時代あったなあ」と懐かしんでいただけると助かります。
表題の通り小話です。
読み物として書いているためなるべく分かりやすくしているつもりですが、領域や機能については抽象化してあるため、少々想像しにくい場合があるかもしれません。ご了承ください。
前提のお話
この領域は私にとっては初めてのことだらけでした。領域の特性としてもそうですが、機能としてのコード1つ1つとしてもなんというか、何年も運用されてユーザーのに価値を生み出しながらも、その時その時出会った開発者たちにつぎはぎに改修にされてきたコードといいますか。
ちなみにこれは嫌味ではなくものすごく自分の中ではすごいなあと思っていて。一度作られたまま永遠に死んでいくコードが大変多い世の中で、長い時間ユーザーのブラウザを通ってきたコードとして、紛れもなく生き続けているって尊いですよね(また、先人のコントリビューターには敬意を払うべきですから)。
また、この領域はビジネスに少なからず大きなインパクトがあるので、必要性に駆られて突発的な改修が積み重ねられがちでした。
そんなこんなで平素から、この領域に手を入れる時はとても大変だなと思っているのでした。
ついでにいうと、私はまだつい先日試用期間を終えたド新人です。そんな感じですから、複雑なコードに手をいれるのは震えるのでした。
ついでに背景を追加すると、新しい機能はチームの重要な役割であり、早めに出して検証することが求められていました。
いざ設計開始
その領域(詳細は控えますので、今後は便宜上ドメインAと呼びます)に、まあまあ大きな新機能(今後は新機能と呼びます)を入れることになったということで、いざ設計をスタート。
新機能は外部サービスとの連携を伴うものでした。どんな設計でもそうですが、外部サービスとの連携ロジックが1番考えることが多いです。やっぱり新機能の追加は、何かと厄介そうですね👿

元々PdMやデザイナーがユーザー体験を精緻に構築してくださっており、ほぼ完成されたデザインイメージをもらいつつ、外部サービスのドキュメントを閲覧しつつ設計を始めました。
まず外部サービスとの連携がややこしいコードを生むのは間違いです。そこで連携する場所を1つにまとめて、ロジック自体を固める方針にしました。ドメインAは2〜3のドメインから呼び出され、やや密に結合してしまっていたのですが、幸いその中の1つのドメインに、外部サービス連携の役割を寄せる形でうまくいきそうです。実際に想定するユーザー体験もそのようになっていました。
2〜3のドメインの文脈と密結合していたというのも大きなポイントで、ドメインAはこの密結合により「ドメインBから呼び出された時にはこの処理を、Cから呼び出された時にはあの処理を、Dからの時には……以下略」という感じで、少し他ドメインから染み出した知識を持たなければならない感じでした。つまりドメインB,C,Dのどこからでも新機能を使えるようにするとなると、また微量なカスタマイズが必要になったり、テスト工数が膨れたりするということですね。
連携の機能を思い切って1つのドメイン(図では仮にB)とします。に寄せたのはそういった事情もありました。

寄せることにした場所ドメインBは、ドメインAの中でもまだ最近できた雰囲気のところで、幸いにもスパゲッティ🍝レベルが当社比低めになっています。これも私を安心させてくれました。
このDesignDocは、ほぼ手間取りなしでなしでリードレビューを通すことが叶いました。とても順調でした。今思えば、まさに嵐の前の静かさです。
チームレビュー
事件はチーム内の開発前レビュー時に起きました。
「この外部連携ってドメインCやドメインDでもできないと、全体的に厳しくないですか?」
2〜3のドメインの文脈と密結合していたというのも大きなポイントで、ドメインAはこの密結合により「ドメインBから呼び出された時にはこの処理を、Cから呼び出された時にはあの処理を、Dからの時には……以下略」という感じで、少し他ドメインから染み出した知識を持たなければならない感じでした。
私は上記の大変さにげんなりして機能を1箇所に寄せたのですが、この前提がガラガラと音を立てて崩れた瞬間でした。つまり、寄せたドメイン以外でも他ドメインから呼び出される際にも、外部と連携するロジックが必要そうという話になったのです。
お気づきでしょうか?
寄せることにした場所ドメインBは、ドメインAの中でもまだ最近できた雰囲気のところで、幸いにもスパゲッティ🍝レベルが当社比低めになっています。これも私を安心させてくれました。
いきなりいやな予感がしました。そこそこ複雑な外部と通信するロジックを、複雑なドメインAのコードに、しかもB,C,Dのすべての呼び出しに対応できるように複数埋め込もうという話になっているのです。
すごくこうなりそうな予感です。

それでは聞いてください。
「このCとDの場所からの連携は、いわゆるハッピーパスではなさそうなので、初期スコープとしては落としたいです」
で、出ましたー!複雑なことをしたくないので、MVPとかいう作り方にかこつけて仕様を削りたいマンです。冒頭の通り、この機能は早めに出したいですからね。と自分を正当化してみます。しかしPdMおよびチーム内開発リードは、一理くらいはありそうなこの考えを、一刀両断しました。
「ドメインCとドメインDからも同じように呼び出しができないと、この新機能を使えなくなるユーザーがいる可能性が高い。この場合機能不良かなと」
んんんんんん!まだ連携さえどれくらいのユーザーがしてくれるのかはわからない上にドメインCとドメインDから呼び出される可能性は限りなく低い。私はどちらかというと一旦シンプルに、早く出したい!
この議論は開発現場としては良くある、シンプル仕様のMVPと機能不良の境目の定義の問題ですね。
私はなお、食い下がります。
「やってから考えるのはどうでしょう?繰り返しますが、ハッピーパスではないですし……」
「いや許容できない」
この応酬がかれこれ3回くらいある間に、私も冷静になり始めました。そもそも他ドメインからのん連携という仕様を落とすことが今回は難しいと納得した理由は以下の2つです。
- 担当しているビジネスレベルにおいて、新機能の利用率はたとえ1ptの上下でも売上的に規模の大きいインパクトになりえること
- 外部サービス連携は新機能のコア部分なので、初期に拡張性を考えないことは、今後の改修にマイナス影響になる可能性が高いこと(後回しにするとツケが回りかねない)
シンプルMVPと機能不良の境目の定義はなかなか難しいところではありますが、背景を考えたときにこの仕様は落とせないと判断しました。
余談ですが、この時私は自分の領域のスパゲッティコードがかなりツライと感じており、「開発者観点としてやりたくないこと」についてをかなり頭を悩ませていました。でもこれは、あまり本質的にじゃなかったです。ステークホルダーとともに推進することになりがちなPdM的としては「コードがまずいから仕様落としマース」って基本的には説明がつきません。後から思いましたが、自分の職能の領域に責任を持つことは大切ですが、一緒に働く他の職能のメンバーの事情を考えながら進めるのは、非常に大切な観点なのだと痛切に感じました(チームのPdMとやりとりをしている時にふと気づかされました)。
話は戻りますが、新機能はどのドメインから呼び出されても無理なく外部サービスを利用できる仕組みを入れるとなると、設計はこのドメインAに新たな負債を生み出す感じました。直感的に、このまま進むのはかなりまずいと思いました。
そこでもう一度考えさせて欲しいとお願いをし、一度設計を見直すことに決めたのでした。
設計の見直し
見直しはリードに協力を取りつけて壁打ちするところから始めました。まず自分が開発者の本能として感じている危機感が現実的なものなのか、そこのすり合わせをする必要がありました。また、正直にいうとこの会社においてド新人の私は、まだ信頼指数を勝ち取れているとは言い切れず、権威性の味方を求めてもいました(これが社会ですね!)。
リードとの1時間以上もの壁打ちの末に、段々とわかってきたことがあります。仕様の追加は、今のままではコードの複雑性以外にも、既存仕様の秩序を乱すような危険な側面があるということでした。
つまりMVPと機能不良というよりは、既存の仕様にハマらない仕様というか、うまくいえないんですが。先述、本ドメインAは複数の領域と密結合と話しましたが、そのうち1つの領域と、あまりにもうまいこと考えにくかったのです。いいようによっては、そもそもドメインBからのみ呼び出されることが多いという当初からのハッピーパスが、機能の歪みのように思えました。
仕様が複雑になって、コードが複雑になる未来が、実は根本の仕様やユーザー体験にも起因している可能性に気づいた瞬間でした。
元々PdMやデザイナーがユーザー体験を精緻に構築してくださっており、デザインイメージをもらいつつ、外部サービスのドキュメントを閲覧しながら設計を始めました。
私は最初から、目に見えているデザインの仕様に合わせて設計をしていたことを反省しました。これはデザイナーが悪い話ではなく、エンジニアの責任範囲としてデザインというHowから仕様を創出していくというのは、完全なアンチパターンであるということです。
そこで、ドメインAと新機能およびその外部連携とをどう関わらせていくべきか、改めて再考しました。
新機能の性質を考えた結果、ドメインAの中で外部サービスとの連携ロジックを加味するのではなく、ドメインAと新機能は切り離して疎結合にし、なんとか新機能は「どのドメインから呼び出されているか」を考えない設計にできないかを練ることが最も筋が良さそうと判断しました。新機能を1つのドメインに昇格させるイメージです。

ほとんどのデザインはそのままとはいえ、一部ユーザー体験が変わる印象になります。この変更をなめらかな体験にするためには、デザインの手戻りを発生させる必要もありそうでした。そこで、次にPdMとデザイナーに話を通しにいきました。変更の提案について、以下を根拠としました。
- 現行の仕様はドメインAの仕様に歪みを生じさせ、その不整合のために一層仕様が複雑になること
- ドメインAの複雑性を肥大化させないために新機能との結合は最小限にとどめること(ドメインAが危険帯域であることも説明)
このうち2はバグリスクを下げるという開発的な観点が強いですが、1はユーザー体験に寄与するものです。最初に仕様を考えた時や、チームレビュー時は、いかにコードを健康に保つかに気を取られていましたが、この頃になってやっと、異なる職能や観点を持っているメンバーへどう伝えるかを考えるようになりました。
この新しい仕様と設計の提案は少なからずデザインの手戻りとユーザー体験の変化をもたらしました。一方で仕様の歪みを消し、ドメインAへの手入れを最小限にとどめることで不具合リスクを減らし、テストパターンを大幅に減らし、元々想定していた機能不良の可能性を無理なく消失させてくれる解決策でした。
PdMとデザイナーは快く提案を受け入れた上で、双方から「ではここの外部サービス連携導線はなめらかにしたいので、こうできないか?」という、ユーザー体験をよりよくする提案をくれました。こうして、仕様の見直しと設計が完了したのでした。また、外部サービスの方々との打ち合わせの中で、「そういうことなら……」と、さらにロジックをシンプルする案もいただくことができ、するするとスリムになっていきました。
私はDesignDocを書き直しながら、ふと、また一つ重要な発見をしました。新機能の拡張性は格段に上がっており、仕様は明らかにシンプルになっており、ユーザーにとってわかりやすくなっていそうでした。
おわりに反省
いかがでしたか?
今回の設計は、無事に第一章(いや第二章、第三章か……?)の幕を閉じました。というのは、現在、絶賛実装中なのです。今は完璧な設計と仕様に見えていても、途中でどん詰まりするかもしれません。その場合は、平謝りから始まるエキサイティングな第????章の幕開けになることでしょう。
改めて、設計というのは奥深いです。
セオリーとしての技術的・汎用的な設計パターンに加えて、設計を入れ込む領域や周辺機能の現状、つまりプロダクトを適切に見通さなければなりません。
これまでの歩みを振り返って、今回の私の中の反省点は主に2つありました。
- デザイン等の既に具体化されたものに囚われ、大枠から俯瞰して考えられなかったこと
- MVPと仕様不良の境目の見極め(救うべきところ、捨てるところ)と落としどころの考えが甘いこと
また、1にと2にも関わることなのですが、総じてどんな状況でも仕様がシンプルというのは正義だと実感しました。むしろどうしてもシンプルにならない場合は、何か重要な観点が不足している可能性があるのではないかと。
大反省会と称しての振り返りでした。多大な見落としにより多くの手戻りがあったせいで振り回してしまったチームの方々、途方に暮れていた時に話を聞いてくれた方々などなど、現実でも90度お辞儀の特大感謝ではありましたが、この広いインターネッツからも深くお礼申し上げるとともに、そろそろ結びにしようと思います!
大長編小話?となりましたが、最後までありがとうございました。
Discussion