最近のAIコーディングツールはかなり賢くなっています。
以前のように、
全部 page.tsx に書く
のようなコードを大量生成することは減りました。
むしろ最近は自然に、
components/
hooks/
lib/
types/
のような構成へ分離してきます。
ただ、実際に使っていると別の問題が出てきます。
修正の影響範囲が広いという問題です。
例えば本来数行程度の修正で済む内容でも、
- 型定義更新
- 関連箇所の追従
- import整理
- 命名修正
- 軽いリファクタ
- 不要コード削除
まで同時に行われ、差分が数百行になることがあります。
これは「分離できていない」というより、
変更する境界が曖昧なことが原因だと感じています。
最近のAIは「構造化」はする
これは重要で、最近のAIは普通に責務分離を試みます。
例えば:
- UI → components
- state → hooks
- utility → lib
- 型 → types
のような整理はかなり自然に行います。
なので問題は、「分割されていない」ではありません。
実際には、「変更目的に対して、必要最小限の差分で止める」のがまだ苦手です。
見た目上の分離と、本当の境界は違う
例えば構成が綺麗でも、
components/
hooks/
lib/
types/
実際には:
- hooks が API を直接叩く
- components が localStorage を触る
- lib が React に依存する
- types がビジネスロジックを持つ
という状態は普通に起きます。
つまり、ディレクトリが分かれていることと、変更境界が分離されていることは別です。
AIは構造を真似るのは得意ですが、「どこまで変更してよいか」変更の局所性を保つのはまだ苦手です。
そのため関連性を広く解釈し、変更が波及しやすい。
そこで再び重要になる「関心の分離」
ここで昔からある設計原則が効いてきます。
- Separation of Concerns
- SRP
- 疎結合
- モジュール境界
- Interface / Contract 設計
などです。
ただ、AI時代では目的が少し変わっていると思っています。
従来は:
- 人間が理解しやすい
- 保守しやすい
- チーム開発しやすい
ための設計でした。
一方でAI時代では、「変更影響範囲を制御する」ための意味合いがかなり強くなっています。
特に重要なのが「インターフェース」
例えば:
type EditorDocument = {
nodes: Node[]
}
のような共通契約を定義する。
すると:
- UI
- 保存
- export
- API
- AI生成
などが、この型を境界として接続されます。
つまり、内部実装は変えてもよいが、この契約は壊さない」という制約を作れる。
AIコーディングでは、この「壊してはいけない境界」がかなり重要になります。
逆に境界が曖昧だと:
型変更
↓
保存形式変更
↓
export変更
↓
state変更
↓
UI変更
のように変更が伝播しやすい。
「このファイルを触るな」は意外とうまくいかない
最初は、
このファイル以外変更禁止
のように制約したくなります。
ただ実際には、これだと import/export の調整すらできず、逆に破綻しやすい。
そのため最近は、「ファイル単位」ではなく「責務単位」で制約する方が安定すると感じています。
例えば:
今回の変更は表示責務のみ。
許可:
- UIコンポーネント
- 必要最小限の import 修正
禁止:
- state構造変更
- 保存形式変更
- API仕様変更
のように、「どの責務まで変更可能か」を制限する。
これは人間同士のレビュー観点にもかなり近いと思います。
完璧設計を目指すより、「壊れやすい場所」を境界化する
ただし実際には、最初から綺麗な境界を作るのは難しいです。
AIは開発速度が非常に速いため、
とりあえず page.tsx に書く
↓
あとで分離しよう
↓
AIが既存構造を参考に増殖
↓
巨大化
は普通に起きます。
なので実際には、
- 毎回差分が爆発する場所
- AIが関係ない箇所まで触る場所
- import/export が壊れやすい場所
- レビューがつらい場所
から後付けで境界化していく方が現実的です。
まとめ
最近のAIは、ある程度の責務分離や構造化は普通に行います。
ただ一方で、「変更目的に対して、必要最小限の差分で止める」のはまだ苦手です。
そのため、昔からある
- 関心の分離
- Interface / Contract
- モジュール境界
- 疎結合
といった設計原則が「AIの変更影響範囲を制御するため」に再び重要になっていると感じています。