LINEヤフー Tech Blog

LINEヤフー株式会社のサービスを支える、技術・開発文化を発信しています。

コード品質向上のテクニック:第67回 過ぎたるエラーは猶及ばざるが如し

こんにちは。コミュニケーションアプリ「LINE」のモバイルクライアントを開発している石川です。

この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 67 回です。 LINEヤフー社内には、高い開発生産性を維持するための Review Committee という活動があります。ここで集まった知見を、Weekly Report と称して毎週社内に共有しており、その一部を本ブログ上でも公開しています。(Weekly Report の詳細については、過去の記事一覧を参照してください)

過ぎたるエラーは猶及ばざるが如し

以下の queryUserModel は、ユーザのデータモデルのクエリ結果を ApiResult<UserListQueryResponse?>? という戻り値型として返す関数です。ここで ApiResultUserListQueryResponse はそれぞれ sealed interface で実装されています。

fun queryUserModel(id: UserId): ApiResult<UserListQueryResponse?>? { ... }

sealed interface ApiResult<out T> {
    class Success<T>(val value: T) : ApiResult<T>
    class Failure(val throwable: Throwable) : ApiResult<Nothing>
}

sealed interface UserListQueryResponse {
    class Success(val userModel : UserModel?) : UserListQueryResponse

    class Failure(val errorType: ErrorType) : UserListQueryResponse

    enum class ErrorType { ... }
}

このコードに何か問題はあるでしょうか?

エラー専心

このコードには、エラーやエッジケースを示す方法が複数存在しているという問題があります。例えば、エラーやエッジケースとして以下の 5 つの可能性があります。

  1. null を返す
  2. ApiResult.Failure を返す
  3. ApiResult.Success を返し、その valuenull
  4. ApiResult.Success を返し、その valueUserListQueryResponse.Failure
  5. ApiResult.Success を返し、その valueUserListQueryResponse.Success であり、その userModelnull

エラーを示す複数の方法が存在すると、呼び出し元にとって、何のエラーがどのような意味を持つのかが分かりにくくなります。たとえエラーの形式が異なったとしても、結局は呼び出し元でのエラー処理が同じになることも多く、単にコード煩雑にするだけという結果も招きがちです。これは、呼び出し先の都合で使っているエラーを、そのまま呼び出し元に渡している ことが原因です。

これを解決するためには、呼び出し元が使いやすい・必要とするエラー表現に変換する とよいでしょう。特に多くの場合、 エラーの状態を単一の一貫した表現にまとめる と上手くいくことが多いです。たとえば次の実装では、成功・失敗の区別を UserModelApiResult だけで表現し、エラーの種類は UserRequestErrorType にまとめています。

fun queryUserModel(id: UserId): UserModelApiResult {
    // ...
}

sealed interface UserModelApiResult {
    class Success(val userModel: UserModel) : UserModelApiResult
    class Failure(val error: UserRequestErrorType) : UserModelApiResult

    enum class UserRequestErrorType {
        // ...
    }
}

こうすることで、呼び出し元は UserModelApiResultUserRequestErrorType を知っておくだけで、エラーを網羅的に処理することができます。もちろん、呼び出し元が詳しいエラー情報を必要としないならば、エラーを null などで一括りにし、「エラーが発生した」という情報だけを返してもよいでしょう。 (参考: https://techblog.lycorp.co.jp/ja/20231109a

この考え方は特に、モジュールやレイヤーの境界部分で重要です。例えば、データレイヤ中にデータベースやネットワークに接続しているコードがある場合、データレイヤより上位のレイヤーでは、データベースやネットワークのエラーをそのまま返すのではなく、再構成や抽象化、余分な情報の削除などを検討してください。

一言まとめ

複数のエラー表現を単一のエラー表現に統合することで、呼び出し元での取り扱いを統一する。

キーワード: error, model conversion, abstraction

コード品質向上のテクニックの他の記事を読む

コード品質向上のテクニックの記事一覧