🕊
[Swift] イニシャライズで失敗する場合は nil を返すのではなく throw しよう
伝えたいこと
- イニシャライズで失敗する場合は
nil
を返すのではなく、エラーをthrow
しよう- メリット
-
throw
した先でnil
にもできる -
throw
されたエラーをさらにthrow
することもできる - エラーの定義によっては、内容を出し分けることや
enum
の連想値で情報を持たせることもできる
-
- デメリット
- 特になし
- メリット
nil
を返却した場合
(before) 例えば、引数の moge
と fuga
が正の値でなければイニシャライズできない struct
を用意します。
struct NilHoge {
var moge: Int
var fuga: Int
init?(moge: Int, fuga: Int) {
guard moge > 0 else {
return nil
}
guard fuga > 0 else {
return nil
}
self.moge = moge
self.fuga = fuga
}
}
nil
をアンラップすることしかできない
イニシャライズに失敗した場合は 以下のように、イニシャライズに失敗した場合は nil をアンラップすることしかできません。
func testFuncNilHoge(moge: Int, fuga: Int) -> NilHoge? {
guard let nilHoge = NilHoge(moge: moge, fuga: fuga) else {
print("Initialization failed")
return nil
}
print(nilHoge)
return nilHoge
}
testFuncNilHoge(moge: 1, fuga: -1) // Initialization failed
これでは nil
の意味が不透明であり、何によってイニシャライズが失敗したかの情報が nil
だけでは表すことができません。
(なんで nil
になったのか、moge
の値がよくなかったのか、fuga
の値がよくなかったのかが分からない)
throw
した場合
(after) エラーを enum HogeInitializeError: Error {
case mogeError(Int)
case fugaError(Int)
}
struct ThrowsHoge {
var moge: Int
var fuga: Int
init(moge: Int, fuga: Int) throws {
guard moge > 0 else {
throw HogeInitializeError.mogeError(moge) // <-
}
guard fuga > 0 else {
throw HogeInitializeError.mogeError(fuga) // <-
}
self.moge = moge
self.fuga = fuga
}
}
以下のパターンのように、throw
されたエラーを好きなようにハンドリングすることができます。
try?
して guard let
でアンラップする
(パターン1) このパターンはエラーを throw
する価値はありませんが、nil
を返却した場合と同じように処理を書くことができます。
func testFuncThrowsHoge1(moge: Int, fuga: Int) -> ThrowsHoge? {
guard let throwsHoge = try? ThrowsHoge(moge: moge, fuga: fuga) else {
print("Initialization failed")
return nil
}
print(throwsHoge)
return throwsHoge
}
testFuncThrowsHoge1(moge: 1, fuga: -1) // Initialization failed
do-catch
でエラーハンドリングする
(パターン2) 以下のように do-catch
でエラーハンドリングが可能です。
func testFuncThrowsHoge2(moge: Int, fuga: Int) -> ThrowsHoge {
do {
let throwsHoge = try ThrowsHoge(moge: moge, fuga: fuga)
print(throwsHoge)
return throwsHoge
} catch {
fatalError("\(error)")
}
}
testFuncThrowsHoge2(moge: 1, fuga: -1) // __lldb_expr_222/init thorow.playground:66: Fatal error: mogeError(-1)
上記のように throw
されたエラーの内容を読みとこることができます。
throw
する
(パターン3) さらに さらに throw
することも可能です。
func testFuncThrowsHoge3(moge: Int, fuga: Int) throws -> ThrowsHoge {
let throwsHoge = try ThrowsHoge(moge: moge, fuga: fuga)
print(throwsHoge)
return throwsHoge
}
// 3-1. さらに throws することも可能
func testFunc() throws {
try testFuncThrowsHoge3(moge: 1, fuga: -1)
}
// 3-2. do-catch でハンドリング可能
do {
try testFuncThrowsHoge3(moge: 1, fuga: -1)
} catch {
print("\(error)") // mogeError(-1)
}
throw
することで、必要なレイヤーにエラーをハンドリングの処理をまとめることもできます。
結論(「伝えたいこと」の繰り返し)
- イニシャライズで失敗する場合は
nil
を返すのではなく、エラーをthrow
しよう- メリット
-
throw
した先でnil
にもできる -
throw
されたエラーをさらにthrow
することもできる - エラーの定義によっては、内容を出し分けることや
enum
の連想値で情報を持たせることもできる
-
- デメリット
- 特になし
- メリット
以上になります。
Discussion