Chapter 06

実例:単体テストとリファクタリング #2

とっくり
とっくり
2022.06.14に更新

今度こそ「純粋関数は最高だ!」となるリファクタリングをしましょう。
前回の記事からの続きです。

https://zenn.dev/tockri/books/dcaf6c55e64448/viewer/60b79c
単体テストの準備ができたコードはこちらです。
https://github.com/tockri/not-scary-fp/tree/master/ex-form-validation/refactor-1

この js/validator.js をリファクタリングしていきます。

TDDは最高だ!

$ npx jest test/validator.test.js --watch

として、保存するたびに毎回テストが走るようにしておきます。

空チェックを共通化

まずわかりやすいところで、validateNamevalidateAddressはエラーメッセージ以外は完全に同じなので共通化しましょう。

validator.js
Validator.checkEmpty = function(state, messageIfError) {
  if (state.value !== "") {
    return {
      ...state,
      valid: true,
      message: ""
    }
  } else {
    return {
      ...state,
      valid: false,
      message: messageIfError
    }
  }
}

こんな関数を作ってやれば、validateNamevalidateAddressからはこれを呼ぶだけになりますね。

validator.js
Validator.validateName = function(state) {
  return Validator.checkEmpty(state, "名前を入力してください");
}

Validator.validateAddress = function(state) {
 return Validator.checkEmpty(state, "住所を入力してください");
}

簡単ですね。そしてこうやって変更している間もJestが走り続けて、validateNamevalidateAddressの機能が変化していないことを教えてくれます。安心感がすごい。もうこの辺で、(ああ…純粋関数にしてよかったなあ…)と思えてきますが、まだまだいきますよ。

郵便番号チェック=空チェック+正規表現チェックの組み合わせ

validateZipを見てみましょう。

validator.js
Validator.validateZip = function(state) {
 if (state.value !== "") {
   if (state.value.match(/^\d{3}-\d{4}$/)) {
     return {
       ...state,
       valid: true,
       message: ""
     }
   } else {
     return {
       ...state,
       valid: false,
       message: "000-0000の形式で入力してください"
     }
   }
 } else {
   return {
     ...state,
     valid: false,
     message: "郵便番号を入力してください"
   }
 }
}
  1. 空かどうかチェックする
  2. 空でなければ、正規表現マッチしているかチェックする

という順序で処理しています。この「空かどうか」というのはすでにcheckEmptyという関数を作っているので、正規表現チェックの関数だけを作りましょう。

validator.js
Validator.checkPattern = function(state, pattern, messageIfError) {
  if (!state.valid) { // 前のチェックでエラーになってたら何もしない
    return state;
  }
  if (state.value.match(pattern)) {
    return {
      ...state,
      valid: true,
      message: ""
    };
  } else {
    return {
      ...state,
      valid: false,
      message: messageIfError
    };
  }
}

これを導入すると、validateZipはこうなります。

validator.js
Validator.validateZip = function(state) {
  const state1 = Validator.checkEmpty(state, "郵便番号を入力してください");
  return Validator.checkPattern(state1, /^\d{3}-\d{4}$/, "000-0000の形式で入力してください");
}

checkEmptycheckPatternも、Stateを受け取ってStateを返す純粋関数なので、このように複数のチェックを組み合わせられます。こういうインターフェイスにして良かったですね!

もちろんvalidateMailも同様です。

validator.js
Validator.validateMail = function(state) {
  const state1 = Validator.checkEmpty(state, "メールアドレスを入力してください");
  return Validator.checkPattern(state1, /^[\w\.]+@[\w\.]+[^\.]$/, "メールアドレスの形式が正しくありません。");
}

ここまでのコードはこちらから見られます。

https://github.com/tockri/not-scary-fp/blob/master/ex-form-validation/refactor-2/js/validator.js