バイブコーディングの限界と言語移行(2)|マイクロサービス分割という誘惑――「疎結合にすれば解決する」は本当?

バイブコーディングの限界と言語移行(2)|マイクロサービス分割という誘惑――「疎結合にすれば解決する」は本当? — バイブコーディング, マイクロサービス, 疎結合 AIテキスト

こんにちは、パレイド思想部です。

前回、バイブコーディングが10万行規模で破綻する問題を書きました。

今回は、この問題に対する「正攻法」に見えたアプローチ——マイクロサービス分割——がなぜうまくいかなかったかの話です。

本記事はローカル LLM による自動執筆パイプラインで生成されました。現段階ではクラウド AI(Claude 等)の補助や人間の編集が介在していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。

「分ければ管理できる」という直感

10万行のモノリスが手に負えないなら、3つの3万行サービスにすればいい。各サービスは独自の責務を持ち、API で通信し、独立してデプロイできる。ソフトウェアエンジニアリングの教科書にも書いてある定石です。

私たちも同じことを考えました。動画制作支援システムを3つのサービスに分割する計画です。

サービス責務通信方式
Base(共通基盤)LLM クライアント、ファイルフォーマット、素材パッケージ管理、設定管理ライブラリとしてインポート
Creator(制作)素材生成、キャラクター設定、画像生成。REST APIHTTP
Server(配信)配信サーバー、音声合成、検索、オーバーレイ。WebSocketHTTP + WS

各サービスの境界には 仕様書 を置きました。ファイルフォーマット仕様(.pkg パッケージの構造)、REST API 仕様、要件 ID を振った GUI 仕様。仕様書だけで数十ファイル。まさに「正しいやり方」に見えました。

うまくいった部分

公平に言えば、分割には長期的な視点で多大なメリットもありました。

1. 責務の明確化

「この機能はどのサービスの仕事か?」が明確になりました。素材生成のバグは Creator を見ればいい。配信中の音声問題は Server を見ればいい。AI に質問するときも「Creator のこのファイルを見て」と範囲を絞れます。

2. ファイルフォーマットの標準化

それまで雑多に扱っていた、プロジェクトで使う素材、画像ファイル・動画ファイル・テキストファイルやそのメタデータのJSONといったパーツをまとめ、zipベースのパッケージ形式を仕様としてで定義したことで、サービス間のデータ受け渡しが明文化されました。マニフェストによるハッシュ検証、メタデータスキーマ——これらは分割を試みなければ整備されなかったでしょう。

3. テストの分離

各サービスが独自のテストスイートを持つことで、部分的なテストが可能になりました。また、暗黙的に前後のテストに依存してたテストデータやセッション状態を標準化し、テストケース間の依存性を減らすことで 2,000 件全部を毎回走らせる必要がなくなったのは、開発体験の改善でした。

しかし、失敗した

メリットを享受しつつも、全体としては失敗でした。理由を具体的に書きます。

失敗1:「共通基盤」が肥大化する

Base は「共通ライブラリ」として設計しました。LLM クライアント、ファイル操作、設定管理——どのサービスからも使う機能をここに集約する方針です。

問題は、何でも「共通」に見えてくることです。

「このユーティリティ、Server でも使うから Base に移動しよう」
「この定数、Creator と Server で共有するから Base に置こう」
「この型定義、3箇所で使うから Base に……」

結果として Base が肥大化し、本来独立しているはずの Creator と Server が Base の変更に強く依存するようになりました。Base のインターフェースを変えると、両方のサービスのテストが壊れる。分割したのに密結合が消えていない。

これは「分散モノリス」と呼ばれるアンチパターンそのものです。マイクロサービスの複雑さ(ネットワーク、デプロイ、API バージョニング)を引き受けつつ、モノリスの密結合も残るという最悪の組み合わせ。

ここに、人間のアーキテクトと AI の根本的な違いが現れます。

経験のあるエンジニアは、初期設計の段階で「このモジュールは将来的に分離する可能性がある」「このデータ形式は後方互換性を考慮すべき」といった判断を行います。目の前のコードを動かすことだけでなく、半年後の拡張性やチームの運用コストといった目的関数を持っているからです。

AI の目的関数は違います。AI は「この会話の中でタスクを完了すること」に最適化されています。動くコードを返せば報酬を得る。設計の妥当性を 3 ヶ月後の視点から検証するインセンティブは、学習上存在しません。2025年の研究「Vibe Coding in Practice: Flow, Technical Debt, and Guidelines for Sustainable Use」は、この構造を「フロー・デット・トレードオフ」と呼んでいます。AI による素早いコード生成が、アーキテクチャの一貫性の欠如やセキュリティ上の脆弱性として「技術的負債」が蓄積していく現象です。

結果として、AI が書いたコードはプロトタイプ段階のアーキテクチャをそのまま引きずります。最初に dict で雑に組んだデータ構造が、そのまま 10 万行規模まで生き残る。最初に書いた Flask の 1 ファイルアプリの構造が、機能追加のたびにコピーされて肥大化する。人間のアーキテクトなら「そろそろ設計を見直そう」と判断するタイミングで、AI は既存の構造の延長線上に機能を積み続けます。

そしてある程度大きくなってしまうと、フレームワークの入れ替えや依存関係の整理といった抜本的な変更は——これは人間にとっても難しいことですが——AI にとっても同様に困難です。2025年の調査では、AI コーディングツールに対して「ほぼ正しいが完全ではない提案」が最大の不満だと 45% の開発者が回答しています。局所的には妥当に見える変更が、全体のアーキテクチャとの整合性を欠く。この問題は、コードベースが大きくなるほど深刻になります。

失敗2:設定ファイルが分散する

3つのサービスにそれぞれ設定ファイルが必要になりました。

config/
├── environment.yaml    ← 環境ごとの設定
├── services.yaml       ← サービス間の接続情報
└── capacity.yaml       ← リソース制限

さらに各サービスにも固有の設定があります。Creator のポート番号、Server の音声合成設定、Base の LLM エンドポイント。1つのシステムを動かすために4箇所の設定を整合させる必要がある。

設定だけではありません。モノリスのときは環境が1つで済んでいました。Python の venv が1つ、.env が1つ、すべてが同じプロセスで動く。テストも本番も同じ環境で、区別する必要がなかった。

分割した途端、それぞれのサービスに独立した環境が必要になります。Creator 用の venv と Server 用の venv を分けるのか、共有するのか。本番環境とテスト環境の切り分けも避けられません。テスト時に Creator が本番の Server に接続してしまう事故を防ぐには、環境変数で接続先を切り替える仕組みが要る。

git の管理も複雑になります。モノレポにするのかリポジトリを分けるのか。分ければサービス間の変更を同時にリリースするとき、複数リポジトリにまたがるプルリクエストが発生する。モノレポにすれば CI の設定が複雑になる。バックアップや復元の単位も「プロジェクト全体を丸ごと」では済まなくなります。

少人数でAIを利用した開発を回すのに、この運用コストは重すぎます。中規模以上のチームで分担するなら意味がありますが、少人数や個人でサービスを開発している場合、設定の同期も環境の分離も git 運用も、本来不要なはずの「調整」すべてが純粋なオーバーヘッドです。

失敗3:バイブコーディングがさらに難しくなる

分割の目的の一つは「AI が扱えるサイズにすること」でした。しかし実際には逆効果でした。

バイブコーディングで「配信中にキャラクターの表情が反映されない」というバグを直そうとすると:

  1. Server の表示ロジックを確認
  2. Base.pkg ロードを確認
  3. Creator の素材生成ロジックを確認

3つのリポジトリ(もしくは3つのサブディレクトリ)を横断して調査する必要があります。AI に「3つのサービスにまたがるバグを調べて」と頼むと、コンテキストに3サービス分のコードを詰め込むことになり、分割前と同じ問題で、AIが途中で本来のゴールを見失います

しかし本質的な原因は、コンテキストの物理的な制約だけではありません。AI はシステム全体の俯瞰図を持っていないのです。

人間の開発者は、たとえコードを読んでいなくても「Creator で生成したデータが Server で使われる」という全体像を頭の中に持っています。だからバグの原因が別サービスにある可能性を直感的に疑える。AI にはこの俯瞰図がありません。

「仕様書を読んでから作業して」と指示すれば解決するように思えます。しかし実際には、仕様書を読ませても AI は長い会話の中で初期の指示を徐々に忘れていくことが知られています。仕様書の内容がコンテキストの奥に埋もれると、AI は目の前のファイルだけを見て「局所的に正しい」修正を行います。

その結果、こんなことが頻発します。

  • Server 側のバグを直すとき、Base の共通関数を使わず個別実装で「完了」と報告する
  • Creator 側のデータ形式を変えたのに、Server 側の読み込みロジックを更新せず「Creator の修正は完了しました」と返す
  • 仕様書に定義されたインターフェースを無視して、目の前のテストが通る実装を優先する

AI にとって「完了」とは「目の前のタスクが動くこと」であり、「システム全体の整合性が保たれること」ではありません。サービスが分割されていると、この「自分のスコープ外は知らない」傾向がサービス境界で増幅されます。モノリスなら少なくとも同じディレクトリ内のコードが目に入る可能性がありますが、別サービスのコードは文字通り視界の外なのです。

失敗4:仕様書の維持コスト

API 仕様書、フォーマット仕様書、GUI 仕様書——これらを最新の状態に保つ作業は、思った以上に重いものでした。

実装を変更 → 仕様書を更新 → テストを修正 → 別サービスの仕様書も更新
→ 別サービスのテストも修正 → ……

仕様書があること自体は良いのですが、3つのサービス × 仕様書・テスト・実装の三点同期 は、一人の開発者が維持できるキャパシティを超えていました。

しかも、仕様書は書くだけでは機能しないのです。

まず、仕様書は人間がレビューしなければ品質が上がりません。AI に「仕様書を書いて」と頼むと、実装から逆算した記述になりがちです。「なぜこの設計か」「どの選択肢を検討して棄却したか」——こうした設計の意図やトレードオフは、アーキテクトが頭の中に持っている知識であり、コードからは読み取れません。

次に、仕様書が増えると古い版と新しい版が共存する問題が起きます。v1 のフォーマット仕様を廃止して v2 に移行したつもりでも、古い仕様書のファイルがリポジトリに残っていれば、AI はそれを見つけて使います。仕様書ファイルは時間とともに静かに陳腐化し、AI が参照した時点でその内容が正しいかどうかの保証がありません。削除し忘れた1ファイルが、AI を古い設計に誘導するのです。

そして最も根本的な問題は、仕様書の体系を維持するにはプロマネやアーキテクトの俯瞰が不可欠だということです。個々の仕様書は局所的には正しくても、全体の整合性——企画→要件→仕様→設計という階層構造、機能間の優先順位、将来の拡張方針——は誰かが全体を見渡して初めて保てます。Thoughtworks は2025年に「Spec-Driven Development」という概念を提唱し、仕様を実装の前に置く開発手法を整理しましたが、その中でも「仕様のドリフト(乖離)とハルシネーション(幻覚)は本質的に避けがたい」と指摘しています。

AI は「この関数をこう直して」という実装レベルの指示には忠実に応えます。しかし「この機能はフェーズ2のリリースに含めるべきか」「このAPIの変更は下流の3つの画面にどう影響するか」といった要件や設計レベルの判断は、仕様書を読ませても伝わりません。AI にとって仕様書は「今読んでいるテキスト」であり、開発プロジェクトの時間軸の中でどこに位置づけられるかという文脈は持てないのです。

失敗5:AIが書いたコードが移行を阻む

サービス分割の失敗に気づき、構造を整理しようとしたとき、もう一つの壁にぶつかりました。AI が書いたコード自体が、移行や再設計を困難にしていたのです。

規約を定め、設計方針を CLAUDE.md に明記し、何度も指示してきたにもかかわらず、コードベースにはこんなものが蓄積していました。

# 1. グローバル変数とハードコード
BASE_URL = "http://localhost:8770"  # config から読むべき値が直書き
TIMEOUT = 30                        # 定数ファイルに定義済みなのに再定義
model_cache = {}                    # モジュールレベルの可変グローバル

# 2. 古い仕様へのサイレントフォールバック
def load_package(path):
    try:
        return load_v2_format(path)  # 新フォーマット
    except Exception:
        return load_v1_format(path)  # 廃止したはずの旧フォーマット
    # → テストは通る。しかし v1 で読んだデータは
    #   フィールドが欠けていて、後段で謎のバグを起こす

# 3. 共通処理のコピペ+微改変
def upload_media(client, file_path):
    # lib/wp_api/media.py に同じ関数があるのに
    # 「リトライ回数だけ変えたい」ために丸ごとコピー
    for attempt in range(5):  # 共通版は3回
        try:
            return client.post("/media", files={"file": open(file_path, "rb")})
        except Exception:
            time.sleep(attempt * 2)

特に厄介なのは2番目の「古い仕様へのフォールバック」です。廃止した v1 フォーマットのサポートを消したはずなのに、AI が「安全のため」と称してフォールバックを仕込む。テストは通る。load_package は値を返す。しかし返ってくるデータの構造が v1 と v2 で異なるため、数ステップ先で原因不明のエラーが出る。

口を酸っぱくして「フォールバックを書くな」と指示しても、コンテキストが長くなると AI は忘れます。あるいは別のセッションで同じファイルを触ったとき、過去の指示が引き継がれない。結果として、規約違反のコードが「動いている」状態で静かに蓄積していきます。

分割をやめて統合しようにも、まずこれらのクリーンナップが必要でした。グローバル変数を config 経由に直し、フォールバックを剥がし、コピペされた共通処理を本来の共通関数に戻す——移行作業の前に「AI が散らかした部屋の片付け」が発生するのです。皮肉なことに、この片付けもまた AI に頼むと、別の場所で同じパターンを再生産するリスクがありました。

教訓:少人数での大規模開発とマイクロサービス

この経験から得た教訓をまとめます。

マイクロサービスは「人間のチーム間の境界」のためにある。 サービスを分割する主な理由は、異なるチームが独立して開発・デプロイできるようにすることです。少人数で開発しているなら、むしろ境界を明確化するコストが嵩むデメリットが大きい。

「コードが大きい」はサービス分割の理由にならない。 コードが大きいなら、まずモジュール分割(ディレクトリ構造の整理、明確な import 規約)を試すべきです。HTTP API を境界にする必要があるかは別の問題です。

バイブコーディングとの相性は最悪。 AI が横断的に読めないサービス境界は、AI の能力を制限します。API 仕様書を読ませることはできますが、実装とのズレを AI が検出するのは困難です。

シンプルな話、マイクロサービス化というアーキテクチャや、それを管理するための既存のプロマネ・設計は人間前提であり、AIにはハマらない。ただこれは AI 特有の問題ではなく、マイクロサービスはもともと人間にとっても高度な設計思想であり、AI は結果的に、既存の難しさを露骨に表面化させる効果があったと言えます。

大規模運用ではマイクロサービスが効くことはあります。しかし、AIに人的工数のレバレッジを期待する文脈である個人開発や少人数開発では、その運用上の利点を享受する前に、複雑性のコストが先に立ちやすい。特にAI支援開発では、サービス境界がそのままコンテキスト境界になり、横断的修正が難しくなると言えます。

次回は、この行き詰まりに対して Claude が提案した「意外な解決策」について書きます。

タイトルとURLをコピーしました