ループの可読性とは何か
「可読性が高いループ」とは、意図が一目で分かり、バグが起きにくく、後から変更しやすい書き方です。ここが重要です:処理の目的(抽出・変換・集計・実行)に合った構文を選び、否定条件を早く捨て、インデックス管理を必要最小に抑え、非破壊の原則を守ることで、ループはぐっと読みやすくなります。
目的別に選ぶ構文
役割に合う道具を選ぶ
- 変換(新しい配列が欲しい): map
- 抽出(条件で残す): filter
- 集計(ひとつに畳み込む): reduce
- 副作用の実行(ログ・DOM 操作): forEach
- 柔軟な制御(break/continue/await): for…of/for
意図に合う構文を選ぶと、ループ内部の分岐や手続きが減り、コードが目的中心になります。
例:意図が伝わる置き換え
// 目的が「在庫ありのラベル作成」なら…
const labels = products
.filter(p => p.stock > 0) // 抽出
.map(p => `${p.name} (${p.price})`); // 変換
JavaScriptネストを浅く保つテクニック
否定条件は先に捨てる
for (const p of products) {
if (!p) continue; // 欠損はスキップ
if (p.stock <= 0) continue; // 在庫なしはスキップ
// 核心だけを書く
labels.push(`${p.name} (${p.price})`);
}
JavaScript- ガードで浅く: 早めの continue でネストを浅くする。
- 肯定だけを書く: 本体はやりたい処理のみ。
1階層ずつ処理する
// ネスト配列の中身だけ欲しいなら flatMap を使う
const items = groups.flatMap(g => g.items);
JavaScript- 役割分離: 展開→抽出→変換の順に段階を分ける。
早期リターンの活用
関数の先頭でガード節
function toLabels(products) {
if (!Array.isArray(products) || products.length === 0) return [];
return products.filter(p => p?.stock > 0).map(p => `${p.name} (${p.price})`);
}
JavaScript- 不正は即返す: 正常系だけ下に伸ばすと読みやすい。
- 分岐を減らす: ネストと条件の複雑さが下がる。
ループの途中終了
function findFirstValid(xs) {
for (const x of xs) {
if (x == null) continue;
if (isValid(x)) return x; // 早期 return
}
return null;
}
JavaScript- 目的に合う終了: “見つかったら終了”は find/for…of+return。
副作用とデータ変換の分離
副作用は forEach、変換は map
// 変換(新配列が欲しい)
const names = users.map(u => u.name);
// 副作用(ログやDOM)
users.forEach(u => console.log(u.name));
JavaScript- 混ぜない: map の中で外部配列へ push しない。forEach は“実行”に徹する。
- 非破壊の原則: 元配列を書き換えず、新配列へ結果を入れる。
インデックスと範囲の明確化
境界は開区間で書く
for (let i = 0; i < arr.length; i++) { /* … */ } // “未満”が鉄則
JavaScript- off-by-one 回避: <= は余分に回って undefined を読む。
インデックスが必要な時だけ使う
for (const [i, v] of arr.entries()) {
// i と v の両方を明示できる
}
JavaScript- 値中心: 位置不要なら for…of で簡潔に。
- 削除は逆順: splice するなら末尾から前へ。
非同期処理での可読性
順次なら for…of+await
async function fetchAll(ids) {
const out = [];
for (const id of ids) {
const r = await fetch(`/api/${id}`);
if (!r.ok) break; // 失敗で終了
out.push(await r.json());
}
return out;
}
JavaScript- 待ちやすい: 1件ずつ確実に処理しやすい。
並列なら map+Promise.all
const results = await Promise.all(
ids.map(id => fetch(`/api/${id}`).then(r => r.json()))
);
JavaScript- 短く明確: “並べて待つ”意図をはっきり表現。
命名と構造化の指針
名前で意図を語らせる
- ラベル例: source: 入力配列、out: 出力配列、sum: 合計、hit: 最初の一致。
- 関数分割: validate: 検証、format: 整形、collect: 収集。役割別に分けるとループが薄くなる。
高コストの準備は外で
const collator = new Intl.Collator("ja", { sensitivity: "base" });
const labels = rows.map(r => collator.compare(r.a, r.b) > 0 ? r.a : r.b);
JavaScript- 再利用: 正規表現や Collator の生成はループ外で一度だけ。
実践例の前後比較
before:目的が混ざって読みづらい
const labels = [];
for (let i = 0; i < products.length; i++) {
const p = products[i];
if (!p || p.stock <= 0) { /* 何もしない */ }
else {
const s = `${p.name} (${p.price})`;
labels.push(s);
}
}
JavaScriptafter:役割分離で一目瞭然
const labels = products
.filter(p => p?.stock > 0) // 先に捨てる
.map(p => `${p.name} (${p.price})`); // 変換だけに集中
JavaScriptまとめ
ループの可読性は「目的に合う構文選択」「否定条件の早期排除」「非破壊の原則」「インデックス最小化」「非同期の設計分離」で決まります。抽出は filter、変換は map、集計は reduce、副作用は forEach、停止・待機は for…of。ガードでネストを浅く保ち、命名で意図を語らせ、重い準備は外へ。これを徹底すれば、初心者でも短く、意図が伝わり、壊れにくいループを書けます。

