内容
- 目的: 今一度swift2のエラーハンドリングを復習しておこう
- 目次:
- Error handling in objc
- NSError
- Error handling in swift
- try, do-catch, defer
- Yet another approach: Results
- Error handling in objc
Error handling in objc
NSError
- unix process
- 0 ... 正常終了
- 1~255 ... 異常
- NSError
- code: Int
- domain: String. code重複の防止
- userInfo: [NSObject: AnyObject]?
- reasonやメッセージを渡す
(NSError **) を最後の引数に入れるパターン
NSError *error = nil;
BOOL success = [[NSFileManager defaultManager] moveItemAtPath:@"/path/to/target"
toPath:@"/path/to/destination"
error:&error];
if (!success) {
NSLog(@"%@", error);
}
コールバックの最後の引数に (NSError *)を入れるパターン
NSURL *URL = [NSURL URLWithString:@"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!data) {
NSLog(@"%@", error);
} else {
// ...
}
}] resume];
問題点
- 冗長
- エラーチェックを忘れがち
- エラーチェックを忘れてもコンパイルエラーにはならない
- エラー時のclose処理などが簡潔に書けない
- goto?
Error handling in swift
Error handling in swift
- 簡潔に書きたい
- エラー処理忘れをコンパイル時にチェックしたい
- closeなどの復帰処理を簡潔に
ErrorType
- ErrorTypeは空のprotocol
- enumでもstructでもclassでも良い
- throw文を使ってerrorを投げる
func canThrowErrors() throws -> String
Handling Error
- 4つのハンドリング方法
- propagation
- do-catch
- optional value
- assert
Swift book より
When a function throws an error, it changes the flow of your program, so it’s important that you can quickly identify places in your code that can throw errors
どこでエラーが起こるのかが、すぐに特定できることが重要
コスト
- Swift error handlingはcall stackを辿ったりはしない
- コストはほぼ通常のreturnと同じ
Propagating Errors Using Throwing Functions
- tryキーワードをメソッド呼び出しの前につける
try vendingMachine.vend(itemNamed: name)
Handling Errors Using do-catch
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
print(error)
}
Handling Errors Using do-catch
- do節内でErrorが投げられたら, マッチするcatch節に処理が移る
- pattern matching(where)
- patternを書かないとerrorにbindされる(default)
- 必ず全てをcatchする必要はなくて、一部のエラーは伝搬させるということもできる
Converting Errors to Optional Values
-
try?
を使うと結果をoptionalで取得できる
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
Converting Errors to Optional Values
- すぐにreturnする場合など、処理が単純な場合は簡潔に書ける
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
Assert
try!
- エラーが基本発生しないとわかっている時は、値をunwrapしてruntimeにassertionさせる
let photo = try! loadImage("./Resources/John Appleseed.jpg")
Cleanup Actions
- finally?
- つ defer
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// Work with the file.
}
// close(file) is called here, at the end of the scope.
}
}
まとめ
- 簡潔に書きたい
- try!, try?, guard, let, defer
- エラー処理忘れをコンパイル時にチェックしたい
- throws でエラーの発生可能性明示
- コンパイル時にチェック
- closeなどの復帰処理を簡潔に
- defer
他言語との比較
- Java: 検査例外
- 無視することができない
- catch (Exception e){}による握りつぶしが横行
- C#
- 非検査例外
- catch し忘れ
- Swift
- 検査例外に近い
- tryでエラーの起こる箇所の明示
- 実行時のオーバーヘッドが少ない
Results
- Result : 「成功かもしくは失敗」を表現した型
- Optionalにさらに付加情報を加えたもの
- メソッドの戻り値にResultを使う
- パターンマッチが使える
enum Result {
case .Success(T)
case .Error(E)
}
enum Optional<T> {
case .Some(T)
case .None
}
antitypical/Result
- デファクト
- ReactiveCocoa, APIKitなどいろんなライブラリで使われている
map and flatMap, making Result more composable than throw
- materialize/dematerialize でthrowsと変換
func stringForKey(json: JSONObject, key: String) -> Result<String, JSONError> {
...
}
switch stringForKey(json, key: "email") {
case let .Success(email):
print("The email is \(email)")
case let .Failure(JSONError.NoSuchKey(key)):
print("\(key) is not a valid key")
case .Failure(JSONError.TypeMismatch):
print("Didn't have the right type")
}
map, flatMap, mapError, flatMapError
let idResult = intForKey(json, key:"id").map { id in String(id) }
let keyStringVal = stringForKey(json, key: "key").flatMap {
stringForKey(json, key: $0)
}
- Errorの型が合わない時はmapError, flatMapErrorで型を合わせる
- Rustのtry! macroはこのアプローチをさらに書きやすくしたもの
今後への期待
- 非同期例外
- C#, JavaScriptのような async await catch
- 言語側でのResultのサポート