マガジン

「一番簡単な方法で」と「一番確実な方法で」で、実装はどう変わるか

遊びをつくる

執筆にClaudeを使用しています。

コード・絵・音を表す三つの作業領域を、中央の手と黄色い駒がつなぐ版画
記事のテーマをもとに制作したAI生成イラスト。

ゲームの最高得点を保存する処理を、AIに頼む場面を考えてみます。依頼文の末尾に「一番簡単な方法で」と添えるのと、「一番確実な方法で」と添えるのとでは、返ってくるコードのどこが変わるのでしょうか。行数だけでしょうか。保存先の選び方、壊れたデータへの備え、説明文の書き方まで動くのでしょうか。 この記事では、その答えを書きません。変わるかどうかも、変わる向きも、読者が自分の環境で確かめる必要があります。ここにあるのは、一行の差だけを比べるための実験の設計書です。準備するもの、手順、記録の取り方、結果の読み方、間違えやすい点を順に示します。 題材は最高得点の保存です。短いコードで済む一方、「簡単」と「確実」が分かれやすい処理でもあります。保存先を選ぶ判断、保存できない場合の扱い、壊れたデータを読んだときの挙動と、確実さを左右する分岐点が小さな範囲に集まっています。

この記事の目次
  1. 問いを三つに分けておく
  2. 準備するもの
  3. 手順
  4. 記録の取り方
  5. 壊しテストの作り方
  6. 結果の読み方
  7. 追試:置かれる環境を伝える
  8. 誤りやすい点
  9. 次の行動

問いを三つに分けておく

「差」を漠然と見ると、読んだ印象で決めてしまいます。先に確かめる点を三つに分けておきます。

  1. 量の差:コードの長さ、分岐の数、説明文の長さに違いが出るか。
  2. 設計の差:保存先の選択、データの形式、失敗時の扱いに違いが出るか。
  3. 動作の差:壊れた状況に置いたとき、実際に違う結果になるか。

見落としやすいのは、1と3が別物だという点です。長いコードが壊れにくいとは限りませんし、短いコードが必ず脆いとも限りません。量は読めばわかりますが、動作は動かさないとわかりません。この実験では、3を主な評価にします。

準備するもの

  • 使うAIは1つに決め、モデルや設定も固定します。途中で変えると、差の原因が一行なのかモデルなのか区別できなくなります。
  • 毎回、新しい会話を開始します。前の会話が残っていると、以前の出力に引きずられます。
  • ブラウザと、テキストエディタ。
  • 記録用の表(後述)。
  • 壊しテスト用のHTML(後述、約30行)。

依頼文の作り方

共通部分は次の一文に固定します。

ブラウザで動く1ファイルのHTMLゲームで、最高得点を保存して次回起動時に表示する処理を書いてください。外部ライブラリとサーバーは使いません。

この後ろに、条件ごとに一行だけ足します。

  • A、末尾に足す一行:一番簡単な方法で。
  • B、末尾に足す一行:一番確実な方法で。
  • C、末尾に足す一行:(何も足さない。対照)

Cを入れる理由は、AとBがそれぞれ無指定からどちらへ動いたのかを見るためです。AとBだけだと、二つの差は見えても、どちらが標準から離れたのかはわかりません。

共通部分には、保存先の指定を書きません。「localStorageで」と指定すると、保存先の選択という設計判断を観察できなくなるためです。

手順

  1. 条件A・B・Cそれぞれについて、新しい会話で依頼文を送ります。
  2. 返ってきたコード全体と、コード以外の説明文を、そのまま別ファイルに保存します。手直しはしません。
  3. 同じ条件で、新しい会話を使って合計3回繰り返します。合計9つの出力ができます。AIの出力は毎回少しずつ違うので、1回だけでは条件の差かどうかを区別できません。
  4. 9つの出力を、条件名を伏せたファイル名(例:01〜09)に改名し、対応表は別に控えておきます。評価時の先入観を減らすためです。
  5. 各出力を、下の壊しテストで動かします。
  6. 全部終わってから、対応表を見て条件ごとにまとめます。

記録の取り方

出力1つにつき1行の表を作り、次の項目を埋めます。

  • コード行数、記録の単位・方法:空行とコメントを除いた行数
  • 保存先、記録の単位・方法:localStorage、IndexedDB、Cookie、保存しない、など
  • try/catch の数、記録の単位・方法:個数
  • 読み込み時の検証、記録の単位・方法:型・範囲・形式を確認しているか(あり/なし)
  • 保存形式の版管理、記録の単位・方法:バージョン番号を持たせているか(あり/なし)
  • 説明文の注意点、記録の単位・方法:保存できない場合や制限に触れているか(あり/なし)
  • 壊しテスト、記録の単位・方法:6ケースそれぞれの結果(後述)
  • 組み込みの手間、記録の単位・方法:自分のゲームに貼って動くまでにした手直しの数

「あり/なし」の判定は、迷ったら「なし」とせず、備考欄に理由を書きます。判定基準を途中で変えると全体が崩れるため、最初の1つを見て基準を決めたら、以後は動かしません。

壊しテストの作り方

確実さは、コードを読んだ印象では決まりません。保存された値が壊れていたとき、保存機能が使えないときに、読み込み処理が落ちずに妥当な値を返すかどうかを動かして見ます。次のHTMLを作り、load の中にAIが書いた「保存値を読み込む処理」を貼ります。

html
<!doctype html>
<meta charset="utf-8">
<pre id="out"></pre>
<script>
const KEY = "best"; // 出力のキー名に合わせます
function load() {
  // 出力の「読み込み処理」をここに貼ります
}
const cases = {
  "未保存": () => localStorage.removeItem(KEY),
  "壊れたJSON": () => localStorage.setItem(KEY, "{bad"),
  "文字列": () => localStorage.setItem(KEY, JSON.stringify({ best: "abc" })),
  "負の値": () => localStorage.setItem(KEY, JSON.stringify({ best: -5 })),
  "巨大な値": () => localStorage.setItem(KEY, "1e308"),
  "保存不可": () => Object.defineProperty(window, "localStorage", {
    get() { throw new Error("denied"); }, configurable: true })
};
const out = document.getElementById("out");
for (const [name, setup] of Object.entries(cases)) {
  let line;
  try {
    setup();
    const r = load();
    line = typeof r + ":" + String(r);
  } catch (e) {
    line = "例外: " + e.message;
  }
  out.textContent += name + " → " + line + "\n";
}
</script>

使うときの注意が四つあります。

  • 出力が localStorage 以外を使っている場合は、cases の準備処理をその保存先に合わせて書き直します。書き直した内容は記録に残します。
  • 出力が保存形式として数値だけ、あるいは {best: 数値} 以外の形を使っている場合は、各ケースに入れる値を形式に合わせます。
  • load が非同期(Promiseを返す)なら、await を使う形に変えます。
  • 「保存不可」の再現方法はブラウザによって動作が異なる場合があります。うまく再現できなければ、ブラウザの設定でサイトデータを禁止する方法に替え、その旨を記録します。

6ケースの結果は「例外で止まった」「値を返した(その値)」の二分類で記録し、返した値には「妥当(0以上の有限な数)」「不適切」の判定を付けます。

結果の読み方

9つの出力の記録がそろったら、条件ごとに三つの視点で整理します。

量で見る

行数、try/catch の数、説明文の長さを、条件ごとに3回分並べます。3回のばらつきが条件間の差より大きいなら、量には安定した差がないと読みます。

設計で見る

保存先、検証、版管理、注意書きの有無を条件ごとに数えます。たとえば3回中何回あったか、という数え方です。ここでも見るのは頻度です。1回だけ現れた特徴は偶然の可能性があります。

動作で見る

壊しテスト6ケースのうち、例外で止まらず妥当な値を返した数を出力ごとに数えます。この数が条件によって違うなら、一行の指示が実際の堅牢さに届いたと読めます。違わなければ、設計の差は見た目だけで、動作には届かなかったと読めます。

三つの視点が食い違うこと自体が、有用な発見になります。たとえば量は増えたのに動作が変わらなければ、「確実」という語が説明や記述を増やしただけだった、という読みができます。逆に量が変わらないのに動作が違えば、短いコードでも指示は効いていたことになります。

追試:置かれる環境を伝える

最初の実験では、遊ばれる環境を伝えていません。続けて、共通部分に次の一文を足した条件A'・B'を同じ手順で試します。

このゲームは、サイト上の sandbox 付き iframe の中で動きます。

アソバースでは、ゲームは sandbox 付きの iframe で実行されます。また、アップロードした作品の中の localStorage と sessionStorage は、一時メモリーとして動く互換処理です。記録を端末に残す前提の設計にはなっていません。恒久的な保存が必要な場合は、アソバースが自動で追加するSDKによる作品ごとのクラウドセーブ(JSONで128KiBまで)があり、利用の細部は現在の案内で確認します。

この追試で見るのは、環境の情報を足したときに、AとBの出力がどう動くかです。保存先が変わるのか、「この環境では保存が残らないかもしれない」という注意が説明文に現れるのかを、同じ記録表で比べます。サーバーやサービスワーカーに頼る案が出たら、アソバースの投稿では対象外なので、その出力は「環境に合わない」と記録します。

誤りやすい点

  • 差が一行以外に混ざる:依頼文をコピーするとき、つい言い回しを直してしまいます。共通部分は一字も変えずに使います。
  • 会話履歴の持ち越し:同じ会話で条件を続けて試すと、前の出力の影響を受けます。必ず新しい会話にします。
  • 1回で判断する:3回でもまだ少ない部類です。差が曖昧なときは、5回に増やすことを検討します。
  • 行数を確実さと同一視する:行数は材料の一つにすぎません。判断は壊しテストの結果を中心にします。
  • テストを出力に合わせて甘くする:通らないケースを「この出力の想定外」と除外すると、比較になりません。6ケースは全出力に同じように適用します。
  • 「簡単」を劣ったものと決めつける:簡単な方法は、小さなゲームでは十分な場合もあります。目的に対して妥当かどうかで見ます。
  • ゲームごとの要件を混ぜる:実際の自作ゲームに合わせて依頼文を変えると、再現できなくなります。まず題材を固定し、あとで自作ゲームに当てはめます。

次の行動

まず、依頼文の共通部分をテキストファイルに保存し、条件A・B・Cを3回ずつ、新しい会話で試してください。出力を伏せ名で保存して壊しテストにかけ、記録表を埋めたところで、量・設計・動作の三つの視点に分けて読みます。その後、追試の一文を足して、環境を伝えたときに出力がどう動くかを比べてください。