🏗️

ネスト階層が深いif文をリファクタリングする

2024/09/29に公開

概要

初学者にありがちなネスト階層が深いif文を分解し、早期リターンでシンプルに記述する方法を紹介します。

要件

次の要件を満たすバリデーションを実装する(ライブラリは使用しない)

  • 入力された開始日と終了日の差分が n ヶ月以上離れている場合にエラーとする。
  • 1 年以上の差分は指定できない(n <= 12)。
  • 終了日が開始日よりも前の日付ではいけない。
  • n ヶ月後の同日(応答日)は指定期間に含む
  • 時刻は考慮しない

課題

※ 読み飛ばしていただいて構いません。
次のように if 文をネストして表現した既存コードがありました。
記述量が多く可読性も悪いため、このコードを要件に合わせてリファクタリングしていきます。

const isValidPeriod = (
  startDate: Date,
  endDate: Date,
  maxRangeOfMonths: number
): boolean => {
  if (maxRangeOfMonths <= 12) {
    if (endDate.getFullYear() - startDate.getFullYear() === 0) {
      if (endDate.getMonth() - startDate.getMonth() <= maxRangeOfMonths) {
        if (endDate.getMonth() - startDate.getMonth() === maxRangeOfMonths) {
          if (startDate.getDate() >= endDate.getDate()) {
            return true;
          } else {
            return false;
          }
        } else {
          return true;
        }
      } else {
        return false;
      }
    } else if (endDate.getFullYear() - startDate.getFullYear() === 1) {
      if (12 - startDate.getMonth() + endDate.getMonth() <= maxRangeOfMonths) {
        if (
          12 - startDate.getMonth() + endDate.getMonth() ===
          maxRangeOfMonths
        ) {
          if (startDate.getDate() >= endDate.getDate()) {
            return true;
          } else {
            return false;
          }
        } else {
          return true;
        }
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
  return false;
};

実装

リファクタリングの観点

  • 重複する評価を統合する
  • 早期に結果を返却し、記述量を減らす
  • マジックナンバーを消す
  • 極力ネストしない
  • 不要な else, else if を使用しない
const isValidPeriod = (
  startDate: Date,
  endDate: Date,
  maxRangeOfMonths: number
): Boolean => {
  // なくても良いですが、個人的に12が何の数字が考えるのが嫌なので変数にします。
  const TWELVE_MONTHS = 12;

  // 何度もgetter呼び出すのが嫌なので先にオブジェクトとして定義してしまいます。
  const start = {
    y: startDate.getFullYear(),
    m: startDate.getMonth(),
    d: startDate.getDate(),
  };
  const end = {
    y: endDate.getFullYear(),
    m: endDate.getMonth(),
    d: endDate.getDate(),
  };

  // 差分最大値のチェックをチェックしています。
  // 13ヶ月以上は指定できないようにします。
  if (maxRangeOfMonths > TWELVE_MONTHS) return false;

  // 月の差分
  const monthDiff = (() => {
    return start.y === end.y
      ? end.m - start.m // 同年の場合
      : TWELVE_MONTHS - start.m + end.m; // 年またぎの場合
  })();

  // 年の差分が要件を満たしていること
  // 1年以上の開きがある場合は要件に満たないため、falseを返却して処理を終了させます。
  if (end.y - start.y > 1) return false; 

  // 月の差分が要件を満たしていること
  // 選択期間が指定最大値 (nヶ月)を超えている場合は要件に満たないため、falseを返却して処理を終了させます。
  if (monthDiff > maxRangeOfMonths) return false;

  // 日の差分が要件を満たしていること
  // 月の差分が最大値の且つ、開始日付の数字が終了日付よりも大きくない場合は要件に満たないためfalseを返却して終了させます。
  // 例: 最大値3ヶ月のときに2024/01/01 〜 2024/04/02 => false
  if (monthDiff === maxRangeOfMonths && start.d < end.d) return false;

  // すべてのチェックを通ればOK
  return true;
};

おわりに

いかがでしたでしょうか。
思いつきで書いているのでもっと良い書き方はあるかと思いますが、初学者がやりがちなif文のネストしまくり問題を解決する一助になれば幸いです。

Discussion