こんにちは。コミュニケーションアプリ「LINE」のモバイルクライアントを開発している石川です。
この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 67 回です。 LINEヤフー社内には、高い開発生産性を維持するための Review Committee という活動があります。ここで集まった知見を、Weekly Report と称して毎週社内に共有しており、その一部を本ブログ上でも公開しています。(Weekly Report の詳細については、過去の記事一覧を参照してください)
過ぎたるエラーは猶及ばざるが如し
以下の queryUserModel
は、ユーザのデータモデルのクエリ結果を ApiResult<UserListQueryResponse?>?
という戻り値型として返す関数です。ここで ApiResult
と UserListQueryResponse
はそれぞれ 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 つの可能性があります。
null
を返すApiResult.Failure
を返すApiResult.Success
を返し、そのvalue
がnull
ApiResult.Success
を返し、そのvalue
がUserListQueryResponse.Failure
ApiResult.Success
を返し、そのvalue
がUserListQueryResponse.Success
であり、そのuserModel
がnull
エラーを示す複数の方法が存在すると、呼び出し元にとって、何のエラーがどのような意味を持つのかが分かりにくくなります。たとえエラーの形式が異なったとしても、結局は呼び出し元でのエラー処理が同じになることも多く、単にコード煩雑にするだけという結果も招きがちです。これは、呼び出し先の都合で使っているエラーを、そのまま呼び出し元に渡している ことが原因です。
これを解決するためには、呼び出し元が使いやすい・必要とするエラー表現に変換する とよいでしょう。特に多くの場合、 エラーの状態を単一の一貫した表現にまとめる と上手くいくことが多いです。たとえば次の実装では、成功・失敗の区別を 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 {
// ...
}
}
こうすることで、呼び出し元は UserModelApiResult
と UserRequestErrorType
を知っておくだけで、エラーを網羅的に処理することができます。もちろん、呼び出し元が詳しいエラー情報を必要としないならば、エラーを null
などで一括りにし、「エラーが発生した」という情報だけを返してもよいでしょう。 (参考: https://techblog.lycorp.co.jp/ja/20231109a)
この考え方は特に、モジュールやレイヤーの境界部分で重要です。例えば、データレイヤ中にデータベースやネットワークに接続しているコードがある場合、データレイヤより上位のレイヤーでは、データベースやネットワークのエラーをそのまま返すのではなく、再構成や抽象化、余分な情報の削除な どを検討してください。
一言まとめ
複数のエラー表現を単一のエラー表現に統合することで、呼び出し元での取り扱いを統一する。
キーワード: error
, model conversion
, abstraction