shinobis.comのすべてのPHPコードは、Claudeをコーディングパートナーとして書いた。過去6ヶ月のすべての関数、すべてのクエリ、すべてのPHPファイル。確認せずにコピー&ペーストすることはない。しかし検証後は出力を信頼する。ほとんどの場合、動作する。

3回動作しなかった。そして毎回、6ヶ月分のチュートリアルでは教わらなかったPHPの何かを学んだ。チュートリアルがそれらのトピックをカバーしていないからではない。本番でコードを壊すバグは、そのトピックについて読むだけでは到達できないレベルで教えてくれるからだ。

バグ1: require_onceはコードを実行する

auto-linkerは毎日午前6時にcronとして実行されるスクリプトだ。すべての公開済み投稿を読み、それらの間の接続を分析し、内部リンクを自動的に更新する。Claudeはこれを直接実行可能なスクリプトとして書いた。データベース接続を開き、投稿をクエリし、ファイルのグローバルスコープですべてを処理する。

問題はadmin.php内からauto-linkerの関数を使う必要が出たときに現れた。ファイルをrequire_onceした。PHPはファイルをロードし、ロードすることでグローバルスコープのすべてのロジックを実行した。管理パネルを開くたびに、auto-linkerがすべての公開済み投稿を処理した。ページのロードに8秒かかった。

修正前のコード:

// auto-linker.php
require_once __DIR__ . '/functions.php';

// admin.phpがrequire_onceするたびにこのロジックが実行された
$pdo = getDB();
$posts = $pdo->query("SELECT * FROM posts WHERE status = 'published'");
// ... admin.phpがロードされるたびにすべての投稿を処理

修正後:

// auto-linker.php
require_once __DIR__ . '/functions.php';

// 直接呼び出されたときのみ実行、インクルード時は実行しない
if (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) {
    $pdo = getDB();
    $posts = $pdo->query("SELECT * FROM posts WHERE status = 'published'");
    // ... cronが直接呼び出したときのみ実行
}

function addAutoLinks($postId, $langCode) {
    // ...
}

学んだこと: PHPのrequire_onceは関数を利用可能にするだけではない。ファイルのグローバルスコープにあるすべてのコードをロードし実行する。Claudeは私が依頼した通り、スクリプトを直接実行可能なものとして生成した。別のファイルが後でそれをインクルードすることを予測しなかった。

バグ2: 同じ名前の2つの関数

管理パネルは数週間にわたって成長した。あるセッションでClaudeにサイト設定を更新する関数を依頼した。handleUpdateLinksと名付けた。200行後の別のセッションで、投稿のリンクを更新する関数が必要になった。ClaudeはhandleUpdateLinksと名付けた。

PHPは同じ名前の2つの関数を許可しない。Fatal error: Cannot redeclare function。

修正は最初の関数をhandleUpdateSettingsにリネームすること。些細なことだ。興味深いのはなぜ起きたかだ。

Claudeはセッション間のメモリを持たない。2番目の関数を生成したとき、最初の関数の存在を知らなかった。その時点で論理的な命名規則で両方に名前を付けた。クラッシュは避けられなかった。同じファイルで数週間作業している開発者なら競合に気づいただろう。各セッションを新しい宇宙として見るClaudeにはできない。

学んだこと: AIは個々の関数の生成に優れている。複数のセッションにわたって成長するファイルの一貫性を維持するのは苦手だ。システムの整合性の責任はツールではなく人間にある。

バグ3: header()の前のecho

markdown-negotiator.phpには2つのモード がある。ホームページをMarkdownとして配信するか、個別の投稿をMarkdownとして配信するか。Claudeは同じファイルに両方のモードを書いたが、異なるパターンで。

ホームページにはechoを直接使用した:

if ($slug === null) {
    echo "# " . $siteName . "\n\n";
    echo "> " . $siteDesc . "\n\n";
    
    header('x-markdown-tokens: ' . $tokenEstimate);
    // クラッシュ: headers already sent
}

個別投稿には$output変数への連結を使用し、echoの前にheaderを送信した。正しいパターン。しかしホームページには適用しなかった。

修正後:

if ($slug === null) {
    $output = "# " . $siteName . "\n\n";
    $output .= "> " . $siteDesc . "\n\n";
    
    $tokenEstimate = (int)(str_word_count($output) * 1.3);
    header('x-markdown-tokens: ' . $tokenEstimate);
    
    echo $output;
}

PHPはすべてのheader()呼び出しがブラウザへの出力の前に行われることを要求する。Claudeはこのルールを知っている。ファイルの一つのセクションでは正しく適用した。同じファイルの別のセクションでは適用しなかった。矛盾は知識のエラーではない。注意のエラーだ。AIは自分のコードを自分自身に対してレビューしない。このバグを検出したのは、ファイルを監査中のGeminiだった。

3つのバグに共通すること

どれも構文エラーではない。3つすべてがコンテキストエラーだ。Claudeはrequire_onceがコードを実行することを知っている。PHPが重複関数を許可しないことを知っている。header()がechoの前に来ることを知っている。しかし、人間の開発者が同じプロジェクトで数ヶ月作業することで構築する完全なシステムビューを持っていない。

最初のバグはアーキテクチャコンテキストの欠如。2番目はセッション間のメモリの欠如。3番目は内部一貫性の欠如。

これらのバグのそれぞれが、ドキュメントを読むだけでは学べなかったPHPの具体的なことを教えてくれた。PHPを勉強したくてこれらを学んだのではない。本番でコードが壊れて、なぜかを理解しなければならなかったから学んだ。AIがコードを生成した。バグがそれを理解することを強制した。結果として、各バグの前よりも優れた開発者になった。

AIをコーディングパートナーとして使うことは学びを置き換えない。方向を変える。チュートリアルからではなく、ツールのミスから学ぶ。そして本番のバグはどんなチュートリアルよりも速く教えてくれる。