なぜ?から始める実装設計
はじめに
実装設計におけるBackground(背景)は、「なぜ問題をこのシステムで解決しようとしているのか」を説明します。
ただし、実際に書いてみると実装方法(how) から始めてしまい、なぜ(why) が抜けてしまうことがよくあります。
私自身、リファクタリングの面も含まれるような変更に対してBackgroundを書いた際に、技術的な詳細ばかりが先行してしまい、「そもそもなぜこの機能が必要なのか」を説明できていなかった経験があります。
この記事では、その反省を踏まえてBackgroundに書くべきこと/書くべきでないことを整理し記録に残したいと思います。
今回の経験
シチュエーション
あるシステムの設計において、共通ライブラリと機能固有のモジュールが存在している。
AIシステムからの出力を構造化するため、出力スキーマを定義してAIの設定に渡す必要がある。
しかし、現状では出力スキーマが共通ライブラリにハードコーディングされており、
共通部分にビジネス固有のロジックが存在するという設計上の問題があったため、
その構造をリファクタリングする必要があった。
自分が書いたBackgroundの大枠
システムにおいて、様々な用途で固定の出力スキーマをLLMから返してもらう必要がある。
現在は出力スキーマが共通ライブラリにハードコーディングされており、
システムの用途を追加するたびに共通ライブラリを修正する必要があり影響範囲が大きくなってしまう&
記述が冗長になってしまい保守性が下がる。
共通ライブラリではなく機能のモジュールで出力スキーマを定義できるようにし、
システムの用途の追加や修正を行っても共通のインターフェースで出力スキーマを扱える仕組みを
整えることが求められている。
このような書き方の問題点:
- 読み手が置いてきぼりになる:そもそもなぜAIシステムから固定の出力スキーマを返してもらう必要があるのかがわからない
- 技術的詳細が先行している:実装の話から始まっている
- ビジネス価値が不明:これによって誰がどう嬉しくなるのかが伝わらない
レビューで指摘を受けてはじめて、自分が「how(どうやって実装するか)」ばかり考えていて、「why(なぜ必要なのか)」をまったく説明できていないことに気づきました。
実体験から学んだ3段階思考フレームワーク
この経験から、Backgroundをは以下の3段階で考えることが重要だと学びました。
Step 1:「そもそも論」で考える
最初に自分に問いかけるべき質問:
- そもそもこの機能はなぜ必要なのか?
- 従来の方法では、なぜダメなのか?
私の場合:
- 「AIをシステムの一部として組み込み、自動的に処理させるため、自由形式の自然文ではなく指定したJSONスキーマに従った構造化出力が必要である」
- 「自由形式のテキストでは、後続の処理で必要な情報が抜けたり、形式が異なったりしてシステマティックな処理ができない」
Step 2:「現実のギャップ」を明確にする
理想と現実のギャップを整理:
- 理想的にはどうあるべきか?
- 現在はどうなっているか?
- そのギャップがなぜ問題なのか?
私の場合:
-
理想:各機能が独立してスキーマを管理でき、1つの機能の変更が他に影響しない
-
現実:データ処理用スキーマが共通ライブラリにハードコーディングされており、機能固有のビジネスロジックが共通部分に入り込んでいる
-
問題:以下の構造的な問題が発生している:
1. 機能追加のたびに共通部分を修正
- 新しい機能(要約、分類など)を追加するたびに共通ライブラリの変更が必要
- 機能固有の要件変更でも、システム全体に影響する修正が発生
2. 特定プロバイダーへの依存
- 現在の実装が特定のAIプロバイダー専用になっている
- 他のAIプロバイダーに対応する際も共通部分の修正が必要
3. 責務の混在
- 本来各機能で管理すべきスキーマが共通ライブラリに混入
- 汎用的であるべき共通部分が特定機能の知識を持ってしまっている
避けるべき表現
私が最初に書いてしまったような技術用語で埋め尽くされた説明:
- 「出力スキーマが共通ライブラリにハードコーディングされている」
- 「レイヤー間の依存関係が逆転している」
- 「共通基盤がビジネスロジックを抱えている」
推奨される表現
構造的な問題に焦点を当てた説明:
- 「新しい機能を1つ追加するために、システム全体のテストとデプロイが必要になってしまう」
- 「本来安定すべき基盤部分が、ビジネス要件の変化によって頻繁に修正される状況になっている」
- 「汎用的なライブラリに特定機能の仕様が蓄積され続け、将来的にメンテナンスが困難になる」
Step 3:「求められる方向性」を示す
具体的な解決方法は書かずに、どの方向に向かうべきかだけを示す:
- どんな性質を持つシステムが必要か?
- どんな価値を提供すべきか?
- どんな制約を満たすべきか?
私の場合:
- 性質:「各機能が独自のスキーマを管理でき、どのAIプロバイダーでも統一的に動作する構造が必要」
- 価値:「新機能の追加や既存機能の改善が共通部分に影響を与えずに迅速に行える」
- 制約:「スキーマ管理の責務を適切な場所に配置し、共通部分は汎用的な機能のみ提供する」
今回の経験を振り返って:修正前後の比較
私が実際に書いたBackgroundを、この思考フレームワークに沿って修正するとどうなるか見てみましょう。
修正前(技術的詳細から始めるパターン)
システムにおいて、様々な用途で固定の出力スキーマをLLMから返してもらう必要がある。
現在は出力スキーマが共通ライブラリにハードコーディングされており、システムの用途を追加するたびに共通ライブラリを修正する必要があり影響範囲が大きくなってしまう。記述が冗長になってしまい保守性が下がる。
共通ライブラリではなく機能のモジュールで出力スキーマを定義できるようにし、システムの用途の追加や修正を行っても共通のインターフェースで出力スキーマを扱える仕組みを整えることが求められている。
修正後(ビジネス価値から始めるパターン)
システムでは、AIを活用して様々な業務を自動化・効率化する必要がある。そのためには、自由形式の自然文ではなく指定したJSONスキーマに従った構造化出力が必要である。
例えば、データ処理機能では「処理後テキスト」と「処理情報」を含むJSON、要約機能では「要約文」「重要ポイント」を含むJSONを生成するなど、それぞれの機能に応じて異なる出力形式が求められる。しかし現在は、これらの出力スキーマが共通ライブラリにハードコーディングされており、機能固有のビジネスロジックが共通部分に入り込んでしまっている。さらに、この実装は特定のAIプロバイダーに依存している。そのため、新しい機能を追加したり、他のAIプロバイダーに対応したりするたびに、システムの共通部分を修正しなければならない。
出力スキーマの管理を各機能で行い、どのAIプロバイダーでも統一的に動作する汎用的な仕組みを提供する設計が求められている。
実践的なチェックリスト
今回の反省を踏まえて、Backgroundセクションを書き終えたら必ず確認すべきポイントをまとめました:
- 技術的な詳細を知らない人でも理解できるか?
- 「なぜこの機能が必要なのか」が最初に説明されているか?
- 具体的な実装方法(how)ではなく、理由(why)に焦点を当てているか?
- 読み手が「なるほど、確かに必要だね」と納得できるか?
- ビジネス価値や実際の困りごとが明確に述べられているか?
陥りがちな罠と対策
罠1:「現在の技術的問題」から書き始める
対策:まず「この機能によって、誰のどんな課題が解決されるのか」を明文化する
罠2:アーキテクチャ図を思い浮かべながら書く
対策:技術を知らないステークホルダーに説明するつもりで書く
罠3:解決方法を詳しく説明してしまう
対策:「どのような性質を持つ仕組みが必要か」を1-2文で表現するに留める
罠4:抽象的すぎる表現を使う
対策:具体的な例や数値を交えて説明する(ただし技術的詳細は避ける)
まとめ
今回の経験を通して、実装のBackgroundで重要なのは「ストーリー」だと思いました。私のように技術的な詳細から書き始めてしまうと、全体のストーリーが見えず、読み手にとって実装の必要性が分からないドキュメントになってしまうと学びました。
覚えておくべき3段階
- 「そもそも論」で考える - なぜこの機能が必要なのかから始める
- 「現実のギャップ」を明確にする - 理想と現実の差とその問題を整理する
- 「求められる方向性」を示す - 具体的な解決方法ではなく、必要な性質・価値・制約を示す
最後に
今回、「なぜこの機能が必要なのかが抜けている」とレビューされてはじめて、自分が書いたBackgroundの問題に気づきました。技術的な正しさばかりに気を取られて、一番重要な「そもそもの必要性」を説明できていませんでした。
この失敗から学んだことは、今後の実装設計でも使い続けるつもりです。特に 「そもそも論で考える」 ステップは、技術詳細から入りがちなエンジニアには必須だと感じています。
何かの参考になれば幸いです。
Discussion