「テストしやすく」と頼むと、設計はどう変わるか
遊びをつくる執筆にClaudeを使用しています。

三目並べをAIに作ってもらい、遊んでみたら勝敗の表示がおかしかった場面を考えてみます。原因は勝敗判定にありそうですが、判定の処理がクリックの処理や画面の書き換えと一続きになっていると、判定だけを取り出して確かめられません。盤面を作り直し、何度もクリックして再現するしかなくなります。 「テストしやすく作ってください」という一行を依頼文に足すと、この状況は変わるのでしょうか。変わるとすれば、どの部分でしょうか。関数の数が増えるのか、画面操作と判断の分離が進むのか、乱数の扱いが変わるのかを考えます。 この記事は結果を報告するものではなく、読者が自分で答えを出すための実験の設計書です。同じ仕様で依頼文を一行だけ変え、出てきたコードの関数の分け方を数えて比べます。
確かめる問いと、比べる二つの依頼文
問いは「依頼文の末尾に『テストしやすく』という一行を足すと、生成されるコードの関数の分け方は変わるか」です。「分け方」は、次の三つの観点に分けて見ます。
- 関数の数と、最も長い関数の長さ
- 画面操作(DOM)、乱数、時間に触れる関数がどれだけ散らばっているか
- 勝敗判定やCPUの手選びが、盤面などを引数で受け取って値を返す形になっているか
題材は三目並べにします。判定、CPUの手選び(乱数)、画面の更新、リセットがすべて入るので、分け方の差が出やすい仕様です。
比べる依頼文は次の二つです。
- 条件A:「三目並べを、1つのHTMLファイルで作ってください。プレイヤーが○、CPUが×で、CPUは空きマスからランダムに選びます。勝ち・負け・引き分けを表示し、リセットできます。」
- 条件B:条件Aの全文に、末尾の一行「テストしやすいように作ってください。」だけを足したもの
AとBの差は、この一行だけにします。語順の調整や言い換えを混ぜると、何が効いたのか分からなくなります。
準備と手順
準備するものは次のとおりです。
- 使うAIとそのモデル設定を一つに固定します。実験の途中で切り替えません。
- 新しい会話を10回分開ける状態にします。前の会話の履歴が残っていると、条件が揃いません。
- 生成コードを保存するフォルダを、条件ごとに分けて作ります(A-1〜A-5、B-1〜B-5)。
- 次の節にある数え方の補助ページを、自分のブラウザで開ける場所に保存します。
手順は次の順で進めます。
- 条件Aの依頼文を新しい会話に貼り、出てきたHTMLを保存します。これを5回、毎回新しい会話で行います。
- 条件Bも同じように5回行います。A→Bの順に固めず、A、B、A、Bと交互に行うと、時間帯などの偏りを減らせます。
- 保存した10本をブラウザで開き、実際に遊べるか確認します。最後まで遊べないものは、記録票で「動作不可」と印を付け、集計から外すか別集計にします。
- 動いた作品ごとに、記録票を埋めます。
記録票と、数え方の補助コード
1本につき1行、次の項目を記録します。
- 関数の数、記録の仕方:補助ページの出力の行数
- 最長関数の行数、記録の仕方:補助ページの出力の最大値
- DOMに触れる関数の数、記録の仕方:出力の「依存」に「DOM」とある関数の数
- 乱数に触れる関数の数、記録の仕方:同じく「乱数」の数
- 判定関数の引数、記録の仕方:勝敗判定が盤面を引数で受け取り値を返すなら「引数」、外にある盤面を直接読むなら「外部参照」
- 乱数の差し替え、記録の仕方:CPUの手選びで、乱数の関数や値を引数などで外から渡せるなら「可」、不可なら「不可」
- 呼び出し可否、記録の仕方:ブラウザのコンソールから判定関数を直接呼べれば「可」。即時関数やモジュールの中に閉じていて呼べなければ「不可」
関数の数や行数は補助ページで数えます。引数の形や差し替えの可否は、コードを読んで判断する項目です。
補助ページは、生成されたコードを貼ると、関数ごとの行数と依存を出力します。保存して単独で開ける最小構成です。
<textarea id="src" rows="10" cols="60"></textarea>
<button id="run">数える</button>
<pre id="out"></pre>
<script>
const deps = {DOM:/document\.|querySelector|getElementById/, 乱数:/Math\.random/, 時間:/Date\.now|new Date|setTimeout|setInterval/};
function findFunctions(code) {
const re = /function\s+([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{|(?:const|let)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*\{/g;
const list = [];
let m;
while ((m = re.exec(code))) {
let depth = 1, i = re.lastIndex;
while (i < code.length && depth > 0) {
if (code[i] === "{") depth++;
if (code[i] === "}") depth--;
i++;
}
list.push({ name: m[1] || m[2], body: code.slice(re.lastIndex, i - 1) });
}
return list;
}
function report(code) {
return findFunctions(code).map(f => {
const lines = f.body.split("\n").filter(l => l.trim()).length;
const hit = Object.keys(deps).filter(k => deps[k].test(f.body));
return `${f.name}: ${lines}行 / 依存: ${hit.join("・") || "なし"}`;
}).join("\n") || "関数が見つかりません";
}
document.getElementById("run").onclick = () => {
document.getElementById("out").textContent = report(document.getElementById("src").value);
};
</script>この補助ページは粗い数え方をします。波括弧のない式形式のアロー関数や、async付きの関数、オブジェクトのメソッド、文字列の中の波括弧は正しく拾えません。「依存: なし」と出ても、関数の外の変数を読み書きしていないことまでは保証しません。数えた結果は目安とし、疑わしい作品は目で読み直してください。
最後に、判定関数を直接呼ぶ小さなテスト問題を二つ用意します。呼び方は作品ごとに違うので、関数名や引数の形は読んで合わせます。
- 問題1:上の段の3マスがすべて○で、ほかが空の盤面。○の勝ちと判定されるべきです。
- 問題2:1段目が左から○・×・○、2段目が○・×・×、3段目が×・○・○で、9マスすべてが埋まった盤面。どの段・列・斜めも揃っていないので、引き分けと判定されるべきです。
各問題をDOMなし、コンソールだけで呼べたかを記録票に書きます。
結果の読み方
5本ずつの数字が並んだら、平均よりも、まず二つの条件の範囲が重なっているかを見ます。
- 範囲が大きく重なっている場合。 一行の効果は、この5回では見分けられません。同じ条件の中でも作品ごとのばらつきがあるからです。回数を増やすか、別の仕様で繰り返します。
- Bのほうが関数の数が多く、DOMに触れる関数の数が少ない場合。 関数が増えただけでなく、画面操作と判断が分かれた可能性があります。判定関数の引数の欄が「引数」に寄っているかを合わせて確認します。
- Bのほうが関数の数は多いが、DOMに触れる関数の数は同程度の場合。 細かく刻んだだけで、画面と判断の分離は進んでいない可能性があります。呼び出し可否の欄が「不可」のままなら、数が増えても確かめやすくはなっていません。
- 乱数の差し替えがBで「可」に寄っている場合。 CPUの手選びを固定した検証がしやすくなる方向の変化です。ただし5本の傾向であり、依頼文の効果だと断定はできません。
- ほとんど差が出ない場合。 「テストしやすく」だけでは指示が抽象的で、具体的な形(判定は盤面を引数で受け取る、乱数は外から渡す)までは伝わらなかったと読めます。その場合は条件Cとして、Bの一行を「画面の更新と勝敗判定を分け、判定は盤面を引数で受け取る関数にしてください」のような具体的な指示に置き換え、同じ手順で5回行って比べます。
どの読み方でも、関数の数だけで良し悪しを決めないでください。多ければ良い設計とは限りません。見るべきは、判断の部分が画面や乱数から切り離されて単体で呼べるかどうかです。
起こりやすい誤り
- 一行以外の差が混ざる。 会話の履歴が残っていた、途中でモデルや設定を変えた、条件ごとに仕様の書き方を少し直した、といったことで比較が崩れます。
- テストコードの有無と混同する。 条件Bは「テストを書いて」と頼んでいません。生成物にテスト関数が付いてきた場合は、付いてきたこと自体を別の項目としてメモし、関数の数には含めるか除くかを決めて、10本すべてに同じ扱いを適用します。
- 動かない作品を混ぜる。 動かない作品の関数の分け方を数えても、比較の役に立ちません。動作不可は別に集計します。
- 補助ページの数を信じすぎる。 上で触れたとおり、拾い漏れがあります。条件ごとに同じ見落とし方をするとは限らないので、差が小さいときほど目で確かめます。
- 5本で結論を出しすぎる。 5本ずつの比較は、傾向をつかむための目安です。別のモデルや別の題材でも同じとは言えません。
- 記録を後から整える。 先に項目と判断基準を決め、見てから基準を変えないようにします。
次の行動
まず条件AとBを5回ずつ生成し、補助ページで関数の数と依存を数えて記録票を埋めてください。差が見えなかったときは、一行を具体的な指示に替えた条件Cを追加し、同じ手順でもう一度比べます。自分の作るゲームに合わせて、題材を三目並べから置き換えても構いません。

