Revise Error handling in Swift

## 内容

- 目的: 今一度swift2のエラーハンドリングを復習しておこう
- 目次:
    - Error handling in objc
        - NSError
    - Error handling in swift
        - try, do-catch, defer
        - Yet another approach: Results

---

# 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

- ４つのハンドリング方法
    - 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<T, E> : 「成功かもしくは失敗」を表現した型
- Optionalにさらに付加情報を加えたもの
- メソッドの戻り値にResultを使う
    - パターンマッチが使える

---

```
enum Result {
   case .Success(T)
   case .Error(E)
}
```

```
enum Optional<T> {
    case .Some(T)
    case .None
}
```

---

# [antitypical/Result](https://github.com/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のサポート

---

# References

- [Swift book: Error handling](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html)
- [NSHipstar: NSError](http://nshipster.com/nserror/)
- [NSError Class reference](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSError_Class/)
- [Three Stories of Error Handling in Swift](https://realm.io/news/tryswift-yuta-koshizawa-error-handling-swift/)
- [antitypical/Result](https://github.com/antitypical/Result)
- [Rust book: Error Handling](https://doc.rust-lang.org/book/error-handling.html)