同僚と複雑なタスクでペアプログラミングをする際、私たちはキーボードの前から始めません。ホワイトボードに向かいます。コンポーネントをスケッチし、データフローを議論し、境界について議論します。どのように構築するかを議論する前に、システムが何をすべきかについて合意します。この合意の後——時には短く、時には長く——ようやく腰を据えてコードを書きます。ホワイトボーディングはオーバーヘッドではありません。そこに本当の思考があり、それがその後のコードを正しいものにします。原則は単純です:キーボードの前にホワイトボード

AI コーディングアシスタントでは、この原則が完全に失われます。速度は魅力的です。機能を説明すれば、数秒で数百行の実装が返ってきます。AI は要件を完全に理解しているかもしれません——リトライロジックを備えたメール通知サービス、など。しかし何を構築するかを理解することと、どのように構築するかを共同で考えることは、2つの異なる活動であり、AI はそれらを1つにまとめてしまいます。どのコンポーネントを作成するか、既存のインフラを使うか新しい抽象化を導入するか、インターフェースをどうするか——そうした議論を挟まずに、要件から実装へ一気に飛び、すべての技術的設計判断を暗黙のうちに行ってしまいます。

私はこれを「実装の罠」と呼ぶようになりました。AI は目に見える出力を非常に速く生み出すため、設計について考えることとコードを書くことの間の自然なチェックポイントが消えてしまうのです。その結果は単に設計が合わないコードというだけではありません。自分が関与しなかった設計判断を解きほぐし、今まさに1行ずつレビューしなければならない実装の中にそれらが詰め込まれているという、認知的な負担です。

見えない設計のコスト

「実装の罠」とは、単に AI が設計を飛ばすということではありません。意味のある意味で、AI はコードを生成する際に設計判断を行っています——スコープ、コンポーネント境界、データフロー、インターフェース、エラーハンドリングについて。しかしそれらの判断は、実装の中に埋め込まれたまま、静かに到着します。「待って、すでにキューシステムがあるよ」や「そのインターフェースは既存のサービスと合わない」といった指摘をできる瞬間がありません。AI の設計思考を最初に見るのはコードを読むときであり、それは不一致を発見するのに最もコストが高く、認知的に負担の大きい場所です。

これが、AI が生成したコードのレビューが同僚のコードのレビューよりずっと疲れる理由だと私は考えています。人間のペアがホワイトボーディングの後にコードを提出する場合、私はすでに理解し合意した設計に対して実装をレビューしています。一方、AI が単一のプロンプトからコードを生成する場合、私は同時にスコープ(必要なものを構築したか?)、アーキテクチャ(コンポーネント境界は適切か?)、統合(既存のインフラに適合するか?)、契約(インターフェースは正しいか?)、コード品質(実装はきれいか?)——すべてを一度に、絡み合った状態で評価しなければなりません。

それは1回のレビューで扱うには多すぎる判断の次元です。人間の脳はそのようにできていません。見落としが生じるのは、注意が足りないからではなく、負荷がかかりすぎているからです。

ソフトウェアエンジニアはこの原則を1980年代から理解しています。Barry Boehm の「変更コスト曲線」は、要件の誤解を設計段階で修正するコストは、実装段階で修正するコストのほんの一部であり、本番環境で修正するコストより桁違いに小さいことを示しました。同じ経済性が AI との協働にも当てはまります。2分の設計会話でスコープのずれを捉えることは、400行の生成コードに織り込まれた問題を発見するよりも、根本的に安上がりです。

見落とされやすいもう1つのコストがあります。AI は機能を追加しがちです。通知サービスの依頼に対して、レート制限、アナリティクスフック、Webhook システムなどを含むコードを返してくることがあります——どれも依頼していなかったものです。これは単なるスコープクリープではなく、私が「技術的負債の注入」と呼ぶものです。依頼しなかったすべての追加は、レビューしなければならないコード、書かなければならないテスト、保守しなければならないサーフェスエリアです。そしてそれらがきれいで理にかなって見えるため、「本当に必要か」と疑問を持つことなく受け入れてしまいやすいのです。レビュープロセスの認知負荷は増大します。なぜなら、依頼しなかった問題を解決するコードを評価することになるからです。

ホワイトボードの再構築

解決策は、人間のペアが自然に行っているホワイトボーディングの会話を再構築することだと私は考えています——AI の暗黙の設計思考を明示的かつ協調的なものにすることです。直接実装を求めるのではなく、段階的な設計レベルを順に進みます。各レベルで、生成コードに埋もれてしまうであろう判断のカテゴリを表面化させます。

私は抽象から具体へ5つのレベルを使っています:

  1. Capabilities —— このシステムは何をする必要があるか? コア要件のみ。実装の詳細は含めない。
  2. Components —— 構成要素は何か? サービス、モジュール、主要な抽象化。
  3. Interactions —— コンポーネント同士はどのように通信するか? データフロー、API コール、イベント。
  4. Contracts —— インターフェースは何か? 関数シグネチャ、型、スキーマ。
  5. Implementation —— ここでコードを書く。

重要な制約:Level 5 が承認されるまでコードは書かない。

この単一のルールが協働のダイナミクスを変えます。各レベルはチェックポイントとなり、コードが存在する前に同意・不同意・方向転換ができる場所になります。Level 1 は共有語彙の確認——設計が始まる前に、AI と自分が同じ機能、同じスコープについて話していることを確認します。より深い価値は Level 2 から始まります。ここで本当の技術的設計会話が展開されます。コンポーネント境界、次にインタラクションパターン、次に契約。各レベルでは、すべての判断を同時に扱うのではなく、1つのカテゴリの判断だけを考えます。これは認知負荷の管理です——脳は一度にすべてを扱う必要がありません。そして人間と AI が各ステップで合意するため、共有メンタルモデルが段階的に構築され、ホワイトボードでの作業と同じように進みます。

これは効果的な人間のペアの働き方です。ホワイトボードで同僚は、一息で完全な実装を提案しません。彼らは箱を描き、それが何をするのか説明し、フィードバックを待ち、次の箱を描きます。共有メンタルモデルは段階的に構築されます。Design-First は同じ規律を AI との協働に適用します。段階的な会話で合意が1ステップずつ構築され、各ステップが次の判断空間を制約するのです。

この構造がなければ、AI は本当の意味でのペアではありません——別室で一人ですべての判断を下し、完成したコードを持って戻ってくる同僚です。Design-First は協働を本物にします。人間と AI が設計の各レイヤーを一緒に検討し、実装が始まる時点で、何をなぜ構築するのかについて genuine な共有理解ができているのです。

実際の運用例

最近、通知サービスを構築する際に、このアプローチはその価値をすぐに示しました。

私は明確なコンテキストを持って会話に臨みました。ストーリーから得た機能スコープ——v1 ではメール配信のみ、リトライロジックと基本的なステータストラッキング——と、BullMQ をジョブ処理に使用しているプロジェクトの技術スタックです。Capabilities レベルでは、このスコープを最初に共有し、設計が始まる前に AI に理解を確認させました。短い確認ですが重要なものでした。構築するものと、明示的にスコープ外であるものについて、同じ言語で話していることを保証したのです。

本当の設計会話は Components レベルで始まりました。AI は5つのコンポーネントを提案し、その中に BullMQ をラップする専用の RetryQueue 抽象化が含まれていました。紙の上ではアーキテクチャ的にきれいでしたが、不要でした。BullMQ の組み込みリトライメカニズム(指数バックオフ付き)がネイティブに処理しており、追加の抽象化は価値のない間接化を増やすだけです。私は反対しました。BullMQ を直接使い、ラッパーは不要。AI はコンポーネント構造を簡素化しました。これはコンテキストの欠如ではありません——AI は私が共有したプロジェクトコンテキストから BullMQ について知っていました。これは既存のインフラを抽象化するかどうかという、本物の設計上の意見の相違であり、コードがまだ存在しない時点で数秒で解決しました。

Interactions レベルでは、この簡素化されたコンポーネント構造が連鎖的な利点をもたらしました。不必要な抽象化レイヤーがないため、データフローはより直接的になりました。ハンドラが BullMQ ジョブをキューイングし、ワーカーが処理し、リトライはネイティブに処理されます。インタラクション設計は、ラッパーを含む場合よりもクリーンで、既存のインフラに適合したものになりました。

Contracts レベルでは、特に価値のあることが起きます。合意された関数シグネチャ、型、インターフェース——NotificationPayloadNotificationResultEmailProviderDeliveryTracker——が揃った時点で、私は AI に実装コードを書く前にテストを書くよう依頼することができました。設計会話は、副次的な効果として、テスト駆動開発の前提条件を作り出していたのです。契約を承認し、テストを生成し、そのテストに対して実装する。TDD を実践するチームにとって、Design-First は自然な入り口を提供します。TDD を実践しないチームにとっても、契約は安全網として機能します——誤解がバグになる前に可視化できる、具体的でレビュー可能な仕様です。

Level 5 で実装が始まる頃には、コードは単一のプロンプトで生成されたものよりもはるかに整合性の高いものになっていました。スコープは確認済み。アーキテクチャは既存のインフラに適合していました——AI が会話の途中で教えられたからではなく、設計レベルでその使い方を議論したからです。契約は定義され、テスト済み。AI は推測していませんでした——合意された設計を実装していたのです。

規律と調整

このアプローチは、AI アシスタントの典型的な使い方とは逆行する形の規律を必要とします。AI は役立つように訓練されており、「役立つ」とは通常、目に見える出力を素早く生み出すことを意味します。抑制されなければ、実装に飛びつきます。レベルを混ぜて、すでにコードが書かれたコンポーネント図を提示したり、実装が添付された契約を提案したりします。「待って——まだ Level 2 だ。コンポーネント構造だけを見せて」という規律は、実際にはワーキングメモリを早まった詳細から守ることです。判断されている決定に適した抽象化レベルで会話を保つのです。

すべてのタスクが5つのレベルすべてを必要とするわけではありません。このフレームワークは作業の複雑さに応じてスケールします。

タスクの複雑さ開始レベル
単純なユーティリティLevel 4(Contracts)日付フォーマッタ、文字列ヘルパー
単一コンポーネントLevel 2(Components)バリデーションサービス、API エンドポイント
複数コンポーネントの機能Level 1(Capabilities)通知システム、決済統合
新規システム統合Level 1 + 深めの Level 3サードパーティ API、イベント駆動パイプライン

このフレームワークは複雑さを管理するためのツールであり、画一的に適用する儀式ではありません。

Design-First は、私が「Knowledge Priming」と呼んでいるものとも相乗効果を発揮します——作業を始める前に、AI に選別されたプロジェクトコンテキスト(技術スタック、コーディング規約、アーキテクチャ判断)を共有することです。AI がすでにプロジェクトのアーキテクチャ、命名規約、バージョン制約を理解している場合、各設計レベルはすでにコードベースにアンカリングされています。Capabilities の議論は実際の要件を反映し、Components レベルでは既存の命名パターンが使われ、Contracts は確立された型の規約に合致します。Priming が語彙を提供し、Design-First が構造を提供します。2つが揃うことで、絶え間ない修正ではなく、自然に合意が生まれる会話が実現します。

実際には、1つの開始プロンプトでパターン全体を起動するのに十分なことが多いです。

I need to build [feature]. Before writing any code, walk me through the design:

- Capabilities: [your scope constraints]
- Components: [your infrastructure and architecture constraints]
- Interactions: [your integration and data flow patterns]
- Contracts: [your type and interface conventions]

Present each level separately. Wait for my approval before moving to the next.
No code until the contracts are agreed.
      

このプロンプトは順序の規律を設定します——AI は各レベルを提示し、フィードバックを待ち、明示的な承認が得られるまでコードを保留します。しかし本当の価値は角括弧の中にあります。各開発者が埋めるプロジェクト固有の制約です。「v1 ではメールのみ」「既存の BullMQ インフラを使う」「関数型パターンで、クラスは使わない」「すべてのインターフェースは確立された型の規約に合致させる」。これらの制約は各レベルを通じて引き継がれ、繰り返す必要なく各設計判断を形作ります。各チェックポイントで会話は深まり——私はコンテキストを追加し、仮定を修正し、自分がコードベースについて知っていることに基づいて設計を方向づけます。プロンプトが動きを始め、各レベルでの協働がそれを形作るのです。

時間の経過とともに、このパターンが価値があると判断したチームは、それを再利用可能で共有可能な指示としてエンコードすることが多く、アーキテクチャのガードレール、品質の期待値、チーム固有のデフォルト値を追加して、すべての設計会話に自動的に適用するようになります。

このアプローチにコストがないわけではありません。設計会話は、すぐにコードを依頼するよりも時間がかかります。些細なタスクでは、そのオーバーヘッドは正当化されません。また学習曲線もあります。プロンプトを入力してコードを受け取ることに慣れた開発者にとって、順序立てられた構造は最初は馴染みにくいかもしれません。特に締め切りが迫り、「とにかくコードを依頼しよう」という誘惑が強い場合には、この規律を維持するには意識的な努力が必要です。

私は、この投資が主に報われるのは、非自明な作業——誤解が数分ではなく数時間のコストを生むような作業、アーキテクチャの整合性が重要になる作業、コードが最初の会話の後も長く保守・拡張される作業——においてだと考えています。

結論

ホワイトボードの価値は、図そのものではありませんでした。それは合意——2人が問題を1レイヤーずつ一緒に考えていくときに生まれる共有理解でした。Design-First はこの原則を AI との協働に適用します。

人間と AI が同じスコープ、同じコンポーネントアーキテクチャ、同じインタラクションモデル、同じ契約から作業する場合、認知負荷は疲弊した vigilance から協調的な洗練へと移行します。開発者の役割は、見えない設計判断を逆解析することから、それらを明示的に方向づけることへと変わります。これこそが、実際に AI とペアを組むということだと私は考えています——単にその出力を受けるのではなく、その思考に参加することです。

このアプローチ全体の最もシンプルな形は、1つの制約です。設計が合意されるまでコードは書かない。それ以外はすべて、そこから導かれます。