可読性優先 vs 速度優先とは何か
コードには「誰が読んでも意図が分かる」「実行が速い」という2つの価値があります。ここが重要です:大半の業務コードでは“まず可読性”を優先し、速さは“必要な箇所だけ”最適化します。可読性は保守性・バグ減少・レビュー効率に直結し、速度はユーザー体験に直結します。両者は対立ではなく、段階的に両立できます。
// 可読性優先(分かりやすい連鎖)
const out = items
.filter(it => it.active)
.map(it => ({ id: it.id, price: it.price * 2 }))
.toSorted((a, b) => a.price - b.price);
// 速度優先(1パス融合、割り当て削減)
const tmp = [];
for (const it of items) {
if (!it.active) continue;
tmp.push({ id: it.id, price: it.price * 2 });
}
tmp.sort((a, b) => a.price - b.price);
JavaScript可読性を優先すべき場面
チーム開発・保守が前提のロジック
- 安定運用: 可読性が高いとレビュー・改修が速く、バグが減る。
- 意図の鮮明化: map/filter/reduce の連鎖は“何をしているか”が一目で分かる。
- 安全性: 非破壊(toSorted/filter/map)を使うと副作用が減り、UI状態管理と相性が良い。
// 仕様そのままの可読コード(誰でも追える)
const result = items
.filter(byEnabled(filters))
.filter(byPriceRange(filters))
.map(toViewRow)
.toSorted(compareFn);
JavaScriptまだボトルネックが分からない段階
- 計測前: 先に読みやすく正しいコードを書き、後から計測して“本当に遅い箇所だけ”最適化する。
- 学習効果: 初心者が意図を保ったまま変更できる。
速度を優先すべき場面
大量データ・高頻度処理のホットパス
- 中間配列削減: filter→map の連鎖を1パスに融合して割り当て・GCを減らす。
- 前計算キー: 文字列正規化や日付変換を比較前に1回で済ませる。
- 辞書化: 重複検索を O(1) に落とす。
// 1パスでフィルタ+変換+集計
function fastPipeline(items, pred, mapFn) {
const out = [];
let sum = 0;
for (const x of items) {
if (!pred(x)) continue;
const y = mapFn(x);
out.push(y);
sum += y.price ?? 0;
}
return { rows: out, sum };
}
JavaScript早期終了が効く検索
- find/some/every: “見つかったら終わる”系に置き換えると無駄な全走査を避けられる。
const hit = items.find(x => x.id === targetId);
const anyExpensive = items.some(x => x.price > 100000);
JavaScript両立のための設計パターン(読みやすさを崩さず速くする)
パイプの分離(宣言的+実装的)
- 宣言部: 何をしたいかを“読みやすい関数名”で並べる。
- 実装部: 実行は内部で1パス融合などの最適化を行う。
// 宣言部(可読)
const filtered = byFilters(items, filters);
const sorted = sortRows(filtered, sortKey);
// 実装部(高速)
function byFilters(list, f) {
const out = [];
for (const x of list) {
if (f.enabledOnly && !x.active) continue;
if (f.min != null && x.price < f.min) continue;
out.push(x);
}
return out;
}
JavaScript前計算(Schwartzian transform)
- ポイント: 比較時に重い処理を避ける。キーを持つ軽量ラッパで並び替え、最後に剥がす。
const prepared = rows.map(x => ({ x, k: x.name.trim().toLowerCase() }));
prepared.sort((a, b) => a.k.localeCompare(b.k, 'ja', { numeric: true }));
const sorted = prepared.map(p => p.x);
JavaScript非破壊を維持しつつ中間を減らす
- filterMap: 中間配列を作らず“最後の出力だけ”作るユーティリティを用意する。
const filterMap = (arr, pred, mapFn) => {
const out = [];
for (const x of arr) if (pred(x)) out.push(mapFn(x));
return out;
};
JavaScriptありがちな誤解と対策(重要ポイントの深掘り)
「for のほうが常に速い」は半分だけ正しい
- 現実: 小〜中規模では map/filter の可読性が勝つことが多い。最適化は“ホットパスだけ”。
- 対策: まず計測。差が意味を持つ規模かを確認してから置換する。
console.time('pipeline');
const out = items.filter(pred).map(mapFn);
console.timeEnd('pipeline');
JavaScript早まった最適化はバグの温床
- 現実: 可読性を崩してまで速度を上げると、仕様変更に弱くなる。
- 対策: 意図が伝わる抽象(関数名・分かりやすい引数)を維持し、内部だけ速くする。
破壊的操作で性能を上げて副作用で事故
- 現実: sort/splice の破壊的使用が共有状態を壊す。
- 対策: UI・共有配列は非破壊(toSorted/toSpliced)。ローカル一時データのみ破壊的可。
実践レシピ(可読性を保ちながら速くする)
宣言的パイプ+内部最適化
function querySortPage(items, cfg) {
const filtered = filterFast(items, cfg);
const sorted = sortFast(filtered, cfg.sort);
return paginate(sorted, cfg.page, cfg.perPage);
}
// 実装の中で融合・前計算
function filterFast(list, { q, enabledOnly }) {
const out = [];
const keyQ = q ? q.trim().toLowerCase() : null;
for (const x of list) {
if (enabledOnly && !x.active) continue;
if (keyQ && !x.name.toLowerCase().includes(keyQ)) continue;
out.push(x);
}
return out;
}
JavaScript大量データだけ最適化するスイッチ
function maybeFastFilterMap(arr, pred, mapFn, threshold = 50000) {
if (arr.length < threshold) {
return arr.filter(pred).map(mapFn); // 可読
}
return filterMap(arr, pred, mapFn); // 速い
}
JavaScriptまとめ
可読性優先 vs 速度優先の核心は「まず読みやすく正しく書き、計測して“本当に効く箇所だけ最適化する”」ことです。大量データ・ホットパスでは1パス融合、前計算キー、辞書化で速度を上げ、UI・共有状態では非破壊を徹底して安全性を守る。宣言的なパイプを維持しつつ内部で高速化する“二層設計”が、読みやすさと速さを同時に満たす最良の妥協点です。
