【学習】AIのコードで間違い探し ― 見抜く力を鍛える4問(AI時代のプログラミング学習 第7回)
【AI時代のプログラミング学習 第7回】
第1回|第2回|第3回|第4回|第5回|第6回|第7回(今ここ)|目次へ
第6回では、AIの使い方を3つの段階に分けて紹介しました。その中で、3〜4か月目の練習として「間違い探し」を挙げています。
今回は、その間違い探しを実際にやってみる演習編です。ここにあるのは、AIに頼んで書いてもらったコードです。どれも一見ちゃんと動きそうですが、間違いが隠れています。
1. この演習の進め方
AIは、動かないコードや危ないコードも、自信たっぷりに出してきます。この演習では、それを見抜く練習をします。
- まだ実行しないでください。まずはコードを読んで、あやしいところを探します。
- 見つけたら「どの行か」「なぜ間違いか(何が起きるか)」「どう直せばいいか」の3つをメモします。
- 全部見つけたと思ったら、実際に実行して、予想どおりか確かめます。
- 最後に「解答と解説」を開いて答え合わせをします。
各問題に「バグの数」を書いてあります。数が合うまで探してみてください。わからないときは、ヒントを見てOKです。
2. 問題1 平均点と最高点(C#コンソール・★)
AIへの頼み方:「点数の配列から、平均点と最高点を表示するプログラムを書いて」
バグの数:2つ
using System;
class Program
{
static void Main()
{
int[] scores = { 72, 85, 90, 64, 78 };
int sum = 0;
int max = 0;
for (int i = 0; i <= scores.Length; i++)
{
sum += scores[i];
if (scores[i] > max)
{
max = scores[i];
}
}
double average = sum / scores.Length;
Console.WriteLine($"平均点: {average}");
Console.WriteLine($"最高点: {max}");
}
}
ヒント
- 配列の要素は5個です。
iはいくつからいくつまで動きますか? - 手計算すると、平均点は何点になるはずですか?
averageがdoubleなのに、その値になるでしょうか?
解答と解説(クリックで開く)
バグ1:i <= scores.Lengthi が 5 になったとき scores[5] を読もうとして、IndexOutOfRangeException で止まります。i < scores.Length にします(または foreach を使います)。
バグ2:sum / scores.Lengthint ÷ int なので小数が切り捨てられます。本当は 389 ÷ 5 = 77.8 ですが、77 と表示されます。(double)sum / scores.Length のように、割る前に double にします。
ポイント:1つ目のバグで止まるので、2つ目は1つ目を直すまで見えません。「1つ直したら、もう一度疑う」ことが大事です。また、max の初期値が 0 なので、点数がすべてマイナスの配列では正しく動きません。int max = scores[0]; のほうが安全です。
3. 問題2 不合格の点数を取り除く(C#リスト・★★)
AIへの頼み方:「点数のリストから60点未満を取り除いて、残った人数を表示して」
バグの数:1つ(実行したときにエラーになるタイプです)
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var scores = new List<int> { 72, 45, 90, 38, 81 };
// 60点未満を取り除く
foreach (int score in scores)
{
if (score < 60)
{
scores.Remove(score);
}
}
Console.WriteLine($"合格者は{scores.Count}人です");
}
}
ヒント
foreachで順番に見ている途中で、そのリスト自体の中身を減らしています。- 本棚の本を左から順に数えている途中で、誰かが本を抜いたらどうなるでしょう?
解答と解説(クリックで開く)
バグ:foreach の中の scores.Remove(score)
列挙の途中でリストを変更したため、InvalidOperationException(Collection was modified…)で止まります。RemoveAll を使うか、後ろから for で回して消します。直したあとの表示は「合格者は3人です」になります。
var scores = new List<int> { 72, 45, 90, 38, 81 };
// 60点未満を取り除く
scores.RemoveAll(score => score < 60);
Console.WriteLine($"合格者は{scores.Count}人です");
ポイント:「読んでいる途中のものを書き換えない」は、C#以外でも共通の考え方です。エラーメッセージを自分で読んでから解説を見ると、5分ルールの練習にもなります。
4. 問題3 税込み金額の計算(WinForms・★★)
AIへの頼み方:「WinFormsで、単価と個数を入力してボタンを押すと、税込み(10%)の合計金額を表示して」
バグの数:2つ(1つはビルドの時点で、もう1つは使い方しだいで起きます)
フォームには textBoxPrice(単価)、textBoxCount(個数)、buttonCalc、labelTotal が置いてあるものとします。
private void buttonCalc_Click(object sender, EventArgs e)
{
int price = int.Parse(textBoxPrice.Text);
int count = int.Parse(textBoxCount.Text);
int total = price * count * 1.1;
labelTotal.Text = $"税込み合計: {total}円";
}
ヒント
1.1はintですか?intとdoubleをかけ算すると、結果の型は何になりますか?- 利用者が、何も入力せずにボタンを押したら? 「3個」と漢字まで入れてしまったら?
解答と解説(クリックで開く)
バグ1:int total = price * count * 1.1;int × double は double になるため、int に入れられずビルドエラー(CS0266)になります。
バグ2:int.Parse(...)
空欄や「3個」のような入力で FormatException になり、アプリが落ちます。int.TryParse で確認し、数字でなければメッセージを出します。
private void buttonCalc_Click(object sender, EventArgs e)
{
if (!int.TryParse(textBoxPrice.Text, out int price) ||
!int.TryParse(textBoxCount.Text, out int count))
{
MessageBox.Show("単価と個数には数字を入力してください。");
return;
}
// 1円未満は切り捨て(端数の扱いは仕様として確認する)
int total = price * count * 110 / 100;
labelTotal.Text = $"税込み合計: {total}円";
}
ポイント:(int)(price * count * 1.1) でもビルドは通りますが、1.1 は小数の誤差を含むため、端数処理が意図とずれることがあります。お金の計算は整数で計算するのが安全です。また「切り捨て・四捨五入・切り上げのどれにするか」はプログラムではなく仕様の問題です。AIに頼むときに、そこまで文章で書けていたか振り返ってみてください。
5. 問題4 プレイヤーの移動とコイン(Unity 6・★★★)
AIへの頼み方:「Unity 6で、矢印キーでプレイヤーを動かして、コインに触れたらGameManagerのスコアを10点増やすスクリプトを書いて」
バグの数:3つ(1つは「動くけれど、おかしい」、2つは「AIの知識が古い」タイプです)
前提:プロジェクトは Unity 6 で新しく作成し、入力は Input System を使う設定になっています。シーンには AddScore(int) を持つ GameManager があり、コインには Coin タグと Is Trigger の Collider が付いています。
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 5f;
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
transform.Translate(new Vector3(x, 0, z) * speed);
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Coin"))
{
GameManager gm = FindObjectOfType<GameManager>();
gm.AddScore(10);
Destroy(other.gameObject);
}
}
}
ヒント
Update()は1秒に何回呼ばれますか? パソコンの性能によって回数は変わりますか?Input.GetAxisは、どの入力の仕組みの書き方でしょう? 前提の設定と合っていますか?- Unity 6 のエディタで、どこかの行に警告(黄色の表示)が出ていないか見てみましょう。
解答と解説(クリックで開く)
バグ1:* speed だけで移動している
1フレームごとに 5 ずつ動くため、60fps なら1秒で約300も進みます。パソコンの性能で速さも変わります。* speed * Time.deltaTime にします。
バグ2:Input.GetAxis(...)
旧 Input Manager の書き方です。入力の設定が Input System のみだと、実行時に InvalidOperationException になります。Keyboard.current などの Input System の書き方にします。
バグ3:FindObjectOfType<GameManager>()
Unity 6 では非推奨で、警告が出ます。FindFirstObjectByType<GameManager>() を使い、毎回探さずに Start() で1回だけ取得しておきます。
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerController : MonoBehaviour
{
public float speed = 5f;
private GameManager gameManager;
void Start()
{
gameManager = FindFirstObjectByType<GameManager>();
}
void Update()
{
Keyboard keyboard = Keyboard.current;
if (keyboard == null)
{
return;
}
float x = 0f;
float z = 0f;
if (keyboard.leftArrowKey.isPressed) x -= 1f;
if (keyboard.rightArrowKey.isPressed) x += 1f;
if (keyboard.downArrowKey.isPressed) z -= 1f;
if (keyboard.upArrowKey.isPressed) z += 1f;
transform.Translate(new Vector3(x, 0, z) * speed * Time.deltaTime);
}
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Coin"))
{
if (gameManager != null)
{
gameManager.AddScore(10);
}
Destroy(other.gameObject);
}
}
}
ポイント:AIは、ネット上に多い「古い書き方」をそのまま出してくることがあります。「このコードは Unity 6 向けか?」と疑い、公式ドキュメントで確かめる習慣をつけましょう。警告(黄色)は「動くからいい」ではなく、読むべきメッセージです。
まとめ
今回の4問に出てきた間違いは、どれも実際にAIがよく出してくるものです。
- 範囲の境界(
<=と<) - 型の扱い(整数の割り算、
intとdouble) - 動いている途中のデータの変更
- 利用者の入力を信じすぎる
- 古いAPIや、フレームの考え方の抜け
コードを「動くかどうか」だけでなく、「なぜそう書いてあるのか」「どんなときに壊れるか」まで考えて読むこと。それが、AIのコードを安心して使うための筋肉になります。
【AI時代のプログラミング学習 第7回】
第1回|第2回|第3回|第4回|第5回|第6回|第7回(今ここ)|目次へ











ディスカッション
コメント一覧
まだ、コメントがありません