💡

【6日目】『リーダブルコード』を意識してJavascriptをリファクタリングしてみた

に公開

技術ブログ6日目。
本日は、Javascriptの読みやすさを追求します。

〇課題
注文合計金額計算プログラム
一応、期待通りに計算は行われます。
しかし、何をしているかが読み解きづらく、修正時にバグを出しやすい状態です。

〇修正前コード(動くけれど読みにくいコード)

// 課題コード
function process(p, q, status) {
  let r = 0;
  
  if (p > 0) {
    if (q > 0) {
      // 合計を計算
      let total = p * q;
      
      // 10個以上は10%引き
      if (q >= 10) {
        total = total * 0.9;
      }
      
      // 会員種別による割引
      if (status === 1) {
        total = total - 500;
      } else if (status === 2) {
        total = total - 100;
      }
      
      // 消費税(10%)
      r = total * 1.1;
    }
  } else {
    r = 0;
  }
  
  return r;
}

// 実行例
console.log(process(1000, 10, 1)); // 会員かつ10個以上購入の場合

〇リファクタリングの指針
名著『リーダブルコード』の以下の原則を意識しました。
名前の改善:p, q, r などの抽象的な名前を、意味を持つ名前に変更。
条件記述の単純化:深いネストを解消し、「ガード節(早期リターン)」を導入。
説明変数の導入:計算式に名前を付け、コード自体が説明書になるように修正。

〇修正後コード

/**
 * 注文の合計金額(税込)を計算する
 * @param {number} unitPrice - 単価
 * @param {number} quantity - 数量
 * @param {number} memberStatus - 会員ランク (1: プレミアム, 2: 一般)
 */
function calculateTotalPrice(unitPrice, quantity, memberStatus) {
  // 定数の定義(マジックナンバーの排除)
  const TAX_RATE = 1.1;
  const BULK_DISCOUNT_THRESHOLD = 10;
  const BULK_DISCOUNT_RATE = 0.9;

  const MEMBER_DISCOUNTS = {
    PREMIUM: 500,
    REGULAR: 100,
  };

  // 1. ガード節による早期リターン
  // 不適切な条件を先に排除し、メインロジックのネストを浅くする
  if (unitPrice <= 0 || quantity <= 0) {
    return 0;
  }

  // 2. 説明変数による計算過程の明確化
  const basePrice = unitPrice * quantity;
  
  const isBulkPurchase = quantity >= BULK_DISCOUNT_THRESHOLD;
  const priceAfterBulkDiscount = isBulkPurchase 
    ? basePrice * BULK_DISCOUNT_RATE 
    : basePrice;

  const memberDiscountAmount = (memberStatus === 1) ? MEMBER_DISCOUNTS.PREMIUM 
                             : (memberStatus === 2) ? MEMBER_DISCOUNTS.REGULAR 
                             : 0;

  // 3. 再代入を避け、constで値を確定させる
  const totalPriceBeforeTax = Math.max(0, priceAfterBulkDiscount - memberDiscountAmount);
  
  return Math.max(0, totalPriceBeforeTax * TAX_RATE);
}

console.log(calculateTotalPrice(1000, 10, 1));

〇主な修正ポイント
1.ガード節の導入
ifの中にifがある深いネストを解消しました。

2.名前に情報を詰め込む
p, q, r を unitPrice, quantity, totalPrice へ変更。 また、status === 1 を MEMBER_DISCOUNTS.PREMIUM と比較するようにし、コメントがなくても「何と比較しているか」が一目で分かるように修正しました。

3.「説明変数」と「不変性」
「後で値が変わる空箱(let)」を、各段階の計算結果に名前を付けてconstで固定しました。
「今の変数の値はどうなっているか?」とコードを遡って追跡する手間をなくすことを目指しました。

4.マジックナンバーの排除
0.9 や 500 といった生の数値を定数化しました。
これにより、将来的な消費税率や割引額の変更にも、1箇所の修正で対応できます。

〇参照先
▼公式ドキュメント
Math.max()
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Math/max

JavaScript オブジェクトの基本
https://developer.mozilla.org/ja/docs/Learn_web_development/Core/Scripting/Object_basics

条件(三項)演算子
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Conditional_operator

▼書籍
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック

以上

Discussion