
その設計判断、なんで選んだか3秒で言える?🤔
これ絶対あるじゃん??
- 「なんでここキャッシュ使ってるんだっけ?」って自分のコードを30分読んだ
- 「データの正しさより落ちないこと優先で〜」って口で言えるのに、コードにそれが1行も書いてない
- PRレビューで「なんでRedis使ったんですか?」って聞かれて「まあ速いので」しか言えなかった
- 新しいメンバーが「前の設計の意図ってなんですか?」って聞いてきて、チーム全員が無言になった
これがUndocumented Design Decision(暗黙の設計判断)、以降「口伝コード」って呼ぶね、が原因なんだよね。
てかシンプルに言うとね
設計判断を言葉にしてない = 「なぜ選んだか」が消えた状態でコードが動き続けてるっつーこと。
「それの何がまずいの?ってなる気持ち、わかる!」動いてるし、テストも通るし、デプロイもできてる。でもさ、たとえば「データの正しさ」と「落ちないこと」のどっちを取るかを決めたのに——それを誰も知らないまま時間が経ったら?
次に触った人が、同じ悩みをゼロから考えることになる。 あるいは気づかずにそのさじ加減をぶっ壊す。
ちょっと待って、先に全然関係ない話していい?🏠
都内のタクシー会社「丸幸交通」に、鈴木さんっていうベテランドライバーがいたんだけどさ。20年やってて、特にラッシュ時の都心ルートが得意。「渋滞を避けて早く着く」って評判が社内にあったの。
問題は、鈴木さんが「なぜその道を選ぶか」を一度も言葉にしたことがなかったこと。ここがヤバいんだけど。
「なんとなく体で覚えてる」「経験だから説明できない」
それでも鈴木さんがいる間は何も起きなかったんだよね。
で、鈴木さんが辞めた月。ここから全部崩れるの。
後輩の宮本さんが初めてラッシュ時の都心ルートを任された。
「宮本さん、今日はどの道を?」と同乗した先輩の田村さんが聞いた。
「環状線が速そうかなって思って」
「鈴木さんは絶対その道行かなかったんだけどな」
見事に渋滞にはまった。お客さんに謝りながら、乗車時間が40分増えた。
「鈴木さんならどうしてたんだろう。なんでダメだったんだろう」
翌日も、翌々日も、同じことが起きた。新しく入った4人のドライバーが全員、同じポイントで同じ判断を間違えた。
会社のアプリ評価は1ヶ月で4.3から3.7に落ちたんだよね。顧客からのクレームは週30件を超えた。橋本部長は「なんで鈴木さんがいたときは問題なかったんだ」と毎朝会議で言い続けたらしい。えぐくない?
「鈴木さんの判断は鈴木さんの頭の中にしかなかった。ルートを選んだ理由は、鈴木さんと一緒に退職した」
これが本番で起きてるやつが、「設計判断を言葉にしないまま作っちゃう」ってこと。
じゃあコードで見てみよっか👀
鈴木さんポジションのエンジニアが書いたコード、よくあるやつ。
import json
import redis
CACHE_TTL = 60 # なんとなく60秒
class OrderRepository:
def __init__(self):
self.cache = redis.Redis()
def get_order(self, order_id: str) -> dict:
# キャッシュから取得を試みる
cached = self.cache.get(f"order:{order_id}")
if cached:
return json.loads(cached)
# なんとなくレプリカを使う
order = self.read_replica.query(
"SELECT * FROM orders WHERE id = %s", order_id
)
self.cache.setex(f"order:{order_id}", CACHE_TTL, json.dumps(order))
return order
def update_order(self, order_id: str, status: str):
# プライマリDBを更新
self.primary_db.execute(
"UPDATE orders SET status = %s WHERE id = %s",
(status, order_id)
)
# キャッシュのinvalidateは……なし
この設計に込められた判断:
- なぜTTL 60秒?→ 誰も知らない
- なぜレプリカから読む?→ 誰も知らない
- なぜ更新時にキャッシュをinvalidateしない?→ 誰も知らない
半年後、新しいメンバーが update_order の直後に get_order を呼ぶ処理を追加した。古いステータスが返り続けた。本番で注文データの不整合が発生した。深夜2時にSlack通知が来た。
原因を追うのに2日かかったのは、「なぜこの設計にしたか」がコードのどこにも書いてなかったから。わたしも昔これやったから言えるんだけど、「コードは正しいのに結果がおかしい」状態の調査は本当に時間が溶ける。
やばいのまだあんだけど、バグに気づけない問題😭
「壊れやすい」だけじゃなくて、**「壊れてても何が正しいか分からない」**状態になるのが本当にえぐい。
鈴木さんが「なんで環状線を避けたか」を言葉にしてなかったせいで、宮本さんは「環状線がダメなのか、時間帯がダメなのか、曜日がダメなのか」すら判断できなかった。
コードも同じ。
def test_get_order_after_update():
repo = OrderRepository()
repo.update_order("order-123", "shipped")
result = repo.get_order("order-123")
# "shipped" を返すべき?
# それとも、キャッシュから古いデータを返すのが「正しい」動作?
# → 設計判断が言語化されていないので、テストの期待値が書けない
assert result["status"] == ??? # 正解が存在しない
「落ちないことを優先するためにキャッシュを使い、データの正しさは60秒以内しか保証しない」って判断がはっきり書いてあれば、assert result["status"] == "processing"(古いデータが正しい)と書ける。
でも言葉にしてないと、バグかどうかすら判断できない。設計判断を言葉にしないのは、壊れやすいだけじゃなく、バグを隠す設計でもある。
ちゃんと書き残してたチームの話もするね〜✨
鈴木さんの後輩が就職した別の会社、林さんのチームの話。
林さんの会社には「ルート選択ログ」があった。ドライバーが「今日は環状線を避けた。理由:水曜18時台は渋滞確率90%。代替:首都高経由、料金+200円だが20分短縮」って30秒でアプリに入力する仕組み。
林さんが退職しても、後輩が困ることはなかった。ログを読めば「なぜ」が全部わかるから。クレームゼロ。定時退社。橋本部長みたいな人も現れなかった。
コードでも同じことをやる。
# ─────────────────────────────────────────
# 設計判断メモ(2024-03-01 山田)
#
# トレードオフ:一貫性(Consistency) < 可用性(Availability) を選択
#
# 理由:
# - 注文ステータスの1分程度のラグはビジネス上許容されている(PM確認済)
# - ピーク時のDB負荷軽減が最優先(SLA: 99.9% uptime)
#
# 代償(必ず守ること):
# - 更新系APIは必ずキャッシュをinvalidateする責任を持つ
# - TTLを5分以上にしない(整合性SLAが崩れる)
#
# 再考タイミング:決済処理が同一システムに入ったとき(一貫性優先に切替が必要)
# ─────────────────────────────────────────
CACHE_TTL = 60 # 1分(上記メモ参照)
class OrderRepository:
def get_order(self, order_id: str) -> dict:
cached = self.cache.get(f"order:{order_id}")
if cached:
return json.loads(cached)
# 設計判断メモより:可用性優先のためレプリカから読み取り
order = self.read_replica.query(
"SELECT * FROM orders WHERE id = %s", order_id
)
self.cache.setex(f"order:{order_id}", CACHE_TTL, json.dumps(order))
return order
def update_order(self, order_id: str, status: str):
self.primary_db.execute(
"UPDATE orders SET status = %s WHERE id = %s",
(status, order_id)
)
# 設計判断メモの「代償」に従いinvalidate(省略厳禁)
self.cache.delete(f"order:{order_id}")
新しいメンバーが来ても「なぜこうなっているか」がコードと一緒にある。「いつ見直すか」まで書いてあるから、仕様変更が来たときに判断できる。深夜Slack通知なし。
ちゃんとした名前もあるから一応言うね📚
Architecture Decision Record(以降ADRって呼ぶね)とは、設計で何を選んで、何を捨てたかを残しておくっつーこと。
広めたのはMichael Nygardって人で、2011年のこと。
設計って常に「何かを選んで何かを捨てる」の連続なんだよね。ADRはその「何を選んで何を捨てたか」を書き残すやり方。今はGitHubに docs/adr/ ってフォルダ作って残すのが定番ね。
難しそうに聞こえるけど、要は「なぜこっちを選んで、何をあきらめて、いつ考え直すか」って聞き続けるだけなんだよね。
これだけ覚えて帰って👌
合言葉はこれ。nkyo(なんでか書けよ)
コードを書くとき・レビューするとき、この問いを使って。
「この設計判断が変わるとしたら、どんな出来事が起きたとき?」
たとえばさっきの「TTL 60秒・落ちないこと優先」の判断。
- 出来事①:「決済機能が同じシステムに入った」→ 整合性の厳密な保証が必要になる。キャッシュ戦略を根本から変える
- 出来事②:「DAUが10倍になって、DBへの読み取りが限界に」→ TTLを伸ばして「落ちないこと」をさらに優先する
- 出来事③:「SLAに『ステータスのリアルタイム反映』が明記された」→ キャッシュをやめる選択肢が浮上する
出来事が3種類ある = この判断は「3人の鈴木さん分の理由」で変わりうるってこと。
それだけの重みがある判断を、なぜ選んだか誰も知らない状態にしてたら、変えるタイミングすら分からなくなる。
まとめるとこういうことね
| 観点 | 言語化なし(アンチパターン) | 言語化あり(正しい設計) |
|---|---|---|
| 設計意図の継承 | 担当者が変わると意図が消える | コメント・ADRに残り続ける |
| テストの書きやすさ | 期待値の「正解」が定義できない | トレードオフが前提なので期待値が書ける |
| 機能拡張時 | 無意識にトレードオフを破壊する | 判断の前提を踏まえて拡張できる |
| 障害調査 | 設計意図の追跡に数日かかる | 数分で「なぜこうしたか」が分かる |
| 設計変更のタイミング | 「変えていいか」の判断基準がない | どんな出来事が起きたら変えるかが明示されている |
コードに5行追加するだけで、半年後の自分とチームを救えるっつーこと。鈴木さんみたいに知識を頭の中だけに持ったまま去らないで🫶
沼りたい人はこっちも読んで📖
権威ある書籍
- Michael Nygard『Release It! Design and Deploy Production-Ready Software』(2018年 第2版、Pragmatic Bookshelf)— サーキットブレーカー・バルクヘッドなど本番システムの安定性パターンを扱った定番書
- Neal Ford, Rebecca Parsons, Patrick Kua『Building Evolutionary Architectures』(2017年、O'Reilly)— 変化に強いアーキテクチャ設計とFitness Functionを提唱した書籍。設計上のトレードオフを継続的に評価・検証する考え方を扱っている
- Eric Brewer「Towards Robust Distributed Systems」(2000年、PODC基調講演)— 一貫性・可用性・分断耐性のトレードオフを最初に提唱した原著
一次資料
- Michael Nygard「Documenting Architecture Decisions」原文 https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions
- ADR GitHub Organization(テンプレート集) https://adr.github.io/
無料で読める入門記事
- ThoughtWorks Technology Radar「Lightweight Architecture Decision Records」 https://www.thoughtworks.com/radar/techniques/lightweight-architecture-decision-records
- Joel Parker Henderson「Architecture Decision Record templates」 https://github.com/joelparkerhenderson/architecture-decision-record
Dev-Here「ギャルでも分かる設計の勘ドコロ!」シリーズ 第5回