📝

「whyを残す」で未来の自分とチームを救う

に公開

はじめに

「なんでこのコードってこうなってるんだっけ?」

半年前に自分が書いたコードを見返して、実装の意図が思い出せず困った経験はありませんか?
コードレビューで「なぜこの実装にしたんですか?」と聞かれて、答えに詰まったことは?

実は、これらの問題は**「whyを残す」**という習慣で解決できます。

私たちLectoでは、物事の対応一つにおいても、以下を大切にしています:

  1. 背景を考える:なぜこの対応が必要なのか
  2. 対応方法を悩む:どのアプローチが最適か、複数の選択肢を比較する
  3. 決めた理由を残す:whyをコミットメッセージやドキュメントに記録する

本記事では、「whyを残す」ことで実際にどんな価値が生まれたのか、実例を交えてご紹介します。

きっかけ:AIに聞いたら、過去の判断理由が全部わかった

先日、以前実装した郵便番号検索機能について技術記事を書こうと思い、Claudeにこう聞いてみました。

郵便番号の検索によるライブラリの選択の経緯をgit logから教えてもらえないですか

すると、Claudeは過去のコミット履歴を辿り、なぜそのライブラリを選んだのか、なぜ途中で変更したのかという詳細なタイムラインを出力してくれました。

郵便番号検索ライブラリの選択経緯

変遷(時系列)

1. 初期実装(2025年8月7日 17:42)
   ライブラリ: zipcloud API(外部サービス)
   - 最初は外部APIサービス(zipcloud)を使用して実装

2. YubinBango.jsへの切り替え(2025年8月7日 18:50) - わずか1時間後
   コミット: a1b2c3d(例)

   選定理由(プライバシー重視):
   1. 個人情報保護: KYCシステムで扱う郵便番号を外部APIに送信するリスクを回避
   2. 透明性: オープンソースで処理内容を確認可能
   3. 最小限の情報送信: 郵便番号の最初3桁のみを送信(完全な7桁は送信しない)
   4. 追跡困難: GitHub Pagesの静的ファイル参照のため個別追跡が困難

3. サーバーサイド実装への移行(2025年8月25日)
   コミット: e4f5a6b(例)
   ライブラリ: jp-zipcode-lookup

   選定理由(技術的改善):
   1. SSR対応: React Router v7のサーバーサイドレンダリングに完全対応
   2. プライバシー保護の強化: 外部API不使用、ローカル完結
   3. TypeScript完全対応: 組み込み型定義
   4. 高速性: ネットワーク遅延なし

わずか数秒で、3段階の技術選定の理由が明らかになりました。

なぜこんなことが可能だったのか? それは、当時のコミットメッセージに**「なぜその判断をしたのか」という背景(why)を残していたから**です。

この経験から、「whyを残す」ことの重要性を改めて実感し、記事にすることにしました。

実例:郵便番号検索の実装で「why」が活きた場面

上記の記事では、郵便番号検索機能の実装について3つのアプローチを比較しました。この記事を書く際に、git logに残されたwhyが非常に役立ちました

アプローチ1:外部API(zipcloud)→ わずか1時間で却下

最初のアプローチでは、zipcloudという外部APIを検討しました。

実装自体は数行で完了しましたが、コミットメッセージには却下理由が詳しく書かれていました

feat: 郵便番号検索の実装(zipcloud API)

却下理由:
- KYC(本人確認)で扱う郵便番号を外部APIに送信するプライバシーリスク
- 外部サービスでアクセスログが保存される可能性
- 処理がブラックボックスで、障害時の原因調査が困難
- お客様の環境のネットワーク制約に影響される

→ オープンソースのライブラリへの切り替えを検討

半年後にこの記事を書く際、なぜ外部APIを使わなかったのかという理由がすぐに分かりました。

アプローチ2:yubinbango-core2 → SSR要件で移行

次のアプローチでは、yubinbango-core2というクライアントサイドライブラリを採用しました。

しかし、数週間後にReact Router v7のSSR対応が必要になり、移行を決断。このコミットメッセージにも詳細な理由が記録されていました:

feat: yubinbango-core2からjp-zipcode-lookupへ移行

移行理由:
1. SSR対応が必須となったが、yubinbango-core2はDOM依存で非対応
2. React Router v7のローダーパターンで使えない
3. プライバシー保護の観点から、外部通信を完全に排除したい
4. TypeScript対応による開発体験の向上

トレードオフ:
- データ更新を手動で行う必要があるが、四半期ごとの更新で十分
- バックエンドの実装が必要だが、SSRの要件上すでにバックエンドがある

参考:
- yubinbango-core2はブラウザ専用: https://github.com/yubinbango/yubinbango-core2
- jp-zipcode-lookupはSSR対応: https://www.npmjs.com/package/jp-zipcode-lookup

このように、なぜ移行が必要だったのかどんなトレードオフを許容したのかが明確に記録されていたため、記事執筆がスムーズに進みました。

アプローチ3:jp-zipcode-lookup → 最終的な選択

最終的なアプローチでは、jp-zipcode-lookupをサーバーサイドで使用する方法を選択しました。

この選択の背景も、コミットメッセージにしっかり記録されていました:

feat: jp-zipcode-lookupでサーバーサイド実装

採用理由:
- SSR完全対応:React Router v7のローダーパターンで使える
- プライバシー保護:郵便番号データをローカルに保持、外部通信なし
- 高速性:ネットワーク遅延なし
- TypeScript完全対応:開発体験の向上
- 活発なメンテナンス:2025年6月最終更新

デメリット(許容理由):
- データ更新を手動管理 → 四半期ごとの更新で十分
- サーバーリソース消費 → 数MBなので実用的に問題なし

技術記事を書く際、これらのコミットメッセージをそのまま引用できました。 git logを見るだけで、当時の判断理由が鮮明に蘇ったのです。

「whyを残す」ことで得られる3つの価値

実際に「whyを残す」文化を実践してきて、以下の価値を実感しています。

1. 未来の自分への贈り物

半年後、1年後に「なんでこういう実装にしたんだっけ?」と疑問に思った時、当時の判断理由がすぐに分かります。

悪い例:

feat: 郵便番号検索のライブラリを変更

良い例:

feat: yubinbango-core2からjp-zipcode-lookupへ移行

理由:
- SSR対応が必須となったため
- React Router v7のローダーパターンで使いたい
- プライバシー保護の観点から外部通信を排除したい

トレードオフ:
- データ更新を手動で行う必要があるが、四半期ごとの更新で十分

前者だと「なぜ変更したのか」が分からず、再び同じ検討をする羽目になります。後者なら、当時の判断理由がすぐに理解できます。

2. チームメンバーへの配慮

新しくプロジェクトに参加したメンバーが、過去の技術的意思決定の背景を理解しやすくなります。

例えば、新メンバーが「なぜ外部APIを使わないんですか?」と疑問に思った時、git logを見れば:

  • zipcloudを検討したが、プライバシーリスクで却下した
  • yubinbango-core2を使ったが、SSR要件で移行が必要になった
  • 最終的にjp-zipcode-lookupを選んだ理由

という経緯が一目瞭然です。

3. 同じ失敗を繰り返さない

「このアプローチは過去に試したが、こういう理由で却下した」という記録があれば、同じ道を辿らずに済みます。

例えば、新メンバーが「zipcloudの外部API使いませんか?簡単ですよ」と提案した時、git logを見れば:

却下理由:
- KYCで扱う郵便番号を外部APIに送信するプライバシーリスク
- 外部サービスでアクセスログが保存される可能性

という過去の判断理由がすぐに共有でき、無駄な議論を避けられます。

不具合対応でも「why」は活きる

「whyを残す」ことは、技術選定だけでなく、不具合対応やコード品質の維持にも役立ちます。

なぜこの実装になっているのか追える

例えば、一見複雑に見えるエラーハンドリングのコードがあったとします。

whyが残っていない場合:

// なぜか3回リトライしている...?
for (let i = 0; i < 3; i++) {
  try {
    await fetchData();
    break;
  } catch (e) {
    if (i === 2) throw e;
  }
}

このコードを見た新メンバーは「なんで3回?」「2回じゃダメなの?」と疑問に思うでしょう。

whyが残っている場合:

// リトライ3回の理由:
// - 本番環境で稀にネットワーク瞬断が発生(Issue #123)
// - 1回のリトライでは不十分だった(2回目も失敗するケースがある)
// - 3回で99.9%成功することを確認(検証データ: docs/network-retry-analysis.md)
for (let i = 0; i < 3; i++) {
  try {
    await fetchData();
    break;
  } catch (e) {
    if (i === 2) throw e;
  }
}

コメントやコミットメッセージに背景が書いてあれば、なぜこの実装が必要だったのかがすぐに理解できます。

キレイなコードでなくても、背景があれば納得できる・・・はず

理想的には、全てのコードが美しくリファクタリングされているべきです。しかし、現実には時間的制約や技術的制約で、ベストではない実装を選ばざるを得ない場合もあります。

そんな時、状況や背景を残しておけば、読む人が納得できます

例:複雑な条件分岐

// この条件分岐が複雑な理由:
// - 旧システムとの互換性を保つ必要がある(2026年3月まで)
// - 新旧の仕様が混在しており、データ移行が完了していない
// - Issue #456: データ移行完了後にリファクタリング予定
if (isLegacySystem && !isMigrated) {
  // 旧システム用の処理
} else if (isLegacySystem && isMigrated) {
  // 移行中の処理
} else {
  // 新システムの処理
}

このように背景を残しておけば:

  • 「なんでこんな複雑なの?」という疑問に答えられる
  • 「いつリファクタリングできるのか」が明確
  • 「勝手に削除してはいけない」という注意喚起になる

具体的な実践方法

では、実際にどうやって「whyを残す」を実践すればいいのでしょうか?

コミットメッセージに書くべきこと

✅ Good Example:
feat: 郵便番号検索をjp-zipcode-lookupに移行

理由:
- SSR対応が必須となったため
- React Router v7のローダーパターンで使いたい
- プライバシー保護の観点から外部通信を排除したい

トレードオフ:
- データ更新を手動で行う必要があるが、四半期ごとの更新で十分

参考:
- 以前のyubinbango-core2はDOM依存でSSR非対応
- jp-zipcode-lookup: https://www.npmjs.com/package/jp-zipcode-lookup

記載すべき内容:

特に重要なのが**Why(なぜ)**です。単に「SSR対応のため」だけでなく、以下のような背景や考えたことを書きましょう:

  • Why(なぜ)

    • なぜこの変更が必要なのか
    • どういう背景でこの判断に至ったのか(例:React Router v7導入、顧客要件、パフォーマンス問題)
    • 何を解決しようとしているのか
    • 他にどんな選択肢を検討したのか、なぜそれを選ばなかったのか
    • どんな議論や懸念があったのか
  • What(何を):何を変更したのか

  • How(どのように):どのように実装したのか

  • Trade-offs(トレードオフ):デメリットは何か、それは許容できるか

  • References(参考):関連するドキュメントやリンク

Whyの良い例:

❌ 悪い例:SSR対応のため
✅ 良い例:
React Router v7の導入に伴い、SEO改善とパフォーマンス向上のためSSRが必須となった。
しかし、現在のyubinbango-core2はDOM APIに依存しており、サーバーサイドで動作しない。
チームで議論した結果、以下の理由からjp-zipcode-lookupへの移行を決定:
- 外部APIは顧客のプライバシー要件を満たせない
- クライアントサイドのみの処理ではSEOに不利
- ローダーパターンでデータフェッチを統一したい

PRやIssueに書くべきこと

PRやIssueでは、さらに詳しく書くことができます。

## 背景(Why)

React Router v7の導入により、SSR対応が必須となりました。
しかし、現在使用しているyubinbango-core2はブラウザのDOM APIに依存しており、
サーバーサイドでは動作しません。

## 選択肢の比較(How)

以下の3つの選択肢を検討しました:

1. **外部API(zipcloud)**
   - ❌ KYCで扱う郵便番号を外部に送信するプライバシーリスク
   - ❌ 処理がブラックボックス

2. **yubinbango-core2(現状)**
   - ❌ SSR非対応(DOM依存)
   - ❌ React Router v7のローダーで使えない

3. **jp-zipcode-lookup(採用)**
   - ✅ SSR完全対応
   - ✅ プライバシー保護(ローカル完結)
   - ✅ TypeScript完全対応
   - ⚠️ データ更新を手動管理(許容可能)

## トレードオフ(Trade-offs)

jp-zipcode-lookupを採用することで、以下のトレードオフが発生します:

- **デメリット**:郵便番号データの更新を手動で行う必要がある
- **許容理由**:四半期ごとの更新で十分であり、カレンダーリマインダーで対応可能

## 参考資料

- [jp-zipcode-lookup公式](https://www.npmjs.com/package/jp-zipcode-lookup)
- [React Router v7のローダーパターン](https://reactrouter.com/en/main/route/loader)

このように詳しく書いておけば、後から見返した時に当時の判断が正しかったかを検証しやすくなります。

コード内のコメントに書くべきこと

特に複雑な実装や、一見不可解な実装には、コメントで背景を残しましょう。

// ❌ 悪い例:Whatしか書いていない
// 3回リトライする
for (let i = 0; i < 3; i++) { ... }

// ✅ 良い例:Whyが明確
// 本番環境でネットワーク瞬断が発生するため3回リトライ(Issue #123)
// 検証の結果、3回で99.9%成功することを確認
for (let i = 0; i < 3; i++) { ... }

まとめ

「whyを残す」ことで得られる価値:

  1. 未来の自分への贈り物:半年後に振り返った時、判断理由がすぐに分かる
  2. チームメンバーへの配慮:新メンバーが過去の意思決定を理解しやすくなる
  3. 同じ失敗を繰り返さない:過去の判断を活かせる

実践のポイント:

  • コミットメッセージに**Why(なぜ)**を丁寧に書く
    • 単なる理由だけでなく、背景、検討した選択肢、議論の内容も含める
    • Trade-offs(トレードオフ)や References(参考)も忘れずに
  • PRやIssueで選択肢の比較と判断理由を詳しく記録する
  • 複雑なコードには背景や制約をコメントで残す

技術的な意思決定において、なぜその選択をしたのかという背景を記録しておくことは、未来の自分やチームメンバーへの贈り物です。

「whyを残す」はコミットメッセージだけではない

この考え方は、コミットメッセージに限らず、あらゆる場面で活用できます:

顧客との課題へのアプローチ

  • なぜこの機能が必要なのか(顧客の背景や課題)
  • どんな議論があって、この仕様に決まったのか
  • 他にどんな選択肢を検討したのか

例えば、「この機能、なんでこういう仕様なんでしたっけ?」となった時でも、
議事録やドキュメントにwhyが残っていれば、すぐに答えられます。

その他の活用例

  • 議事録:決定事項だけでなく、議論の経緯や懸念点も記録
  • 設計ドキュメント:なぜこのアーキテクチャを選んだのか、他の選択肢はなぜ却下したのか
  • 障害対応:なぜこの暫定対応を選んだのか、将来どう改善するのか
  • 仕様変更:なぜ仕様を変更したのか、何を優先したのか

「whyを残す」は、開発全体に通じる考え方です。一見すると手間に思えるかもしれませんが、長期的には圧倒的に効率的です。ぜひ、明日から「whyを残す」習慣を始めてみてください。

参考リンク

Discussion