プログラミング入門(JavaScript) 関数に関数を渡してコードの重複をなくす例

2020年8月2日

エラーコードからエラーの文字を返す課題

今回は実務でありそうな課題をやってみます。

実装内容

ユーザー情報を返してくれるAPIがあります。

このAPIはエラーになった際に{code:’U001′}のようなオブジェクトを返します。

codeの値に応じた文字を返すような実装をして欲しいとのことです。

エラーコードは三種類あり、それぞれに対する文字列は以下です。

  • U001 — ユーザーIDがありません
  • U002 — ユーザーIDが誤っています
  • U003 — 無効なユーザーIDです
  • それ以外 — ユーザーの取得に失敗しました

注意点として、APIで予期せぬエラーになった際には、nullかundefinedが返ってくるとのことです。

例えば {code: 'U002’} の場合は、「ユーザーIDが誤っています」という文字列を返すようにします。

実装してみる

とりあえず関数で実装してみます。

const handleUserError = (error) => {
  if(error === null || error === undefined) return '予期せぬエラー'

  switch(error.code){
    case 'U001':
      return 'ユーザーIDがありません'
    case 'U002':
      return 'ユーザーIDが誤っています'
    case 'U003':
      return '無効なユーザーIDです'
    default:
      return 'ユーザーの取得に失敗しました'
  }
}

これでも十分動作します。

追加の依頼

追加で、カスタマー情報を取得するAPIのエラーについても、同様のことをして欲しいとの依頼がありました。

  • C001 — カスタマーIDがありません
  • C002 — カスタマーIDが誤っています
  • C003 — 無効なカスタマーIDです
  • それ以外 — カスタマーの取得に失敗しました

APIで予期せぬエラーになった際には、nullかundefinedが返ってくるのは一緒とのことです。

以下のように実装しました。

const handleCustomerError = (error) => {
  if(error === null || error === undefined) return '予期せぬエラー'

  switch(error.code){
    case 'C001':
      return 'カスタマーIDがありません'
    case 'C002':
      return 'カスタマーIDが誤っています'
    case 'C003':
      return '無効なカスタマーIDです'
    default:
      return 'カスタマーの取得に失敗しました'
  }
}

リファクタリングしてみる

二つの関数をみると、はじめのif文が共通しています

今後、ユーザーやカスタマー以外にも、同じような関数を追加する必要がありそうです。その度に、同じif文をもった関数が増えていきます。

もし仕様が変わって、nullやundefinedの時は’予期せぬエラー’を返す、という動作が変わった場合、すべての関数に対して同じ修正をしなくてはいけません。

そこで、関数に関数を渡せるという強力な特性を使って重複をなくします。

const handleError = (error, handleFunc) => {
  if(error === null || error === undefined) return '予期せぬエラー'
  return handleFunc(error.code)
}

const userError = (code) => {
  switch(code){
    case 'U001':
      return 'ユーザーIDがありません'
    case 'U002':
      return 'ユーザーIDが誤っています'
    case 'U003':
      return '無効なユーザーIDです'
    default:
      return 'ユーザーの取得に失敗しました'
  }
}

const customerError = (code) => {
  switch(code){
    case 'C001':
      return 'カスタマーIDがありません'
    case 'C002':
      return 'カスタマーIDが誤っています'
    case 'C003':
      return '無効なカスタマーIDです'
    default:
      return 'カスタマーの取得に失敗しました'
  }
}

handleError({code: 'U002'},userError) //ユーザーIDが誤っています
handleError({code: 'C003'},customerError) //無効なカスタマーIDです

共通する処理はhandleErrorに持たせて、それぞれ振る舞いが異なるswitch文の部分は、関数(第二引数のhandleFunc)で渡せるようにしています。

userErrorとcustomerErrorは、switch文のみになることで、責務が明確になりシンプルになりました。

このように、関数に関数を渡すことで柔軟なコードを書くことができます。

補足

補足ですが、カリー化すると、handleErrorの第二引数にuserErrorやcustomerErrorを毎回渡さなくても済むようになります。

カリー化については、またの機会に書こうと思います。

const handleError = (handleFunc) => (error) => {
  if(error === null || error === undefined) return '予期せぬエラー'
  return handleFunc(error.code)
}

const userError = (code) => {
  switch(code){
    case 'U001':
      return 'ユーザーIDがありません'
    case 'U002':
      return 'ユーザーIDが誤っています'
    case 'U003':
      return '無効なユーザーIDです'
    default:
      return 'ユーザーの取得に失敗しました'
  }
}

const customerError = (code) => {
  switch(code){
    case 'C001':
      return 'カスタマーIDがありません'
    case 'C002':
      return 'カスタマーIDが誤っています'
    case 'C003':
      return '無効なカスタマーIDです'
    default:
      return 'カスタマーの取得に失敗しました'
  }
}

const handleUserError = handleError(userError)
const handleCustomerError = handleError(customerError)

handleUserError({code: 'U002'})
handleCustomerError({code: 'C003'})