YovStudio

article 読むトレ #01:そのループ、はみ出してます

公開日: 2025-06-21著者: Yov in YovStudio

このシリーズの目的とスタンス

このシリーズでは、実務でたまに見かけるようなよくあるバグ動くけどちょっとクセのあるコードを模したコードを AI に書いてもらい、それを一緒に観察しながら読む練習をしていきます。

「読むトレ」と名付けたこのシリーズは、コードをただ動かすだけではなく、「なぜこう書かれているのか」「どこで思い違いしやすいのか」などに目を向けながら進める、読みのトレーニングです。

AI には原則、環境構築なしですぐに動かせる HTML / CSS / Javascript で作成してもらいます。
これらの言語特有の仕様について触れることもありますが、なるべくどんな言語でも通用する汎用的なトレーニングになるようにしていくつもりです。
レベル感としては、for 文や if 文の基本が少しわかるくらいの初学者や、実務経験が浅い方を想定しています。

読みながら「自分ならこう書くかも」「ここはちょっと引っかかった」といった気づきを大切にしてもらえたら嬉しいです。

今回のお題コード

では早速、AI に今回のお題となるコードを提示してもらいますね。

AI「自分以外のユーザーを一覧表示するコードを書いてみました。どうでしょう?」

const currentUser = "Alice";  // 現在ログインしているユーザー(自分)
const users = ["Alice", "Bob", "Charlie"];
console.log("あなた以外のユーザー一覧:");

// ユーザーリストの分だけループ
for (let i = 0; i <= users.length; i++) {
  if (users[i] !== currentUser) {
    // 自分以外のユーザーの場合
    console.log(users[i]);
  }
}

どう動く?

さてみなさん、まずは自分でこのコードを読んでみて、どういう動きになるか予想してみましょう。

以下のようなサイトで動かしてみてもよいです。

気になる動き

実際に動かすと以下のような結果になります。

あなた以外のユーザー一覧:
Bob
Charlie
undefined

しかしこれはちょっとおかしいですね?
3 人目として undefined が表示されていますが、誰でしょう?新キャラですか?
ユーザーリストにはこんなユーザーはいませんでしたよね。

const users = ["Alice", "Bob", "Charlie"];

AI のお題から考えると、users から Alice を除いた以下の結果が正しいはずです。

あなた以外のユーザー一覧:
Bob
Charlie

なぜ undefined が表示されるのでしょうか?

原因は「配列の範囲外にアクセスしているから」

このループ条件の i <= users.length ですが、i = 0 からスタートすることを意識しながらよく見てみてください。

for (let i = 0; i <= users.length; i++)

users.lengthusers の要素の数であるため、3 が入っています。
ということは、ループする条件は i <= 3 となり、i = 0,1,2,3 と 4 回処理を繰り返します。つまり、要素の数は 3 個なのに、4 回ループしてしまっているのです。

これにより、下記のコードで users[0],…,users[3] という配列の要素へのアクセスが発生しますが、4回目のループ(i = 3)のときの users[3] は配列の範囲外であるためデータが存在せず、undefined(=値が未定義) になってしまうのです。

if (users[i] !== currentUser) {
  // 自分以外のユーザーの場合
  console.log(users[i]);
}

また、上記のif文自体は「自分以外のユーザーを表示する」ための条件式としては適切ですが、ループ回数のミスにより undefinedcurrentUser を比較してしまい、意図せず条件式を満たしている点も重要ですね。

これが undefined が表示されてしまう原因です。

undefined って、最初は「え、なんで?」となりますよね。
でもこれ、JavaScriptならではの優しさ(?)なんです。JavaScriptって実はかなり緩めの言語で、今回のように配列の範囲外にアクセスしたりしても、他の言語みたいにエラーで止まったりせず、静かに undefined を返してくれちゃいます。
この緩さは一長一短で、特にエラーにならずに処理が進んでしまう点は、バグの発見が遅れることもあるので要注意ですよ。

ちなみに配列の範囲外アクセスは、テストが十分でないうちは実務でもたまに見かけます。
このお題のようにループの条件を間違えているケースと、あとは配列が空っぽなのに1つめの要素を取得しようとしてしまうようなケースが多いでしょうか。

どう修正する?

ループする条件を i < users.length とし、i = 2まででループをやめるようにすればよいでしょう。

// iが配列の範囲内に収まるように修正
for (let i = 0; i < users.length; i++) {
  if (users[i] !== currentUser) {
    console.log(users[i]);
  }
}

もしくは、すべての要素に単純な処理をさせたいだけなら forEach を使うのもアリです。
こちらは配列の範囲外アクセスが発生しないループの書き方なので、バグのリスクが減ります。 ただし、ループの途中で抜け出す必要がある場合など、for文で書かなくてはいけない場合もあります。

// forEachなら users[i] というアクセスをしないので安全
users.forEach((user) => {
  if (user !== currentUser) {
    console.log(user);
  }
});

補足:配列の要素へのアクセスについて

初学者向けなので、今回出てきた配列 users の要素へのアクセスについて少しだけ補足しますね。 以下のように、配列に対して要素番号を指定すると配列の要素を参照できます。 要素番号は 0 から始まる点がポイントです。

users[0] -> 'Alice'
users[1] -> 'Bob'
users[2] -> 'Charlie'
users[3] -> undefined
 :
users[N] -> undefined

今回の読みどころ

  • <=<、たった1文字の違いがバグの原因になる
  • .length を使ったループでは、配列の末尾を超えないように注意
  • 「意図は正しいけど、条件がズレているコード」は、実務でよくあるパターン

おまけ:お題コードの修正版

const currentUser = "Alice";  // 自分はAlice
const users = ["Alice", "Bob", "Charlie"];
console.log("あなた以外のユーザー一覧:");

// ユーザーリストの分だけループ
for (let i = 0; i < users.length; i++) {
  if (users[i] !== currentUser) {
    // 自分以外のユーザーの場合
    console.log(users[i]);
  }
}