iOSアプリ開発でエラーモデル/エラーハンドリングを考える時にいつも悩んでいたのですが、良い感じに1つのパターンに落とし込めたので公開メモとして書いておきます。
あくまでパターンの1つなので全てに当てはまる訳ではないです。
最近考えてたポエムで、「正常系ではない処理ってビジネスの都合が大きく絡むものが多いので、エラーがビジネスロジックになるのは当たり前」というのがあります。つまり、「エラーハンドリングはこれ!」というのはなく、要件によってエラーハンドリングは毎度違って当たり前ということです。
(エラー知見を探してもなかなか出てこないのはこれが原因ですね
仮想要件
クライアントのエラー、サーバーのエラー、アカウントのエラーの3つでエラーを分類したい。各エラーには2桁のエラーコードが割り振られている。これを表現するモデルを作りたい
実装
extension Error where Self: RawRepresentable, Self.RawValue == Int {
var errorCode: Int {
return rawValue
}
}
enum ProjectError: Error {
// 00 ~ 09
enum ClientError: Int, Error {
case invalidInputAge = 0
case invalidInputName = 1
case itemExpired = 2
case requiredUpdate = 3
// 色々あって
case unknown = 9
}
// 10 ~ 20
enum ServerError: Int, Error {
case flightMode = 10
case badNetwork = 11
case invalidResponse = 12
// 色々あって
case unknown = 19
}
// 20 ~ 29
enum AccountError: Int, Error {
case updated = 21
case delegate = 22
case anotherUserAssociated = 23
// 色々あって
case unknown = 29
}
case client(ClientError)
case server(ServerError)
case account(AccountError)
}
extension ProjectError {
var errorCode: Int {
switch self {
case .client(let error): return error.errorCode
case .server(let error): return error.errorCode
case .account(let error): return error.errorCode
}
}
}
ハンドリング
アプリでは、この形式でエラーを使っていきます(Stringは仮です
let result: Result<String, ProjectError> = .success("")
自分がハンドリング可能なエラーが投げられた時は、それだけ捕まえて処理をします。
例えばこんな感じとか。
switch result {
case .success(let value):
completion(value)
case .failure(let error):
guard case .client(let clientError) = error else {
completion(error)
return
}
guard case .invalidInputAge = clientError else {
completion(error)
return
}
self.handleSpecificError()
}
自分が処理できないエラーは上位のモジュールに返していきます。iOSの場合はだいたい最後ViewControllerになりますね。
最後の処理というのは共通エラーハンドリングが多いイメージです。例えば、ダイアログを出すとか。(ex ネットワーク状況がよくありません。しばらくしてから再度お試しください。とか
さいご
エラーハンドリングに正解はなく、ビジネスの都合がモリモリで困難でありソフトウェアエンジニアの腕が試される箇所です。(先輩の受け売り)
エラー設計をより良く、より楽しんでいきましょう!😇