mybook

プロローグ — なぜそうなっているのか誰も知らない

入社初日の洗礼

シンジは画面に映るコードを眺めながら、静かに頭を抱えた。

転職して初めての週。スタートアップから上場企業へ。今度は「テックリード」という肩書きを持って乗り込んできたのに、目の前には理解不能な現実が広がっていた。

# app/models/order.rb
class Order < ApplicationRecord
  # TODO: このロジック絶対に触るな! 2021年8月
  def calculate_final_price
    base = line_items.sum(&:subtotal)
 
    if user.plan_type == "legacy_v1"
      base * 0.88  # なぜ0.88なのか誰も知らない
    elsif user.plan_type == "enterprise_old"
      base - (base > 100000 ? 15000 : 8000)  # 謎の閾値
    elsif created_at < Date.new(2020, 4, 1)
      base * 0.92 + 500  # 消費税改定前の処理?
    else
      apply_standard_discount(base)
    end
  end
end

「このTODOはなんですか?」とシンジはSlackに書いた。

返ってきたのは30分後。元CTOだったという人からのメッセージだった。

「もう退職したので詳細は分かりません。当時の担当者もいないです。とにかく触らないでください。」

触るな、とだけ言われても……

シンジは深呼吸して、もう少し掘り下げてみた。Gitのログを遡る。コミットは3年前。コミットメッセージは「fix: 料金計算ロジック更新」。それだけだった。なぜ0.88なのか。なぜ100,000という閾値なのか。なぜ2020年4月1日という日付がハードコードされているのか。どこにも書いていない。

シンジはPRの一覧を漁った。当時のPRコメントを探したが、そのプロジェクト管理ツールはすでに別のサービスに移行しており、古いデータは消えていた。

チームミーティングの混乱

翌週のアーキテクチャ会議でも同じことが繰り返された。

新機能として「サブスクリプション型の料金プラン」を追加したいというプロダクトの要望がある。バックエンドチームのリーダー、マイはこう言った。

「Sidekiqで非同期処理すれば?」

インフラ担当のリョウは首を振る。「でも今ってResque使ってるよね。なんでSidekiqに変えるの?」

「Resqueは遅い。Sidekiqのほうがスレッドモデルだから速い」

「でもなんでResqueにしたんだっけ?」

誰も答えられなかった。

Loading diagram...

シンジはホワイトボードの前に立ち、チームに聞いた。

「みんな、このResqueの導入はいつ、なぜ決まったか覚えてる?」

エンジニアのコウスケが恐る恐る答えた。「3年前……くらいじゃないですか。当時のCTOが決めたって聞いてます。理由は……知らないです」

「AWSのSQSじゃなくてResqueにした理由も?」

「それも……」

会議室は沈黙した。30分間の議論の末、誰も判断できないまま「持ち帰り」になった。毎週この状況が繰り返されていた。

「何度も同じ議論をしている気がする」とリョウがつぶやいた。「先月もSidekiq対Resqueの話が出たよね」

「3ヶ月前にも」とマイ。

そうだ。これは一度だけの問題じゃない。毎回ゼロから同じ議論をしている。なぜかって、前回の判断の理由がどこにも残っていないから。

コードに埋まった謎

シンジはその週、コードベースの探索に費やした。何十というコミットログを読み、何百というissueを漁った。

そして見えてきたのは、技術的負債の正体だった。

それは「悪いコード」ではなかった。むしろコードの品質は悪くない。テストも書かれている。Rubocopの指摘もほぼゼロ。問題は別のところにあった。

# config/initializers/payment.rb
# Stripe決済を使っている理由:
# - 当初はPAY.JPを検討していた
# - しかしなぜStripeになったのか…コメントなし
# - PAY.JPのほうが国内手数料が安いケースもあるが、変えていい?
 
Rails.application.config.payment_provider = :stripe
 
# app/services/notification_service.rb
# メール通知にSendGridを使っている
# なぜSendGridか?Amazon SESではなく?他に検討したサービスは?
# 設定変更時の影響範囲は?月間送信数の上限は?
# → 誰も答えられない

技術的な判断が、なぜそう決まったのかという文脈ごと消えていた

コードだけが残り、判断の理由は誰の頭にも、どこのドキュメントにも存在しない。

WARNING

コードは「何をするか」を伝えるが、「なぜそうするか」は伝えない。設計判断の理由を失うことは、将来の変更コストを指数関数的に増やす。変更を恐れるチームは、技術的負債を溜め込むしかない。

コードベースの探索をしていて、シンジはもう一つの問題を発見した。データベースのインデックスだ。

# db/schema.rb
create_table :users do |t|
  t.string :email, null: false
  t.string :encrypted_password
  t.integer :plan_type
  t.string :external_id    # なぜexternal_idが必要?どのシステムと連携?
  t.boolean :is_legacy     # legacy というのは何のlegacy?
  t.timestamps
end
 
add_index :users, :email, unique: true
add_index :users, :external_id
add_index :users, [:plan_type, :is_legacy]  # なぜこの複合インデックスが必要?

external_id は何のためにある? is_legacy フラグが true のユーザーと false のユーザーでビジネスロジックがどう違うのか。なぜ plan_typeis_legacy の複合インデックスが必要なのか。

全部分からない。

引き継ぎという名の「伝言ゲーム」

3ヶ月後、シンジのチームに新しいメンバーが加わった。ユウキ、新卒2年目のエンジニアだ。

「このRailsのキャッシュ戦略ってどういう設計思想ですか?」とユウキが聞いた。

シンジは答えようとして、止まった。

……俺も知らない。

Redisをキャッシュに使っていることは分かる。でもなぜElastiCacheでなくEC2上の自前Redisなのか。なぜキャッシュの有効期限がこの値なのか。なぜキャッシュするデータとしないデータを分けたのか。

「調べてみます」とシンジは言うしかなかった。

# app/controllers/api/v1/products_controller.rb
def index
  @products = Rails.cache.fetch("products_list_v3", expires_in: 30.minutes) do
    Product.active.includes(:category, :images).limit(100)
  end
  render json: @products
end
 
# なぜ30分?
# なぜv3?v1やv2はどこに行った?
# なぜlimit(100)?
# includesで:imagesを読み込んでいるが、必要なの?

シンジはコードをGitブレームで追った。最初のキャッシュ実装は2年前。その後3回変更されている。でも変更理由は「fix」「update cache」「adjust」という曖昧なコミットメッセージしか残っていない。

Loading diagram...

最終的に、ユウキの質問への答えは「前任者に聞いてみます」になった。でも前任者はすでに退職していた。

繰り返されるパターン

その週末、シンジは問題を整理した。チームが直面している問題は一つではなく、同じパターンの繰り返しだった。

パターン1: 「なぜ」が分からない

技術的判断の「なぜ」が失われている。

  • なぜPostgreSQLではなくMySQLを使っているのか → 知らない
  • なぜS3ではなくGCSなのか → 知らない
  • なぜそのgemを使っているのか → 知らない
  • なぜそのAPIのレスポンス形式なのか → 知らない

答えを知る人間がチームにいない。いたとしても、記憶は曖昧になっている。

パターン2: 変更が怖い

理由が分からないから、変えたら何が壊れるか予測できない。

  • # TODO: 触るな! というコメントだけが残る
  • 結果として古い実装が温存され続ける
  • バグっぽく見えるコードも「何か理由があるに違いない」と思って触れない

これが技術的負債の本質だ。悪いコードではなく、理由が分からないコードが、変更不可能なコードになる。

パターン3: 同じ議論の繰り返し

毎回同じ技術を比較して、同じ結論に達する(あるいは達せずに時間切れになる)。

  • なぜ前回その判断をしたか覚えていないから
  • 毎回ゼロスタートで調査・議論が必要
  • 時間とエネルギーが無駄になる

シンジの計算では、チームは週に平均3時間を「過去の判断の確認」に費やしていた。年換算で150時間以上。エンジニア1人分の月稼働時間だ。

パターン4: 新人がキャッチアップできない

コードは読める。でも「なぜこのアーキテクチャか」が分からない。

  • 正しい判断の基準が共有されていない
  • 間違った変更が生まれやすい環境
  • 結果として「先輩に確認しないと変更できない」依存関係が続く

ユウキのような優秀な新メンバーが力を発揮できないのは、彼らのせいではない。情報の非対称性が問題だ。

パターン5: インシデントの教訓が消える

本番で障害が起きる。必死に対応して原因を特定する。でも根本原因が「なぜその設計にしたか」の理由不明だったりする。

そして3ヶ月後、同じ原因で同じ障害が起きる。

INFO

これらの問題は「エンジニアが悪い」のではなく、「設計判断を記録する仕組みがない」ことが原因だ。仕組みがなければ、どんなに優秀なチームでも同じ問題を繰り返す。

ADR以外の試み: なぜ失敗したか

シンジが入社する前、チームは問題を解決しようとしていなかったわけではない。いくつかの試みがあった。でもすべて失敗していた。

試み1: Confluence(Wiki)にドキュメントを書く

入社初週、シンジはConfluenceを開いた。「技術設計書」というスペースがある。開いてみると……最終更新が2年前だった。

なぜ更新が止まったか。Confluenceはコードから切り離されている。コードが変わってもConfluenceは変わらない。1年後には「古い情報」になり、誰も信頼しなくなる。

「Wikiは誰も更新しないから信頼できない」という評判が広まる。誰も更新しなくなる。さらに古くなる。悪循環だ。

# Confluenceのページ状況
技術設計書/
├── アーキテクチャ概要.md (最終更新: 2021-11-03)← 2年以上前
├── DB設計.md           (最終更新: 2022-03-15)← 内容が古い
├── 認証フロー.md         (最終更新: 2020-08-22)← Deviseに変わる前の内容
└── インフラ構成.md       (最終更新: 2021-06-10)← ECSに移行する前の内容

「Confluenceは死んでる」というのがチームの共通認識だった。

試み2: コードコメントに理由を書く

リョウが試みたのはコードに直接コメントを書くことだった。

# なぜresqueを使っているのか:
# 2021年当時、sidekiqのライセンス問題があった(らしい)
# sidekiqはv6からGPL的なライセンスに変わったと聞いた
# ただし詳細不明。当時のCTO判断
class ApplicationWorker
  include Resque::Job
end

「コメントは良い試みだ。でも問題がある」とシンジは言った。「このコメントに書いてある『ライセンス問題』は本当か? Sidekiqは商用利用に問題があるライセンスになったことはない。当時の記憶違いかもしれない。でもコメントがあると、誰も疑わない」

誤情報がコードコメントに入ると、それが「公式な理由」として伝播する。ADRなら議論でき、レビューで誤りを指摘できる。

試み3: Slack に質問する文化

「分からないことはSlackで聞く」という文化はある。でも問題は「回答がSlackに埋まる」ことだ。

# Slackでよく見るパターン
ユウキ(今日): 「ResqueからSidekiqに移行を検討してるんですが...」
リョウ: 「たぶん2年前に同じ議論があったよ。#tech-discussionを探してみて」
ユウキ: 「見つけました。でも結論が書いてないです」
リョウ: 「ああ、オフラインで話し合ったやつかも。誰かに聞くしかない」

Slackの議論は検索できるが、文脈が分からない。1,000件のメッセージをさかのぼって背景を理解するのは現実的でない。そして「結論だけがSlackにある」状態になる。

シンジはこれらの失敗を見て確信した。コードと同じリポジトリに入れることが鍵だと。コードが変わればADRも同じPRで変わる。誰かがレビューする。そして削除されない限り永遠に残る。

コードレビューでも起きていたこと

シンジはPRのレビューをしていて、もう一つのパターンに気づいた。

コードレビューのコメントに「なぜこの実装にしたんですか?」という質問が多い。そして回答は口頭Slackで説明されるが、その説明は決してコードやコメントに残らない。

PR #287 のコメント:
---
マイ: このRailsのbefore_actionでavoid_duplicate_requestを呼んでいる理由は?
コウスケ: これはiOS側の実装の問題で、ネットワーク不安定時にリクエストが二重に送られる
         バグがあって、サーバー側でべき等性を担保するために入れたやつです
マイ: なるほど!ありがとう

[PRにはコメントなし、mergeされる]

6ヶ月後、別のエンジニアがavoid_duplicate_requestを見て「これって何?消していい?」と聞く。また同じ説明をする羽目になる。

あるいは説明できる人がいなくて、消してしまって本番障害になる。

転機の一冊

ある夜、シンジはカフェで技術書を読んでいた。マイクロサービスの本だったが、その中の一節が目に留まった。

"Architectural decisions are just as important as code. Yet we treat them as if they are disposable — made once and immediately forgotten." (アーキテクチャの意思決定はコードと同様に重要だ。しかし私たちはそれを使い捨て扱いしている――一度下されたら即座に忘れる。)

シンジは手を止めた。

そうだ。コードはバージョン管理している。テストも残す。でも、なぜその設計にしたかは、誰も記録していない。

考えてみれば当然だった。コードは機械が実行するから、間違えたらすぐエラーになる。でも設計判断は、間違えても数ヶ月後にじわじわと問題が出てくる。だから「記録する」という文化が生まれにくい。

本を読み進めると、「ADR(Architecture Decision Records)」という概念が出てきた。

アーキテクチャ意思決定記録

設計の判断を、その理由と共に記録し、未来に残す。たったそれだけのことが、チームの文化を変えるという。

Loading diagram...

シンジはカフェでノートを開いて書き始めた。

ADRを始めよう。チームに提案しよう。

そして具体的な計画を考えた。

  1. まず過去の重要な判断を5本、ADRとして書き起こす(遡及ADR)
  2. 次のPRから、新しい設計判断はADRとして書く
  3. チームに説明して、少しずつ文化にしていく

「完璧じゃなくていい。5行でもいい。書かないより書いたほうがいい」

技術的負債の見える化

その週末、シンジはスプレッドシートを開き「技術的負債の棚卸し」をした。問題をリストアップして、コストを見積もった。

問題週あたりコスト年間コスト
「なぜ?」への回答調査3時間156時間
同じ議論の繰り返し2時間104時間
新人のオンボーディング遅延5時間/人20時間
変更への恐れによる先送り見えないコスト計測不能

エンジニアの時給を5,000円として計算すると、年間で280人時間 × 5,000円 = 140万円以上のコストが「記録がないこと」から生まれていた。

これはチームの問題ではない。仕組みの問題だ。

シンジはこの計算をチームに見せることにした。「個人の問題ではなく、チームの仕組みの問題として話す。それがADRを導入する際の最重要ポイントだ」

月曜日の朝

月曜日の朝、シンジはいつもより少し早くオフィスに来た。

ホワイトボードに大きく書く。

「なぜそうなっているのか、全員が答えられるチームになろう」

そして書き足した。

「コードは何をするかを語る。ADRはなぜそうするかを語る。」

チームのメンバーが順番に出社してくる。マイが画面を見て「また新しい何か始めようとしてる」と苦笑いする。リョウは「ADRって何ですか?」と聞いてくる。コウスケはコーヒーを飲みながら「また勉強会?」と言う。

シンジはうなずいた。「今日、少し時間をくれ。説明したい」

「なぜそうなっているのか誰も知らない」という状況を終わらせよう。

それがシンジのADRへの旅の始まりだった。

ADRが解決しようとしている根本問題

シンジがADRについて調べるうちに、この問題が普遍的であることを知った。

世界中のソフトウェアチームが同じ問題を抱えている。名前は「設計判断の腐敗(Architectural Decision Rot)」。

コードは腐る。適切なメンテナンスなしに放置されると、品質が低下する。これは広く知られている。

しかし同じように、設計判断も腐る。記録されなければ、時間とともに意味を失う。

Loading diagram...

これに対するADRの答えはシンプルだ。

「設計判断を、その文脈と共に、コードと同じ場所に記録する」

それだけだ。特別なツールも、複雑なプロセスも必要ない。Markdownファイルを書いて、Gitで管理する。それだけ。

でも「それだけ」が文化を変える。

シンジはカフェのノートにこう書いた。

「ADRは銀の弾丸ではない。技術的負債がゼロになるわけでも、全ての問題が解決するわけでもない。でも、設計判断の断絶という最も根本的な問題を解決する最もシンプルな方法だ」

「チームに提案してみよう。もし失敗しても、何も失わない。でも成功すれば、チームが変わる」

INFO

「なぜそうなっているのか誰も知らない」という状況は、エンジニアリング組織の成熟度の問題だ。ADRは、その成熟度を上げるための最もコストが低い方法の一つだ。必要なのは、Markdownファイルを書く意志だけ。

ADRを始めることのコスト

「新しい仕組みを始めるのはコストがかかる」とリョウが反論した。

「ADRを書くのに時間がかかる。その時間はプロダクト開発に使ったほうがいい」

シンジは計算した。

ADR導入のコスト:

  • 最初の10本の遡及ADR: 各1〜2時間 = 合計10〜20時間
  • 新しいADRを書く: 1本あたり15〜30分
  • PRレビューへの追加: 5〜10分

ADRを書かない場合の継続コスト(シンジの試算):

  • 「なぜ?」への調査: 週3時間 = 年156時間
  • 同じ議論の繰り返し: 週2時間 = 年104時間
  • 新人オンボーディング遅延: 年20時間以上

「ADR導入の初期コストは20時間。回収できる時間は年間280時間以上。ROIは1,400%だ」

「エンジニアリングの視点で言えば、キャッシュを使うようなものだ。最初に時間を投資して、後から何倍も速くなる」

リョウはしばらく沈黙した後、言った。「……分かった。試してみる価値がある」

問いかけ

この章を読んで、あなたのチームはどうだろうか。

  • 今のコードベースで「なぜそうなっているのか分からない」部分はいくつあるか?
  • 先週、同じ議論を繰り返したことはなかったか?
  • 新しいメンバーがオンボーディングに何ヶ月かかっているか?
  • 「触るな」コメントはいくつあるか?

もし一つでも「ある」なら、ADRはあなたのチームに価値をもたらすかもしれない。

次の章から、シンジと共にADRの旅を始めよう。最初の一歩は、今日の最初の設計判断を記録することだ。

この本で学ぶこと

この物語を通じて、あなたはシンジと共にADRを学ぶ。

テーマ
第2章ADRとは何か――その構造とメリット
第3章ADRの書き方――テンプレートと実例
第4章実例: データベース選定のADR
第5章実例: 認証方式選定のADR
第6章実例: インフラ構成選定のADR
第7章GitでのADR運用
第8章チームとADR――意思決定の民主化
第9章エピローグ――ADR文化の定着

コードは「何をするか」を語る。ADRは「なぜそうするか」を語る。その両方が揃って初めて、チームは自信を持って未来に進める。

技術的負債の多くは「悪いコード」ではなく「理由が分からないコード」から生まれる。ADRはその問題への、最もシンプルで最も効果的な解答だ。


シンジは月曜日の朝、ホワイトボードの前に立った。チームの全員が集まるのを待ちながら、心の中で繰り返した。

「今日から変える。理由が残るチームにする。」

コードは何をするかを語る。ADRはなぜそうするかを語る。その両方が揃って初めて、チームは自信を持って未来を作れる。