はじめに
JavaScriptを書き始めて少し経った頃、僕の書くコードはlet
だらけでした。
例えば、以下にピザの注文システムを想定したサンプルコードを書いてみます。
// ピザの注文システム
let orderStatus = "pending";
let totalPrice = 0;
let deliveryFee = 0;
let discountAmount = 0;
let finalMessage = "";
if (pizza.size === "large") {
totalPrice = 1500;
if (pizza.toppings.length > 3) {
totalPrice += 200;
}
} else if (pizza.size === "medium") {
totalPrice = 1000;
if (pizza.toppings.length > 3) {
totalPrice += 100;
}
} else if (pizza.size === "small") {
totalPrice = 500;
if (pizza.toppings.length > 3) {
totalPrice += 50;
}
}
if (totalPrice > 1000) {
deliveryFee = 100;
discountAmount = 100;
orderStatus = "specialConfirmed";
} else if (totalPrice > 0) {
orderStatus = "confirmed";
}
// 注文ステータスに応じて最終メッセージを決める
if (orderStatus === "specialConfirmed") {
finalMessage = `注文が完了しました。合計金額は${totalPrice}円です。配送料は${deliveryFee}円、割引額は${discountAmount}円です。`;
} else if (orderStatus === "confirmed") {
finalMessage = `注文が完了しました。合計金額は${totalPrice}円です。配送料は${deliveryFee}円、割引額は${discountAmount}円です。`;
} else {
finalMessage = `注文に失敗しました。`;
}
console.log(finalMessage);
このコード、まぁ動くかといえば、動きます。
でも、後から見返すと「あれ?このtotalPrice
って、今どの条件を通って、いくらになってるんだっけ?」と、自分の書いたコードなのに処理を読み解いていく必要が出てきてしまいがちです。
そんな時、ある先輩に「そのコード、let
が多いからif
も増えて、ごちゃごちゃしてるんだよ。const
をうまく使うと、もっとスッキリするよ」とアドバイスをもらいました。
正直、最初はピンと来ませんでした。でも、騙されたと思って**const
を意識的に使うようにした結果、コードが驚くほどシンプルになり、バグも減った**のです。
この記事では、僕が先輩から教わった「const
思考」で、どのようにコードが改善されていくのか、その過程を共有したいと思います。
なぜlet
だらけのコードは複雑になるのか?
let
は「後で値を変更できる」便利なやつです。
でも、その「いつでも変えられる」という自由さが、かえってコードを複雑にする原因になっていました。
-
状態を追いかけるのが大変:
-
let
で宣言された変数は、コードのあちこちで値が変わる可能性があります。変数の「今」の状態を常に頭の中で把握しておくのは、すごく疲れます。
-
-
ロジックがバラバラになりがち:
- 同じ変数への代入があちこちに散らばると、「結局、この金額はどう決まるの?」という全体像が見えにくくなります。
-
if
文の入れ子地獄になりがち:- 一つの条件分岐が、さらに細かい条件分岐を呼び、気づけば
if
が何重にも重なって、読むのも嫌になるコードが完成します。
- 一つの条件分岐が、さらに細かい条件分岐を呼び、気づけば
const
でシンプルにしていく思考法
const
は「一度値を入れたら、もう変えられない」という制約があります。
この「変えられない」という制約こそが、「どうすれば一度で値を決められるだろう?」と考えるきっかけをくれ、結果的にコードをシンプルにしてくれます。
💡 ここで一つ、大事な注意点
const
は、変数への「再代入」を防ぐものです。なので、オブジェクトや配列の中身(プロパティや要素)は変更できてしまいます。const user = { name: "タロウ" }; user.name = "ジロウ"; // これはOK! user = { name: "サブロウ" }; // これはエラー!
この記事では、この「再代入できない」という性質を逆手にとって、ロジックを組み立てる方法に焦点を当てます。
Step 1: if文の連鎖を「オブジェクト」で置き換える
まず、サイズごとにif
で分岐していた料金計算。
これをオブジェクトを用いて対応表を作ることで決まるようにします。
// Before: if文で条件分岐が連鎖…
let totalPrice = 0;
if (pizza.size === "large") { totalPrice = 1500; }
// ...
// After: 対応表で、どのサイズがいくらか一目瞭然!
const PRICE_TABLE = {
large: { base: 1500, toppingExtra: 200 },
medium: { base: 1000, toppingExtra: 100 },
small: { base: 500, toppingExtra: 50 }
};
const priceInfo = PRICE_TABLE[pizza.size] || { base: 0, toppingExtra: 0 };
const basePrice = priceInfo.base;
const toppingExtra = pizza.toppings.length > 3 ? priceInfo.toppingExtra : 0;
// 計算結果を、変更不能なconstに入れる
const totalPrice = basePrice + toppingExtra;
この書き方のミソは?
-
データとロジックの分離:
- 「料金設定」というデータと、「料金を計算する」という処理がはっきり分かれました。
-
追加・変更がラク:
- 新しいサイズが増えても、
PRICE_TABLE
に1行追加するだけ。if
文をいじる必要はありません。
- 新しいサイズが増えても、
-
もしもの時に備える:
-
|| { base: 0, ... }
の部分は、万が一想定外のサイズが来てもエラーで止まらず、価格を0として扱ってくれるお守りのようなものです。これを防御的プログラミングと言ったりします。
-
Step 2: 謎の数字に「名前」をつける
次に、コードにいきなり出てくる1000
や100
といった数字。これらは「マジックナンバー」と呼ばれ、後から見たときに「この数字、どういう意味だっけ?」となりがちです。これらに、ちゃんと意味の分かる名前をつけましょう。
// Before: 1000って何?100って何?
if (totalPrice > 1000) {
deliveryFee = 100;
discountAmount = 100;
}
// After: ルールをオブジェクトにまとめる
const ORDER_RULES = {
DELIVERY_THRESHOLD: 1000, // 送料無料になる金額
DELIVERY_FEE: 100, // 配送料
DISCOUNT_AMOUNT: 100 // 特別割引の額
};
const isSpecialOrder = totalPrice > ORDER_RULES.DELIVERY_THRESHOLD;
const deliveryFee = isSpecialOrder ? ORDER_RULES.DELIVERY_FEE : 0;
const discountAmount = isSpecialOrder ? ORDER_RULES.DISCOUNT_AMOUNT : 0;
なぜこれが良いのか?
-
コードが仕様書になる:
-
1000
が「送料無料になる金額」だと、誰が読んでも分かります。
-
-
変更が怖くない:
- 「配送料を120円に」と言われても、
ORDER_RULES
の1箇所を直すだけで済みます。
- 「配送料を120円に」と言われても、
Step 3: 文字列のベタ書きを「定数」で管理する
"confirmed"
のような文字列を直接コードに書くと、打ち間違い(タイポ)の元です。これも定数として、一箇所で管理するのが定石です。
// 状態を定数として定義
const ORDER_STATUS = {
SPECIAL_CONFIRMED: "specialConfirmed",
CONFIRMED: "confirmed",
PENDING: "pending"
};
const orderStatus = isSpecialOrder
? ORDER_STATUS.SPECIAL_CONFIRMED
: totalPrice > 0
? ORDER_STATUS.CONFIRMED
: ORDER_STATUS.PENDING;
なぜこれが良いのか?
-
打ち間違いを防げる:
- もし
ORDER_STATUS.CONFIrmED
と打ち間違えても、プログラムが「そんなものはないよ」とエラーで教えてくれます。
- もし
-
どんな状態があるか分かる:
- この注文にはどんなステータスがあるのか、
ORDER_STATUS
を見れば一発で分かります。
- この注文にはどんなステータスがあるのか、
Step 4: コピペコードを「テンプレート」でDRYにする
同じようなメッセージを何度も書くのは、面倒ですし間違いのもとです。プログラミングにはDRY原則(Don't Repeat Yourself / 同じことを繰り返すな)という有名な考え方があります。これに従って、メッセージ生成部分を共通化しましょう。
// Before: if文でメッセージを出し分け。成功時のメッセージがコピペになっている。
if (orderStatus === "specialConfirmed") {
finalMessage = `注文が完了しました。合計金額は${totalPrice}円です。配送料は${deliveryFee}円、割引額は${discountAmount}円です。`;
} else if (orderStatus === "confirmed") {
// 同じメッセージをまた書いている…
finalMessage = `注文が完了しました。合計金額は${totalPrice}円です。配送料は${deliveryFee}円、割引額は${discountAmount}円です。`;
} else {
finalMessage = `注文に失敗しました。`;
}
// After: メッセージのひな形を用意する
const MESSAGE_TEMPLATES = {
SUCCESS: (price, delivery, discount) =>
`注文が完了しました。合計金額は${price}円です。配送料は${delivery}円、割引額は${discount}円です。`,
FAILURE: () => `注文が失敗しました。`
};
const finalMessage = totalPrice > 0
? MESSAGE_TEMPLATES.SUCCESS(totalPrice, deliveryFee, discountAmount)
: MESSAGE_TEMPLATES.FAILURE();
なぜこれが良いのか?
-
修正が一度で済む:
- 「成功メッセージの文言を変えたい」とき、ひな形を1箇所直すだけです。
-
ロジックがスッキリ:
- 「どのメッセージを選ぶか」という判断と、「メッセージの見た目」が分離され、コードが追いやすくなります。
最終的なコード: 「関数」として一つにまとめる
最後に、これまでの改善をすべて「注文を計算する」という一つの関数にまとめます。こうすることで、この一連のロジックを、いつでもどこでも簡単に呼び出せるようになります。
// 設定値は外にまとめて定義
const PIZZA_PRICES = {
large: { base: 1500, toppingExtra: 200 },
medium: { base: 1000, toppingExtra: 100 },
small: { base: 500, toppingExtra: 50 }
};
const ORDER_RULES = {
DELIVERY_THRESHOLD: 1000,
DELIVERY_FEE: 100,
DISCOUNT_AMOUNT: 100
};
const MESSAGE_TEMPLATES = {
SUCCESS: (p, d, dis) => `注文が完了しました。合計金額は${p}円です。配送料は${d}円、割引額は${dis}円です。`,
FAILURE: (reason) => `注文に失敗しました。理由: ${reason}`
};
// 計算ロジックを関数にパッケージング
const calculateOrder = (pizza) => {
const priceInfo = PIZZA_PRICES[pizza.size];
if (!priceInfo) {
return { message: MESSAGE_TEMPLATES.FAILURE("無効なピザのサイズです") };
}
const basePrice = priceInfo.base;
const toppingExtra = pizza.toppings.length > 3 ? priceInfo.toppingExtra : 0;
const totalPrice = basePrice + toppingExtra;
if (totalPrice <= 0) {
return { message: MESSAGE_TEMPLATES.FAILURE("合計金額が0円以下です") };
}
const isSpecialOrder = totalPrice > ORDER_RULES.DELIVERY_THRESHOLD;
const deliveryFee = isSpecialOrder ? ORDER_RULES.DELIVERY_FEE : 0;
const discountAmount = isSpecialOrder ? ORDER_RULES.DISCOUNT_AMOUNT : 0;
const message = MESSAGE_TEMPLATES.SUCCESS(totalPrice, deliveryFee, discountAmount);
return {
totalPrice,
deliveryFee,
discountAmount,
message
};
};
// --- 関数の使い方 ---
const pizzaOrder = { size: "large", toppings: ["cheese", "salami", "onion", "pepper"] };
const result = calculateOrder(pizzaOrder);
console.log(result.message);
// 出力: 注文が完了しました。合計金額は1700円です。配送料は100円、割引額は100円です。
まとめ: 僕がconst
思考から学んだこと
let
とif
だらけのコードから抜け出すことは、単に書き方を変えるだけではありませんでした。僕にとっては、コードの設計思想そのものをアップデートする大きな一歩だったのです。
この経験から学んだ大切なことは3つです。
- ルールや設定は「データ」として切り出す: 価格表や割引ルールをコードのロジックから分離すると、驚くほど見通しが良くなり、変更に強くなります。
-
「意図」が伝わるように書く: マジックナンバーや文字列のベタ書きをやめ、意味のある名前の
const
で定義すると、コードが「何をしたいのか」を語り始めます。 -
値は「一度だけ」決める: 「この変数の値は、ここで決まって、もう変わらない」という
const
の制約が、かえって処理の流れを追いやすくし、バグが入り込む隙をなくしてくれます。
もしあなたが昔の僕のようにlet
だらけのコードに悩んでいたら、まずは「原則const
で書き、どうしても再代入が必要なときだけlet
を使う」というルールから試してみてください。
完璧を目指さなくて大丈夫です。一つや二つだけでも取り入れてみるだけで、あなたのコードはもっと読みやすく、もっと堅牢なものへと変わっていくはずです。