コードレビューの場で、未だに `let` が乱立しているプルリクエストを見るたびに、私はエンジニアの「思考の解像度」の限界を感じざるを得ない。
「とりあえず再代入するかもしれないから `let` にしておこう」
「`const` だと後から変更したくなったときに面倒だ」
もし君が、あるいは君のチームがそんな惰性でコードを書いているなら、今すぐその手を止めてほしい。現代のモダンなJavaScript/TypeScript開発において、`const` は単なる「定数(Constant)」の宣言キーワードではない。それは、「この変数二度と変化しない」というコードの不変性(Immutability)を保証し、読み手の脳内認知負荷を極限まで下げるための最強の設計ツールなのだ。
今回は、V8エンジンのメモリモデルと宣言的プログラミングのパラダイムから、なぜ `const` をデフォルトに据えるべきなのか、その本質を叩き込もう。
—
1. なぜ `let` の乱立はバグの温床になるのか
まずはランタイムの挙動から考えよう。`var`、`let`、`const` は、いずれもV8エンジンのスコープ(Lexical Environment)にバインディングを生成する。だが、`let` と `const` が持つ「TDZ(Temporal Dead Zone:一時的死区間)」の挙動は、巻き上げ(Hoisting)によるバグを防ぐための素晴らしい発明だ。
しかし、スコープの安全性と、アプリケーションロジックの安全性は別問題である。
`let` を使った瞬間、その変数はコードのどこかで「再代入される可能性がある」というフラグを背負う。数千行に及ぶ非同期処理やコンポーネントのライフサイクルの中で、変数がいつ、どこで、どう変異(Mutation)するかを追跡するのは、人間の脳にとって苦行でしかない。
宣言的プログラミングの本質とは何か
命令型プログラミング(Imperative Programming)は、「どうやって状態を変化させるか(How)」を記述する。
// 【アンチパターン】命令型:状態がどこで変わるか追うのが困難
let userStatus = ‘idle’;
if (hasData) {
userStatus = ‘loading’;
}
if (isError) {
userStatus = ‘error’; // あっちこっちで書き換えられる
}
対して、宣言的プログラミング(Declarative Programming)は、「データがどういう構造であるべきか(What)」を記述する。`const` は、この「データ構造の不変のルール」をコード片に定着させるための文法なのだ。
—
2. 実務で直面する非同期API連携・配列処理のコードレビュー
実際のフロントエンド開発、例えば「APIからユーザーデータを受け取り、加工してDOMや状態管理に流し込む処理」を考えてみよう。
ここで、典型的な「やってはいけない `let` の使い方」と、「`const` を主役に据えた堅牢な設計」を比較する。
❌ 非効率でバグを生むプロダクションコード(反面教師)
// どこで値が変わるか分からないため、全行を目で追う必要がある
let processedUsers = [];
let totalScore = 0;
let hasActiveUser = false;
async function handleUserData(apiEndpoint) {
let response = await fetch(apiEndpoint);
let rawData = await response.json();
// ループの中で外側の変数をガンガン書き換えている(サイドエフェクトの嵐)
for (let i = 0; i < rawData.users.length; i++) {
let user = rawData.users[i];
if (user.isActive) {
hasActiveUser = true;
let scoredUser = { ...user, score: user.baseScore 1.5 };
processedUsers.push(scoredUser);
totalScore += scoredUser.score;
}
}
return { processedUsers, totalScore, hasActiveUser };
}
このコードの問題点:
1. `let` だらけのため、後続の処理で意図せず変数が再代入されるリスクが常につきまとう。
2. `for` ループと一時変数 `i`、`user` がスコープを汚染している。
3. 副作用(Side Effect)がコード全体に散らばっており、単体テストが書きづらい。
—
`const` と関数型アプローチによる極限まで美しいプロダクションコード
これをモダンなフロントエンドの知見を総動員してリファクタリングしよう。配列メソッド(`filter`, `map`, `reduce`)と `const` を組み合わせることで、「再代入を一切しない(Immutability)」コードに昇華させる。
/
- APIから取得したユーザーデータをセキュアかつ宣言的に処理する関数
- @param {string} apiEndpoint – 取得先のエンドポイント
- @returns {Promise<{processedUsers: Array, totalScore: number, hasActiveUser: boolean}>}
/
const fetchAndProcessUsers = async (apiEndpoint) => {
// ネットワークI/Oの結果自体は再代入不可の定数として扱う
const response = await fetch(apiEndpoint);
const { users: rawUsers } = await response.json();
// 1. アクティブなユーザーのみを抽出(フィルタリング)
const activeUsers = rawUsers.filter(user => user.isActive);
// 2. スコア計算済みの新しいオブジェクト配列を生成(マッピング)
// すべての変数を const で宣言し、データの変異を完全に排除する
const processedUsers = activeUsers.map(user => {
const BONUS_MULTIPLIER = 1.5; // マジックナンバーを排除した定数
return {
…user,
score: user.baseScore BONUS_MULTIPLIER
};
});
// 3. 集計処理を reduce で宣言的に記述(変数のスコープ外への漏洩を防ぐ)
const totalScore = processedUsers.reduce((acc, user) => acc + user.score, 0);
// 4. 判定結果も const で即時評価
const hasActiveUser = processedUsers.length > 0;
// 不変データ構造としてまとめたオブジェクトを返す
return {
processedUsers,
totalScore,
hasActiveUser
};
};
—
3. この設計がV8エンジンとパフォーマンス、保守性にもたらす恩恵
「`const` にすればパフォーマンスが上がるのか?」という疑問を持つ鋭い読者もいるだろう。
厳密に言えば、V8エンジンのJITコンパイラ(TurboFanなど)は、`let` であっても再代入されていない変数を検出すれば、内部で `const` と同等の最適化(定数畳み込みなど)を行う。したがって、実行速度の差はマイクロベンチマークの誤差レベルに過ぎない。
しかし、「エンジニアの認知コスト」と「バグ修正にかかる時間」という最大のコストにおいて、この設計は圧倒的なパフォーマンス上のアドバンテージを生む。
1. 脳内キャッシュの有効性:
`const` と書かれている変数を見た瞬間、開発者は「この変数の値は、初期化された瞬間の状態から変わらない」と脳内で即座に確定できる。コードを読むときの分岐予測が減り、リーディングスピードが劇的に向上する。
2. 予期せぬバグの物理的な遮断:
誤って `processedUsers = null` のような再代入を記述しようものなら、JavaScriptエンジンが即座に `TypeError`(Assignment to constant variable.)を吐いて教えてくれる。テスト環境や本番環境に行く前に、静的解析や実行時エラーでバグを潰せるのだ。
3. オブジェクトや配列の「イミュータブル」な誤解への補足:
`const` は値が書き換わらないのではなく、「変数のバインディング(参照先)が再代入されない」ことを保証する。そのため、配列のプッシュ(`processedUsers.push()`)やオブジェクトのプロパティ書き換えは技術的には可能だ。だからこそ、上記コードのようにスプレッド構文(`…`)や `map` / `filter` を用いて、「中身のデータすらもミュータブルに変更しない」という厳格なコーディング規約とセットで運用するのが、チーフアーキテクトとしての流儀である。
—
結び:明日からのコードレビューで意識すべきこと
変数宣言において、思考停止で `let` を使う態度は、技術的負債への第一歩を踏み出していると同義だ。
次回のコードレビューからは、こう問いかけてほしい。
> 「その変数、本当に再代入する必要がありますか? `const` にできませんか?」
変数のスコープを狭め、再代入を排除し、データを不変として扱う「宣言的プログラミング」の思想を手にいれたとき、君の書くコードはただ動くだけの代物から、美しく堅牢な「エンジニアリングの芸術作品」へと昇華するはずだ。