0277045a076689a033f3f26d7d4517a2
2015/12/09 01:07:22 投稿
2

条件の多いcase〜whenを配列で置き換える

とあるAPIのクライアントgemの開発に Faraday を使ってるのですが、FaradayのAPI実行時にエラー(ステータスコード400系や500系)が返ると Faraday::ClientError が投げられます

しかし Faraday::ClientError をアプリケーションのmodelやcontrollerの層までそのまま伝播させるとエラーハンドリングが困難なので(rescue の中で if 文書きたくない)、gemの層で独自のエラークラスにラップしてあげる必要がありました。

APIから返されたエラーコードを適切なエラークラスに詰め替える部分は共通化していたのですが(下記コードの with_client_error_handling)、いざ全部完成してみると rescue の中で when が20以上もあるcase 文ができてウッとなったのでリファクタリングしました。

ちなみに今回のリファクタリングで rubocop の Metrics/CyclomaticComplexity(循環的複雑度) のoffenseが解消されました

Before

class MyLib::BaseClient
  private

    # ブロック内で {Faraday::ClientError} が投げられたら {MyLib::BaseError} の各種サブクラスに詰め替えて投げ直す
    # @raise [MyLib::Error]
    def with_client_error_handling
      yield
    rescue Faraday::ClientError => error
      error_code = error.response[:body][:code]
      description = error.response[:body][:description]

      case error_code
      when "auth.invalidToken"
        raise MyLib::AuthInvalidTokenError, description
      when "http.NotFound"
        raise MyLib::HttpNotFoundError, description
      # NOTE: こういうwhenが20個以上ある
      #  (中略)
      else
        # エラーコードにマッチするのがなければデフォルトでBaseErrorを返す
        raise MyLib::BaseError, description
      end
    end
end

# NOTE: これ系のAPIのClientクラスが5〜6個ある
class MyLib::UserClient < MyLib::BaseClient
  # NOTE: こういうメソッドが計30個以上ある
  def person(user_id = "me", options = {})
    with_client_error_handling do
      response = connection.get("/graph/#{user_id}", options)
      response.body
    end
  end
end

After

class MyLib::BaseClient
  private

    # ブロック内で {Faraday::ClientError} が投げられたら {MyLib::BaseError} の各種サブクラスに詰め替えて投げ直す
    # @raise [MyLib::Error]
    def with_client_error_handling
      yield
    rescue Faraday::ClientError => error
      error_class = generate_error_class(error)
      description = error.response[:body][:description]
      raise error_class, description
    end

    # エラーコードとエラークラスのマッピング
    ERROR_CLASSES = {
      "auth.invalidToken" =>  MyLib::AuthInvalidTokenError,
      "http.NotFound"     =>  MyLib::HttpNotFoundError,
      # NOTE: こういうentryが20個以上ある
      #  (中略)
    }

    # {Faraday::ClientError} を {MyLib::BaseError} に詰め替える
    # @param error [Faraday::ClientError]
    # @return [MyLib::BaseError]
    def generate_error_class(error)
      error_code = error.response[:body][:code]

      # エラーコードにマッチするのがなければデフォルトでBaseErrorを返す
      ERROR_CLASSES[error_code] || MyLib::BaseError
    end
end

# NOTE: これ系のAPIのClientクラスが5〜6個ある
class MyLib::UserClient < MyLib::BaseClient
  # NOTE: こういうメソッドが計30個以上ある
  def person(user_id = "me", options = {})
    with_client_error_handling do
      response = connection.get("/graph/#{user_id}", options)
      response.body
    end
  end
end

みんなのコメント