SwiftはどのようにJavaの検査例外を改善したか

Swift には Java の検査例外によく似た仕組みがあります。

```swift
func foo() throws -> String { ... } // Java のような `throws` 節
```

```swift
let s = foo() // `catch` しないとコンパイルエラー
```

```swift
do {
  let s = try foo() // OK
} catch let error {
  // エラー処理
}
```

僕は、 Java の検査例外のコンセプトは素晴らしいと考えていますが、世間ではあまり好かれていないようです。 C# や Scala, Kotlin などの後続言語では採用されず、僕の知る限り Java 以降、検査例外（的なもの）を採用したメジャー言語は Swift だけです。

ただし、Swift の検査例外（的なもの）はいくつかの点で Java の検査例外と異なっています。 Swift は後続だけあって何かしらの改善を試みているわけです。その取り組みがおもしろいので、 **Swift の検査例外的なものが Java の検査例外と何が違い、それがどのような意味を持つのかを紹介します**。

## `throws` 節でエラーの型を指定できない

Java では

```java
// Java
String foo() throws FooException { ... }
```

のように `throws` 節に例外の型を記述することができますが、 Swift では現時点（ Swift 3 ）ではできません。単に `throws` と書くことができるだけで、 `throws` の後ろにエラーの型を記述することはできません。

```swift
// Swift
func foo() throws -> String { ... }
```

これだと Java の **非** 検査例外と同じに見えるかもしれません。しかし、 **非** 検査例外とは大きな違いがあります。何が検査例外的なのかと言うと、 `throws` の付いた関数／メソッドをコールするときには `catch` が強制されるという点です。 `catch` するか `throws` の付いた関数／メソッドの中でコールしないとコンパイルエラーになります。 Java の **非** 検査例外は `catch` が強制されないので大きく違います。

```swift
// Swift
let s = foo() // コンパイルエラー
```

```swift
// Swift
do {
  let s = try foo() // OK
} catch let error {
  // エラー処理
}
```

### エラーの型を指定できなくて大丈夫か

`throws` 節にエラー型を指定できないと網羅的なエラー処理ができません。それで問題ないのでしょうか？

[Swift ではエラーを 4 種類に分け](http://qiita.com/koher/items/a7a12e7e18d2bb7d8c77)、そのうち Simple domain error を `Optional` の `nil` で、 Recoverable error を `throws` で表します（その他の 2 種類のエラーは実行時にハンドリングしません）。 Simple domain error は `String` をパースして `Int` に変換するような、主にエラーが発生する前提条件が明確で失敗したことだけがわかればよい場合に利用されます。 Recoverable error は I/O やネットワーク関連の処理など、多様な原因（外的な要因を含む）によってエラーが引き起こされる場合に利用されます。

`throws` が使われるのは Recoverable error についてなので、原因を網羅して記述するようなことは滅多にありません。大抵は、一つか二つのエラーだけに特別なエラー処理を記述し、後はその他としてまとめて処理するはずです。

```swift
// Swift
do {
  let data = try download(url)
} catch NetworkError.unconnected // ネットワークに接続できない場合
  print("ネットワークに接続できません。設定を確認して下さい。")
} catch { // その他のエラー
  print("サーバーエラーです。しばらくしてから再度アクセスして下さい。")
}
```

ネットワークエラーの原因を羅列して網羅的に（たとえばレスポンスのステータスコードごとに）エラー処理を書いた経験がある人などほぼいないはずです。エラーの型が厳密であることは素晴らしいですが、それがなくても現実的に困ることは多くありません（ただ、 `throws` 節に型が記述できるとドキュメントとしての価値はあると思います）。

Java の検査例外には二つの性質があり、 `catch` して **エラーを処理することを強制する** ことと、起こり得るエラーを網羅することです。相対的に重要なのは前者です。その点で Swift の `throws` 節にエラー型を記述できないという仕様は、 Java でよく見られるサブクラスでメソッドをオーバーライドするときにエラーの型を足せないなどの問題を回避しつつも、最も重要なエラー処理を強制することだけは担保する、バランスの良い選択のように思えます（ただ、個人的にはスーパークラスで想定していないエラーがサブクラスで発生するのは、そのオーバーライド自体が誤りなのではないかと思います）。

### Swift 5 か 6 でエラーの型を指定できるようになるかも？

とはいえ、エラーの型が厳密だとうれしいと考える人は多いので（僕も、実際には指定できるとうれしいように思います）、実は今、 [Swift の言語仕様を話し合うメーリングリスト](https://lists.swift.org/mailman/listinfo/swift-evolution)上で `throws` 節でエラー型を指定できるようにしようという話が出て[議論されています](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170220/032603.html)。ただし、その場合でも　**Java と違い `throws` 節には一つのエラー型だけを記述できるようにしよう** という案が有力です。僕はこれはなかなかいい案だと考えています。

`throws` 節で指定されるエラー型は API のシグネチャの一部です。つまり、その関数／メソッドの実装に引っぱられるのではなく、その API の視点でとらえた抽象度でエラーの種類が記述されるべきです。たとえば、コンテンツの ID を指定してファイルをダウンロードし、ローカルの DB に保存する関数を考えましょう。結果として、成功か、不正な ID が指定されたか、それともダウンロードに失敗したかだけを知りたいとします。そのようなケースでは、内部でネットワークエラーが起ころうが DB エラーが起ころうが、全部「ダウンロードの失敗」として扱いたいはずです。 `IOException` や `SQLException` をそのまま上層に投げるのは正しい設計ではありません。 `DownloadException` という例外を作って、その `cause` に `IOException` や `SQLException` を渡すべきです。

しかし、 Java では `throws` 節に複数の例外の型が記述できるがために、内部で発生した例外がルーズにそのまま上層に投げられるケースが多いように思います。きっと一度は `IOException` や `SQLException` がずらずらと並べられた `throws` 節を見たことがあるでしょう。もしエラー型が一つしか記述できなければ別の抽象度のエラーでラップすることが半強制されるため、そのような事態を改善することができます。

なお、 Swift では `enum` をエラー型にすることができるので、複数の原因を一つのエラー型で表すのは簡単です。たとえば、

- ネットワークに未接続
- タイムアウト
- レスポンスのステータスコードが不正

のいずれかを表す `NetworkError` は次のように記述できます。

```swift
// Swift
enum NetworkError: Error {
  case unconnected
  case timeout
  case badResponse(statusCode: Int)
}
```

このような Tagged Union 的なデータ構造が作れないと、一つのエラー型しか記述できないのは現実的でないかもしれません。

### `throws` 節にエラーの型を記述できない、もしくは一つだけ記述できることの評価

正直、この点に関しては必ずしも「改善」とは呼べないと思います。選択の問題であり、ユースケースや好みに左右されるでしょう。説明の順序の問題で本節の内容を最初に持ってきましたが、次節以降が「改善」と呼べるものだと考えています。

## `try` - `catch` ではなく `do` - `try` - `catch` する

Java の `try` - `catch` には Implicit control flow problem があります。コードを見てもどこの行で例外が `throw` され得るのかということがひと目でわからないという問題です。

```java
// Java
try {
  foo();
  bar();
  baz();
} catch (QuxException e) {
  // どこから来たの？？
}
```

これが困るのは、たとえば `bar` で発生し得る `QuxException` を `catch` しているつもりなのに、間違えて `baz` で発生する `QuxException` も `catch` してしまい不適切なエラー処理をしてしまったというようなケースです。 `IOException` や `SQLException` のような、関連する複数のメソッドで同じ種類の例外が投げられる場合にこういうことが起こりがちです。

しかし、 Swift では Marked propagation によってこの問題を軽減しています。 Marked propagation とは次のコードのように、エラーを `throw` し得る関数／メソッドをコールする箇所にマーク（ Swift では `try` ）を付けなければならないというものです。

```swift
// Swift
do {
  foo(); // `try` がないのでここから `throw` されることはない
  try bar(); // ここでエラーが `throw` され得る
  baz(); // `try` がないのでここから `throw` されることはない
} catch let error {
  // bar() からのみ来る
}
```

これなら `try` を書くときにそこからの `throw` も `catch` してよいかプログラマが判断する機会が必ず与えられることになり、誤って `catch` に巻き込んでしまうことを防げます。

なお、このマークとして `try` というキーワードを使うので、 Swift では Java の `try` の意味で `do` を使います（実はこの Swift の `do` が Haskell の `do` 記法と理論的に対応しており、 Swift の `try` は Haskell の `<-` と対応するのもおもしろい点です）。

## `try!` でエラーを握りつぶすことなくコンパイラを黙らせる

この節で述べることは本投稿で **最も重要なこと** です。本当は投稿の最初に書きたかったんですが、説明の順序の問題でこの位置になっています。

Java の検査例外で一番辛いのが、例外が発生しないことがわかっているケースで例外処理を回避する簡単な方法がないことです。たとえば、次のコードでは UI 側でがんばって入力できるフォーマットが強制されている場合、 `toDate` は絶対に失敗しません。無駄なエラー処理は書きたくありませんが、 Java では次のようなコードを書かなければなりません。

```java
// Java
Date date;
try {
  date = toDate(string);
} catch (FormatException e) { // ここには絶対に到達しない
  throw new RuntimeException("Never reaches here.", e);
}
```

Swift では、 `try` に `!` を付けて `try!` とするだけです。

```swift
// Swift
let date = try! toDate(string) // `catch` は必要ない
```

もし `try!` が付与されているにも関わらず `toDate` がエラーを `throw` した場合にはクラッシュします。これは、 `try!` は前述のエラー分類上 Logic failure （コードそのものの誤り）として扱われており、実行時に動的に処理すべきものではない（コードを修正することで対応すべき）と考えられているからです。これは、 Java でいう `RuntimeException` に対応します（ Java の `RuntimeException` は `catch` することができますが、 Effective Java では Programming error を表すために `RuntimeException` を使い、基本的に `catch` しないことが推奨されています）。

`try!` のような仕組みが Java になくて **一番問題なのは、コードを書くのが面倒なことではなく、コンパイラを黙らせるために例外が握りつぶされてしまうことです**。

Java でコンパイラを黙らせる一番簡単な（短いコードで書ける）方法は次のように例外を握りつぶすことです。

```java
// Java
try {
  foo();
} catch (FooException e) { // 例外を握りつぶす
}
```

このようなコードは例外が発生しないことを想定していても、もしコードのミスで例外が発生してしまった場合に予期せぬ挙動をし、バグの原因を解明することを困難にします。最悪なのは、よくわかっていない初心者がコンパイラを黙らせるために上記のようなコードを書いてしまうことです。例外を握りつぶす行為は例外を `catch` し忘れることよりもひどい事態を引き起こします。安全なエラー処理を実現するための検査例外が、危険なコードを生んでしまうのでは本末転倒です。

Swift でもエラーを握りつぶすことはできますが、そのコードは `try!` よりも長いので、コンパイラを黙らせるためにエラーを握りつぶすということは起きづらいです。

```swift
// Swift
// `try!` でコンパイラを黙らせる
try! foo()
```

```swift
// Swift
// エラーを握りつぶしてコンパイラを黙らせる
do {
  foo()
} catch {
}
```

このように、 **エラー処理を強制する構文を持つ言語は、エラーを握りつぶすよりも簡単な方法でエラー処理を回避（して非検査なエラー処理に）させる方法を提供することが重要です**。

5, 6 年ほど前に僕と @omochimetaru が↓のような構文が Java にほしいと話していたんですが、まさにそれと同じようなものを Swift は実現してくれました。 Java にもこのような構文が追加されるとうれしいのですが・・・。

```java
// Java
foo()!; // 検査例外の非検査例外化
```

## 高階関数と `rethrows`

ときどき、検査例外は高階関数／メソッドを用いた関数型プログラミングと相性が悪いという話を聞きます。

高階関数／メソッドとは次のようなものです。たとえば、 `map` メソッドを使えば次のように簡単に要素を 2 乗することができます。

```java
// Java
Stream<Integer> numbers = Stream.of(2, 3, 5);
Stream<Integer> squared = numbers.map(x -> x * x); // 4, 9, 25
```

しかし、 Java では `map` に検査例外を `throw` し得る `Function` を渡すことができません。

```java
// Java
Stream<String> strings = Stream.of("2017-03-09", "unknown", "1980-01-01");
try {
  Stream<Date> dates = strings.map(s -> toDate(s)); // コンパイルエラー
} catch (FormatException e) {
  // エラー処理
}
```

このようなコードを書きたいのですが、 `toDate` が `throw` し得る検査例外 `FormatException` を `map` に渡した `Function` が `catch` していないのでコンパイルエラーになります。

Swift には `rethrows` という仕組みがあり、 `map` に渡す関数がエラーを `throw` しないなら `map` もエラーを `throw` しないメソッドとして振る舞い、

```swift
// Swift
let numbers = [2, 3, 5]
let squared = numbers.map { $0 * $0 } // 4, 9, 25
```

`map` に渡す関数がエラーを `throw` する場合には `map` もエラーを `throw` するメソッドとして振る舞うということを実現することができます。

```swift
// Swift
let strings = ["2017-03-09", "unknown", "1980-01-01"]
do {
  let dates = try strings.map { toDate($0) } // OK: `map` が `throws` なメソッドになる
} catch error {
  // エラー処理
}
```

Java でも `throws` で指定する例外の種類が一つだけで良いのであれば↓にようにして似たようなことは実現できます。

```java
// Java
interface ThrowableFunction<T, R, E> {
  R apply(T t) throws E;
}
```

```java
// Java
interface Stream<T> {
  ...
  // `map` のオーバーロード
  <R> Stream<R> map(ThrowableFunction<? super T,? extends R, ? extends E> mapper) throws E;
}
```

しかし、 `map` が複数の種類の例外を `throws` し得るようにするには `rethrows` のような新しい構文が必要になるでしょう。

余談ですが、 Swift の `throws` 節にエラー型が一つだけ書けるようになった場合には、 `rethrows` を[次のように表す案が提案がされています](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170213/032267.html)。

```swift
func foo<E: Error>(f: () throws E -> ()) throws E { ... }
```

エラーを `throw` しない関数では `E` に `Never` を指定することになります。 Swift 3 時点では `Never` is an `Error` ではないですが、 `Never` は Scala や Kotlin の `Nothing` のようなものであり、 `Nothing` は [Bottom type](https://en.wikipedia.org/wiki/Bottom_type) （すべての型のサブタイプ）です。もし　Swift の `Never` も Bottom type になれば、 `Never` is an `Error` ということになり、この解釈は自然です。 `throws` なしの関数を `throws Never` と解釈するのはおもしろいです。

## `finally` ではなく `defer` する

Swift には `finally` はありません。代わりに Go 言語のような（ただし関数レベルではなくスコープレベルで実行される） `defer` があり、これを使って終了処理を書きます。 `defer` の一番わかりやすい利点は、開始処理と終了処理がコード上で近くなり、コードの見通しがよくなることです。

```swift
// Swift
do {
  let foo = Foo.open("foo") // 開始処理
  defer { // スコープを抜けるときに実行される
    foo.close() // 終了処理: 開始処理の近くに書ける
  }

  // 長い処理
} catch {
  // エラー処理
}
```

複数の終了処理が必要なケースでも `defer` はうまく機能します。

```swift
// Swift
do {
  let foo = Foo.open("foo")
  defer {
    foo.close()
  }

  // 何らかの処理

  let bar = Bar.start("bar")
  defer {
    bar.stop()
  }

  // 何らかの処理

  let baz = Baz.create("baz")
  defer {
    baz.destroy()
  }

  // 何らかの処理
} catch {
  // エラー処理
}
```

これを Java で書こうとすると `finally` で条件分岐を書くか、 `try` - `catch` をネストしなければなりません。

```java
// Java
Foo foo = null;
Bar bar = null;
Baz baz = null;
try {
  foo = Foo.open("foo");

  // 何らかの処理

  bar = Bar.start("bar");

  // 何らかの処理

  baz = Baz.create("baz");

  // 何らかの処理
} catch (FooException|BarException|BazException e) {
  // エラー処理
} finally {
  // 適切に条件分岐して終了処理を記述しなければならない
  if (baz != null) {
    baz.destroy();
  }

  if (bar != null) {
    bar.stop();
  }

  if (foo != null) {
    foo.close();
  }
}
```

```java
// Java
try (Foo foo = Foo.open("foo")) {
  // 何らかの処理

  try (Bar bar = Bar.open("bar")) {
    // 何らかの処理

    try (Baz baz = Baz.open("baz")) {
      // ネストが深い
    } catch (BazException e) {
      // エラー処理
    }
  } catch (BarException e) {
    // エラー処理
  }
} catch (FooException e) {
  // エラー処理
}
```

try-with-resources 自体は僕は好きですが、上記のようなケースでネストが深くなってしまうのは微妙です。また、 try-with-resources 方式はスコープをまたいで（主にメソッドをまたいで） `close` しなければならないようなケースでは使えないので、 try-with-resources が使えなくても便利な構文はやはり必要です。

なお、 Swift でも try-with-resources と同じことは次のようにして実現できます。

```swift
// Swift
extension Foo {
  static func open(_ input: String, operation: () throws -> ()) rethrows {
    let foo = Foo.open(input)
    operation()
    foo.close()
  }
}
```

```swift
// Swift
do {
  Foo.open("foo") {
    // 何らかの処理
  } // スコープを抜けたら `close`
} catch let error {
  // エラー処理
}
```

## Swift の検査例外（的なもの）が Java の検査例外と比べて劣っているところ

本投稿は Swift の検査例外（的なもの）が Java の検査例外を「改善」した点を紹介する目的のものなので、 Swift の良い点ばかりを挙げてきましたが、 Swift がすべての点で Java に優れているわけではありません。本題とずれますが、 Swift の検査例外（的なもの）を使っていて困っていることも簡単に紹介します。

### エラーからスタックトレースが取得できない

当然 Swift にもコールスタックは存在しますが、 `Error` からスタックトレースを取得することができません。これは、 Swift がシステム言語の領域でも有用な言語を目指しており、速度を重視していることを考えるとしかたない選択かもしれませんが、大して速度が重要でないアプリ開発などで Swift を使っていると不便です。

### Exception chaining のような仕組みがない

Swift の `Error` には `cause` でエラーをチェーンする、 Java の Exception chaining のような仕組みもありません。これも、エラーを上層の抽象度でラップし直そうと思うと辛い点です。

### 対応策

実は、 Swift の標準ライブラリにはエラーを `throw` する関数／メソッドは一つもありません（ Objective-C 由来の Foundation という付属ライブラリにはありますが、それは Swift 用に設計されたものではありません）。 Swift の標準ライブラリはミニマルな状態に保たれており、 `Int`, `Float`, `Double`, `Bool`, `Array`, `Dictionary` などの必要最低限の機能を提供するだけです。具象エラー型も一つもありません。

そのため、 Swift では基本的にはエラー型は利用者が自分で設計することになります。スタックトレースがほしければスタックトレースをもたせ、 Exception chaining がしたければそのような仕組みを作ることはできます。自分が作ったエラーの世界はすべてそのようにしてしまえば、 Java に近いことは実現できます（ためしに `cause` と `callStack` を持った `Exception` の[ライブラリを作ってみました](https://github.com/koher/exceftion)）。ただし、サードパーティのライブラリを使う場合にはその限りではありません。

## まとめ

- Swift では `throws` 節にエラーの型を記述できず、 `catch` の強制の意味だけを持つ
  - エラーを網羅的に処理するケースは少ないので、網羅性の重要度は低い
  - 最も重要な `catch` 忘れを防ぐことができ、かつサブクラスでエラー型が足せないという問題を回避できる
  - Swift にもエラー型指定は将来的に追加されるかもしれず、その場合でも `throws` 節で指定できるエラーの種類は一つになる予定
    - 一つしかエラー型を指定できないことで、その API の抽象度に合わせてエラーをラップすることが半強制される
- エラーを `throw` し得る関数／メソッドをコールする箇所ではマーク（ `try` ）を付与しないといけない
  - 想定していない関数／メソッドの `throw` するエラーを誤って巻き込んで `catch` してしまうことが防げる
- `try!` でエラーを `catch` せずに済ませられる（ Java 的に言えば `RuntimeException` 化）
  - `catch (Exception e) {}` などと握りつぶされてしまうことを防げる
- 高階関数に渡す関数が `throws` かどうかでその高階関数自身が `throws` かどうか決まる `rethrows` という仕組みがある
  - `map` や `filter` などを多用する関数型プログラミングのスタイルと相性が良い
- `finally` ではなく Go に似た `defer` によって終了処理を記述する
  - 終了処理のコードが開始処理の近くに記述できるので可読性が高い
  - Java のように `try` ブロックのどこまで進んだかによって必要な終了処理を分岐する必要がない
  - Swift で try-with-resources のようなこともできる
- Swift がすべて素晴らしいわけではない
  - エラーからスタックトレースがとれなくて辛い
  - Exception chaining のような仕組みもなくて辛い
  - 標準ライブラリには具象エラー型がないので、自分でコールスタックと `cause` を持ったエラー型を設計することはできる