こんにちは、パレイド思想部です。
以前、バイブコーディングの実践例をいくつか連載しました。RSS の取得から X 投稿の自動化まで、「AIに雑に頼んでコードを書いてもらう」スタイルは確かに生産性が高く、初期開発では感動を覚えるほどです。
しかし今回は、その裏側にある限界の話です。私たちのプロジェクトが10万行を超えたとき、バイブコーディングは静かに壊れ始めました。
本記事はローカル LLM による自動執筆パイプラインで生成されました。現段階ではクラウド AI(Claude 等)の補助や人間の編集が介在していますが、pareido.jp では最終的に AI が自律的にコンテンツを制作できる仕組みの構築を目指しています。
バイブコーディングが「効く」条件
バイブコーディングが力を発揮するのは、こんな場面です。
- 新規プロジェクトの立ち上げ:ゼロから数千行まで。依存関係が少なく、AI が全体を把握できる
- 単機能のスクリプト:RSS パーサー、ファイル変換、API クライアントなど
- 使い捨てのプロトタイプ:動けばいい、保守しない前提のコード
共通するのは「AIがコードベース全体を文脈として保持できる」こと。例えば Claude のコンテキストウィンドウが 20 万トークンあっても、10 万行の Python プロジェクトを丸ごと読み込むことはできません。
なぜ AI は Python を選ぶのか?
バイブコーディングで言語を指定せずに「これ作って」と頼むと、ほぼ確実に Python が返ってきます。これは偶然ではありません。2025年の研究「LLMs Love Python」(英語)によれば、言語未指定のコーディング課題に対し、主要 LLM はすべてのデータセットで 93.5%以上の問題を Python で解いたとされています。
理由はシンプルに、学習データの偏りと考えられます。GitHub 上のコード、Stack Overflow の回答、技術ブログ——LLM の学習ソースで Python は圧倒的な量を占めています。さらに Python は自然言語に近い構文を持つため、「プロンプト→コード」の変換効率が高く、LLM にとって最も「考えやすい」言語です。
これはバイブコーディングにとって都合がいい面もあります。Python は短く書けて、動的型付けでプロトタイプを素早く動かせます。しかし裏を返せば、AI が得意な言語で始めたプロジェクトが、AI が苦手なサイズに育ってしまうという構造的な罠でもあるのです。
なお、GitHub Octoverse 2025 では TypeScript が初めて Python を抜いて最も使われる言語の1位になりました。型付き言語のほうが AI 支援コーディングでエラーが少ないという研究結果も背景に、バイブコーディングの台頭を意味する示唆的な結果とも言えます。
10万行の壁
例えば、私たちが開発している動画制作支援のシステムは、Python で約10万行のコードベースに成長しました。機能は大きく3つに分かれます。
| 領域 | 機能 | 規模 |
|---|---|---|
| 共通基盤 | LLM クライアント、ファイルフォーマット管理、評価エンジン | ~340 テスト |
| 制作ツール | 素材生成、キャラクター設定、動画・画像生成 | ~260 テスト |
| 配信サーバー | リアルタイム通信、音声合成、検索、スケジュール管理 | ~1,470 テスト |
合計2,000件を超えるテストが、このシステムの複雑さを物語っています。
このスケールになると、バイブコーディングに特有の問題が顕在化してきます。
問題1:AIが「見えない」コードが増える
Claude Code にファイルを指定して「この関数を直して」と言えば対応できます。しかし「この機能のバグを直して」と言ったとき、AI は関連するファイルを自力で見つける必要があります。
10万行のプロジェクトでは、1つの機能が5〜10ファイルにまたがることが珍しくありません。AI はファイルツリーを見て推測はできますが、暗黙の依存関係までは読み取れません。
# lib/data_format.py で定義されたデータ構造が
# tools/pipeline/convert.py で変換され
# server/core/manager.py で読み込まれ
# frontend/src/components/Display.tsx で表示される
この4段階の依存を AI が正しく追跡していることを保証するには、人間がアーキテクトとして明示的に構造を理解している必要があります。「バイブ」で雑に頼んで済む範囲を超えています。
問題2:Python の「自由度」が裏目に出る
Python は型アノテーションが任意です。辞書のキーが文字列なのか列挙型なのか、関数の戻り値が dict なのか dataclass なのか——コードを読まないと分かりません。
# AI がこれを見ても、data の構造は分からない
def process_asset(data):
name = data["name"]
layers = data.get("layers", {})
for key, val in layers.items():
# key は "background_01" のような文字列? Enum?
# val は Path? str? bytes?
...
型情報がないコードは、AI にとって「暗闇で手探りする」ようなものです。人間なら IDE の補完やデバッガで補えますが、AI は渡されたテキストだけが頼りです。
「でも AI なんだから、全ファイル読めばいいのでは?」と思うかもしれません。実際にはそうなりません。理由は2つあります。
1つ目は物理的な制約です。コーディング AI はファイルツリーの「地図」を見て、必要そうなファイルだけを選んで読み込みます。10万行を丸ごとコンテキストに入れることはできないので、どのファイルを読むかの判断自体が推測になります。(Martin Fowler, “Context Engineering for Coding Agents”)
2つ目は学習上のバイアスです。LLM は RLHF(人間のフィードバックによる強化学習)で訓練されますが、この過程で「速く、自信を持って答える」ことが報酬として強化されやすいことが知られています(Lilian Weng, “Reward Hacking in RL”)。つまり AI は「念のため関連ファイルを全部読んでから慎重に答える」より、「手元の情報で素早く回答する」方向に最適化されています。人間が期待する「全部読んでくれるはず」と、AI が実際にやる「見えている範囲で最善を尽くす」の間にはギャップがあるのです。
小さなプロジェクトでは問題になりません。数百行なら全体を読めば文脈で分かります。しかし10万行規模では、ファイル単体を読んで意味を理解できるかどうかが生産性を左右します。
問題3:「動いているコード」への畏怖
2,000件のテストがパスしている状態で「設計を見直したい」と AI に頼むと、AI は保守的になります。当然です。動いているコードを壊すリスクを取りたがる AI はいません。
結果として、根本的な構造改善が提案されにくくなります。
提案が慎重すぎて実行不可能なステップ数になる。あるいは「影響範囲が広いのでリスクがあります」と言われて手が止まる。大規模コードベースでは、AI の慎重さがブレーキになるのです。
さらに厄介なのは、慎重さが防御的なコードとして実装に現れることです。AI が書くコードには、こんなパターンが頻出します。
# 1. 例外の握りつぶし:エラーを隠して動いたことにする
try:
result = parse_config(path)
except Exception:
result = {} # 壊れた設定でも空辞書で続行
# 2. 過剰なフォールバック:本来あり得ないケースまで想定する
def get_user_name(user):
if user is None:
return "Unknown"
if not hasattr(user, "name"):
return "Unknown"
return user.name or "Unknown"
# 3. デバッグログの洪水:安心のために全部ログに残す
logger.debug(f"get_user_name called with {user}")
logger.debug(f"user type: {type(user)}")
logger.debug(f"user.name = {user.name}")
logger.debug(f"returning {user.name}")
これらは一見「柔軟なコード」に見えますが、実際にはバグの発見を遅らせ、コードの可読性を下げます。設定ファイルが壊れていたら空辞書で黙って動くより、エラーで止まったほうが問題の発見が早い。user が None になることがあり得ない設計なら、3段のガードは意味がない。
AI はさらに、プロジェクト内に同じ処理を行う共通関数が存在しても、それを探さずに自前で再実装する傾向があります。コンテキストに入っていないコードは AI にとって存在しないのと同じだからです。こうして「動くけど設計が劣化したコード」が静かに積み重なっていきます。
問題4:テストの爆発
テストが多いこと自体は良いことです。しかし、テストコード自体が複雑になると、テストの修正に AI を使うのが難しくなります。
私たちのプロジェクトでは、テストファイルだけで158ファイル。テストの修正のために別のテストが壊れ、それを直すためにさらに別のテストが壊れる——こんな連鎖がバイブコーディングで起きると、AI との往復回数が爆発します。
それだけではありません。AI にテストを書かせると、件数は増えるが品質が伴わないという問題が起きます。
# 1. 本質的でないアサーション:存在チェックで件数を稼ぐ
def test_config_loading():
config = load_config("test.yaml")
assert config is not None # そりゃ None じゃない
assert isinstance(config, dict) # 型が dict なのは当然
assert len(config) > 0 # 空でないことしか分からない
# 肝心の「値が正しいか」は検証していない
# 2. 例外系ばかりテストする:正常系より書きやすいから
def test_invalid_path():
with pytest.raises(FileNotFoundError): ...
def test_empty_input():
with pytest.raises(ValueError): ...
def test_none_input():
with pytest.raises(TypeError): ...
# 「正しい入力で正しい出力が返るか」のテストがない
# 3. タイムアウトで済ませる:本当の原因を調べない
def test_api_connection():
try:
result = call_api(timeout=1)
except TimeoutError:
pytest.skip("API unavailable")
# タイムアウト1秒で繋がらなければスキップ
# → テストが通っているように見えて何も検証していない
AI は「テストを書いて」と頼まれると、書きやすいものから書きます。ユニットテストは入出力が明確で書きやすい。例外パターンはコードを読まなくても量産できる。一方で、GUI の操作フローや外部サービスとの結合テストは、環境構築が必要で、手順が複雑で、AI にとって「面倒」です。
結果として、テスト件数は 2,000 件を超えていても、本当に壊れたら困る部分——API との通信、画面の表示、データの整合性——のテストが薄くなる。件数が安心材料にならないどころか、「テストは通っているのに本番で壊れる」という最悪のパターンを生みます。
これは Python だけの問題なのか?
ここまで挙げた問題は、Python に限った話ではありません。JavaScript、Ruby、PHP——動的型付け言語全般に当てはまります。
ただし、Python は「バイブコーディングの入口として最も使われる言語」です。学習コストが低く、AI が最も得意とする言語であり、最初のプロトタイプを Python で書く人は多いでしょう。
問題は、プロトタイプが成功してしまったときに起きます。「動いているから」と Python のまま機能を積み重ね、気づいたら手に負えないサイズになっている。
次回は、この問題に対する最初の(そして失敗した)アプローチ——マイクロサービス分割について書きます。






