PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

AIのための特別なアーキテクチャはいらない:0→1開発で効く設計原則とガードレールを読む

この記事のキーポイント

まず結論:AI時代でも、設計の基本は変わらない

カミナシのエンジニアブログの記事が面白いのは、「AIのための特別なアーキテクチャ」を探した結果、​結局は昔から正しいと言われてきた設計原則に戻ってきたところです。

これはかなり重要な話だと思います。
AIがコードを書く時代になると、「AIが書きやすい構成」「AIが迷わない構成」が必要に見えます。けれど、実際には人間にとっても保守しやすい設計が、そのままAIにも効く。ここがこの記事の核心です。

筆者は新規プロダクトの0→1開発をしていて、短期のスピードと長期の保守性の両立を考えたそうです。0→1って、とにかく早く形にしたい。でも雑に作ると後で地獄を見る。ここでAIを活用するなら、なおさら「AIが暴れない設計」が必要になります。
その答えとして出てきたのが、​関心の分離・価値の高いテスト・依存方向の決定の3本柱でした。


1. 関心の分離:コードを“機能ごと”にまとめる

最初のポイントは、​関心の分離です。
簡単に言うと、​関連するコードを近くに置くということです。

たとえば「ユーザー登録」「請求処理」「進捗率計算」のように、機能ごとにフォルダを分ける。すると、その機能に関係するロジック、DBアクセス、API、UIなどがひとまとめになります。

なぜこれがAIに効くのか

記事では、AIが修正するときに関連ファイルがまとまっていると、​コンテキストに載せやすいと説明しています。
要するに、AIに「この機能を直して」と渡したとき、あちこち探し回らなくて済む。これはかなり納得感があります。

AIは“見えている情報”の中で仕事をします。ファイルが散らばっていると、そのたびに「どこが本体なのか」を探すコストが増える。人間でも面倒なのに、AIならなおさらです。
だから、​機能単位でまとまっている構成は、AIの迷子防止に効くわけです。

Feature-Firstという考え方

記事ではこれを Feature-First と呼んでいます。
機能ごとにディレクトリを切り、その中に

を置く構成です。

この構成のいいところは、​1つの関心事が1つの箱に入ることです。
個人的には、これはかなり実用的だと思います。よくある「レイヤー別(components, services, utils…)」の分け方は、最初はきれいでも、機能単位で追うとコードが横断しまくってつらくなりがちです。Feature-Firstは、その逆で「機能のまとまり」を優先していて、現場向きです。

Public API境界があるのも強い

各featureの index.ts には、外から使っていいものだけを export します。
内部のヘルパー関数やDB操作の詳細は隠す。

image_0003.png

これの何がいいかというと、​外部コードが内部実装に依存しにくくなることです。
つまり、featureの中をいくら整理し直しても、index.ts の外向けの形が変わらなければ、外のコードはほぼ影響を受けません。

これは地味ですが、保守ではめちゃくちゃ大事です。
内部を自由に触れる余地があると、AIはつい内部の細部に手を突っ込みがちです。公開口を絞っておくと、「触っていい面」が減るので、結果的に安全になります。


2. featureをまたぐときは、shared と routes で役割を分ける

Feature-Firstで気になるのは、「じゃあ複数機能にまたがる処理はどうするの?」という点です。
この記事はそこもちゃんと整理しています。

パターン1: 共通ロジックは shared/ に置く

複数featureで使う純粋な計算は、shared/lib/ に置く。
ここでのポイントは、​shared は features に依存しないことです。

つまり依存方向は

の一方通行。

これはかなり気持ちいい設計です。
共通部品って、便利だからこそ汚染しやすいんですよね。「とりあえず shared に置こう」が進むと、いつの間にか shared が何でも屋になって破綻しがちです。
だからこそ、​shared は“純粋な共通関数だけ”に絞るのが大事だと思います。

パターン2: ページ単位の組み合わせは routes/ が担当

複数featureのデータを組み合わせて1つのページを作る場合は、routes/ 層が受け持ちます。
ここはTanStack Startの構成を前提にしていますが、Next.jsの app/ でも似た考え方ができる、と記事では触れています。

ページ側で各featureのデータ取得関数を並列で呼び出し、集めた結果をUIにする。
つまり、​feature同士は互いを知らない
どのfeatureを組み合わせるかは、ページの責任です。

これは役割分担としてとてもきれいです。
featureは「自分の仕事だけ」に集中し、routesは「ページとしてどう組み立てるか」に集中する。
この分離があると、AIに対しても「featureはfeatureだけ触って」「ページで組み合わせて」と指示しやすい。地味ですが、かなり効きます。


3. 価値の高いテスト:内部ではなく“振る舞い”を守る

次の大きな柱はテストです。
記事は、単体テストの「数」より「質」が大事だと強く言っています。これは本当にその通りだと思います。

価値の高いテストとは何か

image_0004.png

記事では、Vladimir Khorikovの考え方を引きつつ、価値の高い単体テストには次の要素があると紹介しています。

この中で特に大事なのが、​リファクタリング耐性です。
要するに、コードの内部を整理し直しても、テストが壊れないこと。

ここ、かなり重要です。
テストが内部構造にべったり張り付いていると、少し整理しただけで大量に落ちる。すると人はテストを信用しなくなります。信用されないテストは、最終的に“うるさいだけの存在”になる。これは悲惨です。

出力値ベーステストが強い

そこで記事が採っているのが、​出力値ベーステストです。
これは、関数の内部がどう動いたかではなく、​入力に対して何が返ってきたかだけを見るテストです。

たとえば、進捗率を計算する純粋関数があれば、

のように、結果だけを確認する。

これならDB初期化もモックもいりません。
テストがシンプルで速い。しかも、内部実装を変えても振る舞いが同じならテストは通る。これはかなり気持ちいいです。

個人的には、テストは「実装を縛るもの」ではなく「振る舞いを守るもの」であるべきだと思っています。
実装を縛りすぎると、保守のたびにテストが敵になる。逆に、振る舞いだけを守るなら、テストは味方のままです。


4. FCISで、純粋関数とI/Oをきれいに分ける

出力値ベーステストを自然に書くには、テスト対象が純粋関数であるのが理想です。
そこで登場するのが Functional Core, Imperative Shell(FCIS)​ です。

これは簡単にいうと、

という分け方です。

この構成の意義

記事では、以下のように役割を切っています。

image_0005.png

これがなぜいいかというと、​ビジネスロジックのテストからI/Oを排除できるからです。
モックやDIコンテナがいらなくなる。テストが軽い。速い。壊れにくい。

これは派手ではないけれど、実際の開発ではかなり効く設計です。
AIは“それっぽいコード”を出すのは得意ですが、I/Oとロジックが混ざったコードを整理しながら正しく保つのは意外と苦手です。だから、あらかじめ純粋関数中心にしておくのは、AI時代にかなり理にかなっています。


5. AIに「守ってね」と言うだけでは足りない

ここからがこの記事のもう一つの面白いところです。
設計原則を決めても、​AIがそれを守るとは限らない
CLAUDE.mdAGENT.md にルールを書いても、あくまで確率的で、100%は保証できない。

これはすごく現実的な指摘です。
AIに「こうしてね」とお願いするのは大事ですが、お願いはお願いでしかない。
しかもレビューで後から気づいても、直しコストがかかる。できればもっと早く止めたい。

そこで静的解析を使う

記事の考え方はシンプルです。
ルールは文章ではなく、静的解析で機械的に強制する。​

静的解析とは、ざっくり言うと「コードを実行せずに、事前にルール違反を見つける仕組み」です。
これなら、違反があれば必ずエラーになる。曖昧さがない。ここが大きい。


6. dependency-cruiserで依存関係を縛る

この記事の中核ツールのひとつが dependency-cruiser です。
これは import の関係を見て、「どこからどこへ依存してよいか」をチェックするツールです。

記事では、設計思想を表すために13個のルールを定義しているそうですが、その中でも重要な4つが紹介されています。

ルール1: domain は純粋であること

domain/ から infrastructure/server/ を import したらエラー。
さらに、ORMのようなI/O系ライブラリにも直接依存しないようにしている。

これは、domainを「ビジネスルールだけの場所」に保つためです。
ここが崩れると、テストのしやすさも、設計の見通しも一気に悪くなります。

ルール2: feature同士は直接つながらない

あるfeatureから別のfeatureを直接importするとエラー。
必要なら shared/ 経由にする。

これは、feature間のべたべたした依存を防ぐルールです。
機能Aが機能Bの中身を知り始めると、境界がすぐ溶けます。
AIは特に「近いものを勝手に使う」傾向があるので、こういう禁止ルールはかなり有効だと思います。

image_0006.png

ルール3: 外部からは index.ts 経由だけ

routes/ などの外側から feature の内部ディレクトリを直接参照するとエラー。
つまり、外部は Public API だけを使う。

これはモジュールの境界を守るための、かなり分かりやすい仕組みです。
外から触れる面が少ないほど、AIも人間も迷いにくい。設計としてきれいです。

ルール4: shared/ は feature に依存しない

shared は共通部品置き場であって、特定featureの事情を知ってはいけない。
依存の向きは必ず features/ → shared/

このルールがないと、shared が“なんでもあり箱”になります。
私はこの手の shared が肥大化していく様子を何度も見てきたので、ここを明示的に縛るのはかなり賢いと思います。


7. knip と Biome で“地味だけど大事”な穴をふさぐ

dependency-cruiserだけでは足りないので、他のツールも組み合わせています。

knip:使われていないコードを見つける

AIがリファクタリングすると、不要になったexportやファイルが残りがちです。
人間が全部追い切るのは大変なので、​未使用コード検出knip で洗い出す。

これは実務でかなり効くはずです。
放置された不要コードは、見た目以上に設計を濁らせます。AIは特に「残骸」を置いていきやすいので、こういう掃除役は重要です。

Biome:lintとformatをまとめて担当

Biome は lint と format を担うツールです。
AIが出しがちな

などを検出します。

ここも地味に大事です。
AIは動くコードを出すことはあっても、型安全性を雑に削ってでも通そうとすることがある。だからこそ、Biomeのような機械的なチェックが必要になります。


8. Git hooksで“コミット前後”に止めるのが強い

image_0014.svg

記事では、これらのツールを lefthook で Git hooks に統合しています。
Git hooks というのは、コミットや push の前に自動で走る仕組みです。

どのタイミングで何をチェックするか

この分け方がとても実戦的です。
コミット時には速いチェックを置き、時間のかかるものは push 時に回す。
これ、かなりバランスがいいと思います。

大事なのは、​レビューで止めるのではなく、手元で止めることです。
エラーを早く返せば、AIも人間もすぐ修正できる。
これはまさに Shift-Left Testing の発想で、開発のなるべく左側、つまり早い段階で問題を見つけるやり方です。


9. この記事が伝えている本当のメッセージ

この記事の本質は、「AIのために特別な設計を発明しなくていい」ということだと思います。

必要なのは、AI専用の魔法ではなくて、

という、かなり基本的で、でもちゃんと効く設計です。

率直に言うと、これはとても健全です。
AIが入ると派手な話に引っ張られがちですが、実際に現場を助けるのはこういう地味なルールだったりします。
「AIにうまく書かせる」のではなく、​AIが雑に書いても壊れにくい構造を先につくる。この発想は、かなり現実的で強いです。


10. まとめ:AI時代こそ、設計を“やさしく厳しく”する

この記事を一言でまとめるなら、
良い設計をAIに任せるのではなく、良い設計をAIでも壊せないようにする、という話です。

そのために必要なのは:

個人的には、この記事は「AI開発の話」というより、​ソフトウェア設計の原則をもう一段実務寄りに引き戻した話として読むと面白いと思います。
AIがいてもいなくても、良い設計は良い。
ただ、AIがいる今は、その良さが以前よりずっと効く。そこが時代の変化なんだろうな、と思います。


参考: AIのための特別なアーキテクチャはいらない ― 0→1開発で実践した設計原則とガードレール - カミナシ エンジニアブログ

同じ著者の記事