1. 人間にとっての「美しさ」が、AIにとっての「迷宮」になる構図
ソフトウェアエンジニアリングの歴史は、「関心の分離(Separation of Concerns)」と「結合度の低減」を追い求めてきた歴史でもあります。オニオンアーキテクチャやヘキサゴナルアーキテクチャ、Clean Architectureに代表される多層レイヤード設計は、ビジネスロジックをフレームワークやデータベースから隔離し、モックを用いた単体テストを容易にするための強力な武器でした。
しかし、2026年の現在、開発チームに最も大きな影響を与えているプレイヤーは人間だけではありません。LLMを頭脳とした自律型コーディングエージェント(Antigravity、Claude Code、Cursorなど) です。
エージェントとペアプログラミングを行う現場で、次のような現象に直面したことはないでしょうか?
- 「ユーザー名にバリデーションを1行追加してほしい」と指示しただけなのに、AIが
Controller、UseCase、UseCaseImpl、RepositoryInterface、RepositoryImpl、Entity、UserDto、UserMapperの8ファイルを延々と読み込み、探索ツールを何往復も呼び出す。 - その結果、コンテキストウィンドウの上限に近づき、途中で指示の前提条件を忘れ(Lost in the Middle)、
Mapperの一部フィールドの詰め替えコードを書き落とす。 - 存在もしない抽象インターフェースのメソッドを勝手に推測して呼び出すハルシネーションを起こす。
「人間にとっての『整然とした多層フォルダ構造』は、有限なコンテキストウィンドウと探索ステップ数で動作するAIエージェントにとって、巨大なトークン浪費の迷宮と化す。いま求められているのは、人間とAIの双方が瞬時に全体像を把握できる『認知負荷の局所化』である。」
AI時代におけるアーキテクチャの評価基準は、「どれだけ純粋にレイヤーを切り離せているか」から、「1つの変更を加えるために、AIと人間が参照しなければならないファイルのホップ数(Indirection Depth)がどれだけ少ないか」 へと急速にシフトしています。
2. 徹底比較: 「過剰な抽象化レイヤー」 vs 「垂直スライス設計」
具体例として、Webバックエンドにおける「ユーザープロフィールの更新機能」を比較してみましょう。
アンチパターン: 実質単一実装のインターフェース乱立と多重DTO変換
従来のClean Architectureを教科書通りに適用しすぎたコードベースでは、以下のように 1 つの処理が複数のファイルに細切れに分散してしまいます。
// ❌ AIが迷子になりやすい分散設計(Indirection Depth が深すぎる例)
// 1. users/domain/user.repository.ts
export interface IUserRepository {
findById(id: string): Promise<UserEntity | null>;
save(entity: UserEntity): Promise<void>;
}
// 2. users/application/update-profile.usecase.ts
export class UpdateProfileUseCase implements IUpdateProfileUseCase {
constructor(
private readonly userRepo: IUserRepository,
private readonly userDtoMapper: IUserDtoMapper
) {}
async execute(dto: UpdateProfileRequestDto): Promise<UpdateProfileResponseDto> {
const user = await this.userRepo.findById(dto.userId);
if (!user) throw new UserNotFoundException(dto.userId);
user.updateProfile(dto.displayName, dto.bio);
await this.userRepo.save(user);
return this.userDtoMapper.toResponseDto(user);
}
}
// 3. users/infrastructure/user.repository.impl.ts
// 4. users/interfaces/dto/update-profile-request.dto.ts
// 5. users/interfaces/dto/update-profile-response.dto.ts
// 6. users/interfaces/mappers/user-dto.mapper.ts ...
実装が Prisma や Drizzle 等の単一のORM/データベースに固定されているにもかかわらず、形式的にインターフェースを定義し、EntityとDTOを双方向に詰め替えるコード(ボイラープレート)が大量に発生しています。
AIエージェントにこの機能を修正させようとすると、最低でも6〜7回のツールコール(grep_search や view_file)が必要となり、プロンプトの往復でトークンを無駄遣いするだけでなく、エージェントの推論精度を著しく低下させます。
改善パターン: コンテキスト指向の垂直スライス(Vertical Slice)
一方、AI親和性と高い凝集度を重視した「コンテキスト指向アーキテクチャ」では、機能単位でファイルを集約(Colocation)します。
// ⭕ コンテキスト指向設計(単一ファイルで入力仕様・検証・永続化が完結)
// features/users/update-profile.ts
import { z } from "zod";
import { db } from "@/shared/db";
import { NotFoundError } from "@/shared/errors";
// ① Single Source of Truth: 入力スキーマと型定義
export const UpdateProfileSchema = z.object({
userId: z.string().uuid("Invalid User ID format"),
displayName: z.string().trim().min(1).max(50),
bio: z.string().max(200).optional(),
});
export type UpdateProfileInput = z.infer<typeof UpdateProfileSchema>;
// ② 実装とビジネスルール(局所化されたクエリと戻り値型推論)
export async function updateProfile(input: UpdateProfileInput) {
const validated = UpdateProfileSchema.parse(input);
const updated = await db.user.update({
where: { id: validated.userId },
data: {
displayName: validated.displayName,
bio: validated.bio,
updatedAt: new Date(),
},
select: {
id: true,
displayName: true,
bio: true,
updatedAt: true,
},
}).catch((err) => {
if (err.code === "P2025") throw new NotFoundError("User not found");
throw err;
});
return updated;
}
このアプローチの劇的な利点は、AIエージェントがこの 1 ファイルを閲覧した瞬間に、機能の全容を完全に把握できること です。
- 何が必須で何が任意か(Zod スキーマで明確に定義されている)
- どのデータベーステーブルのどのカラムが更新されるか
- どのような例外が発生しうるか
参照のジャンプが存在しないため、AIは追加の検索を行う必要がなく、1回のコンテキスト投入で 100% 正確な単体テストや機能拡張を生成できます。
3. コンテキスト指向アーキテクチャの 4 つの鉄則
現場の開発でこの思想を適用するための、実践的な設計ガイドラインをまとめました。
① 局所性の最大化(High Locality & Colocation)
「同じ理由で同時に変更されるコードは、物理的にも近くに配置する」という原則です。Controller、Service、Repository といった「技術的関心事による水平分割」ではなく、機能ごとの「垂直スライス(Vertical Slice)」を採用します。関連する型定義やバリデーションスキーマ、テストコードを同じディレクトリ内に近接させることで、AIの探索コストを劇的に引き下げます。
② 「単一実装インターフェース」の全廃(Kill Single-Implementation Interfaces)
「将来PostgreSQLからMongoDBに変えるかもしれない」「将来別のアダプターを差し替えるかもしれない」という YAGNI(You Aren't Gonna Need It)な抽象化を排除します。
現代のテスティングフレームワーク(Vitest、Jest、Goのモックライブラリ等)は、インターフェースを介さずとも関数単位・モジュール単位で容易にスパイやモックが可能です。また、Testcontainers等を用いた結合テストが主流となった今、形式的なインターフェースはAIにとって「実体定義を探すための無駄な壁」でしかありません。
③ スキーマ駆動(Schema-First)による厳格な入出力境界
TypeScriptのインターフェース定義だけでは、実行時のバリデーションは保証されません。Zod、Valibot、Pydanticなどの「実行時バリデーションライブラリ」を境界に配置します。
AIエージェントはコードを静的に解析する際、スキーマ定義から制約(最大文字数、正規表現、オプショナル条件)を極めて正確に読み取ります。スキーマがそのままバリデーションコードと型定義(z.infer)を兼ねるため、型と実装の乖離が原理的に発生しません。
④ 影響半径(Blast Radius)の極小化
プロジェクトが肥大化すると、誰もが便利な関数を shared/utils.ts や common/helpers.ts に突っ込みがちです。しかし、巨大な共通ファイルはAI駆動開発における最大の爆弾となります。
AIに共通ユーティリティを修正させると、他の20箇所の予期せぬ機能でリグレッション(先祖返り・破壊的変更)を起こすリスクが跳ね上がります。特定の機能でしか使わない小さなヘルパー関数は、共通化せず機能ファイル内に閉じる(Duplication over wrong abstraction)勇気を持つことが、システム全体の安全性を担保します。
4. ドキュメント駆動開発(Document-Driven)との相乗効果
コード構造をコンテキスト指向に再構築した上で、さらに確実な成果を上げるための鍵が 「ドキュメント駆動開発(Document-Driven Development)」 です。
どれほどコードが読みやすくても、エージェントがいきなりソースコードを書き換え始めると、仕様の誤解による手戻りが発生します。プロトコルとして以下の2段階を踏むのがベストプラクティスです。
- 仕様の言語化(Spec.md): 人間とAIが対話しながら、変更する要件と入出力、受け入れ基準を日本語で定義する。
- タスク手順と影響範囲の合意(Task.md): 修正対象のファイル一覧と具体的な修正方針(Strategy)を箇条書きで確定させ、人間の承認を得てから実装ツールをキックする。
局所性の高いコードベースと厳格なドキュメントプロトコルが組み合わさることで、AIエージェントは「迷いなく、最小のステップで、確実な差分」を叩き出す真の開発パートナーへと進化します。
5. まとめ: 「AIフレンドリー」は、突き詰めれば「人間フレンドリー」である
「AIのためにコードの書き方を変える」と聞くと、何か人間側の保守性を犠牲にしているように感じるかもしれません。しかし、現実はその逆です。
無駄な間接参照をなくし、ファイルホップ数を減らし、1つのファイルを開けば関心事が完結しているアーキテクチャは、新しくプロジェクトにジョインした人間のエンジニアにとっても圧倒的に読みやすく、理解しやすいシステム です。
複雑怪奇な多層レイヤーで自尊心を満たす時代は終わりました。シンプルさ、高い凝集度、明示的な型定義——これら普遍的なエンジニアリングの原則に立ち返ることこそが、AI時代における最強のシステム設計戦略なのです。