JavaScript | 配列・オブジェクト:パフォーマンス・設計 – 可読性優先 vs 速度優先

JavaScript JavaScript
スポンサーリンク

可読性優先 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・共有状態では非破壊を徹底して安全性を守る。宣言的なパイプを維持しつつ内部で高速化する“二層設計”が、読みやすさと速さを同時に満たす最良の妥協点です。

タイトルとURLをコピーしました