「例外」がないからGo言語はイケてないとかって言ってるヤツが本当にイケてない件

　この記事は、[Go3 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/go3) の８日目の記事です。
　７日目は @codehex さんによる「[Go でアプリケーションとクライアントのミドルウェアを作成する方法知ってますか？](https://qiita.com/codehex/items/d3741b66b0ccee7a216d)」でした。

　本日はネタ全開でお送りいたします。


# Disclaimer（免責事項）

　はじめに言い訳というか、これを書いた経緯というか。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">プログラミング言語をdisる人をdisる芸を見たいですね！</p>&mdash; yet another (@Maki_Daisuke) <a href="https://twitter.com/Maki_Daisuke/status/1050205802684973056?ref_src=twsrc%5Etfw">2018年10月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

　というツイートをいたしまして、言った手前自分でやるか、と思い立った次第です。
　なので、ネタとしてお楽しみください。

　なお、炎上した場合にも、それすらもネタとして楽しむ所存ですのでアシカラズ。

　それでは、いってみましょう。


# Go言語がイケてない…だ…と……？

　Go言語はイケてない言語としてよくdisられているが、その中でも２大disられポイントがこれだ↓

- Genericsがないm9(^Д^)ﾌﾟｷﾞｬｰ
- 「例外」がないm9(　ﾟ,_ゝﾟ)ﾌﾞﾌﾞｯ

　前者をdisってるヤツらについてはすでに [別の人がdisっているので](https://methane.hatenablog.jp/entry/2017/09/19/Go_%E3%81%AB%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%82%AF%E3%82%B9%E3%81%8C%E3%81%AA%E3%81%8F%E3%81%A6%E3%82%82%E6%A7%8B%E3%82%8F%E3%81%AA%E3%81%84%E4%BA%BA%E3%81%9F%E3%81%A1%E3%81%AB%E5%AF%BE%E3%81%99) 、今回は後者、「例外」[^1] がないことをdisってるヤツらがいかにイケてないかdisることにする。

[^1]: 文中で「例外」とあえてカッコ書きにしているのは、暗にtry-throw-catchとその類型のことを指しています。本来的には例外とは処理の結果が通常どおりにはいかなかったことを指すので、それをreturnで表そうがthrowで表そうが構わないはずです。しかし、try-throw-catchがないことを指して「例外がない」と揶揄されることが多いので、「例外」とカッコ書きで使うことにしました。

　なお、これ以降のコード例では、ライブラリのインポートや冗長なクラス宣言などは省略することにする。


# 事実：達人たちは「例外」を使わない

　おまいら、Googleの「Google C++ Style Guide」を読んだことはあるか？[ここに日本語訳がある](https://ttsuki.github.io/styleguide/cppguide.ja.html)ので、まずは読んでからdisりに来い。
　ここにあるように、GoogleではC++のコーディングで `throw` の使用を禁じている。それによる恩恵よりもデメリットのほうが上回ると考えているからだ。
　これはなにもGoogleに限った話ではない。[LLVM Coding Standard も同様に例外機構の使用を禁じている](https://llvm.org/docs/CodingStandards.html#do-not-use-rtti-or-exceptions)。

　V8やLLVMといった、世界で最も利用されているであろう言語処理系を開発している達人たちは、「例外」を使っていないのだ。彼らの開発した処理系が例外機構を実現しているのというのに、なんとも皮肉な話である。

> いやいや、それはC++の話でしょ。C++はtry-catchの存在が考えられていなかった時代のコードとか、メモリ管理の煩雑さとかあるから、問題多いのはわかるよ。
> でも、Goはそういうしがらみないじゃん。新しい言語を設計するなら、Javaみたいに「例外」をいれるのがモダンな言語としては常識なんだよ。そんなことも知らないの？

　オーケー、わかった。アンタの言っている「例外」ってのはJavaのtry-throw-catchのことなわけね？で、アンタはJavaの「例外」をどれだけ理解しているわけ？


# そもそも「例外」ってなんなん？

　んじゃ一度、アンタらにとってのバイブル的な存在であるはずの『[Java Language Specification](https://docs.oracle.com/javase/specs/jls/se8/html/index.html)』に立ち返って、「例外」とはなんだったのか考えてみようじゃまいか。

## 呼び出し元に異なる型の値を返せる手段としての例外

　「[Chapter 11. Exceptions](https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html)」では次のように書かれている：

> Explicit use of throw statements provides an alternative to the old-fashioned style of handling error conditions by returning **funny values**, such as the integer value -1 where a negative value would not normally be expected.

※強調は筆者による

　奇妙な値（funny values）っていうのは、C言語でよく見られるこういうやつね↓

```C:C
pid_t funny = fork();
if ( funny < 0 ) {
    // pidがマイナス値だとエラー
    fprintf(stderr, "Can't fork");
    exit(1)
} else if ( funny == 0 ) {
    // 子プロセスでの処理
} else {
    // 親プロセスでの処理
}

```

　これはたしかに、いろいろと問題だ。
　具体的になにがエラー原因だったのかは `error` 変数を参照しなければいけなかったりするし、なにより、正常時に返す値の型でエラーが表現できない場合に、どうやって呼び出し元にエラーを通知したらいいものか？

　たとえば、次のようにint配列の平均を求める関数があったとしよう：

```C:C
int average(int* a, int n) {
    int s = 0;
    for ( size_t i=0; i < n; i++ ) s += a[i];
    return s / n;
}
```

　これを、次のように空配列に対して呼び出すと、

```C:C
int main (){
    int a[] = {};
    int r = average(a, 0);
    printf("%d\n", r);
}
```

　コアダンプしてしまう：

```
[1]    939 floating point exception (core dumped)
```

　`n == 0` の場合にはエラーを返すようにしたいが、呼び出し元にintしか返せないのに、どうやって呼び出し元に例外を知らせたらいいというのか？[^2] この関数は、正常な戻り値として 0 はもちろん負数だって返しうる。異常時のために使える"スキマ"はすでに int の中にはないのだ[^3]。

[^2]: 「そんなときのためにC言語には `longjump` という機能があってじゃな…」というC言語賢者のあなたは、下の大域脱出の話まで読み飛ばしてください。

[^3]: intにはスキマがなくても、floatだったら `NAN` (not a number) というスキマがある。割り算は小数が発生しうるのに、ここでの例をfloatではなくあえてintにしているのはこのため。まあ、説明のためであって、実用プログラムではないのでご理解ください。

　こんなとき、Javaだったらこう書ける：

```java:Java
static int average ( int[] a ) {
    if ( a.length == 0 ) {
        // 配列が空なら ArithmeticException を投げる
        throw new ArithmeticException("division by zero");
    }
    int s = 0;
    for ( int i=0; i < a.length; i++ ) s += a[i];
    return s / a.length;
}
```

　この関数は配列が空だったとき、

```java:Java
public static void main(string[] args) {
    int a[] = new int{};
    try{
        System.out.println(average(a));
    } catch ( ArithmeticException e ) {
        System.err.println(e)
    }
}
```

　例外によってエラーを教えてくれる。しかも、その原因は `ArithmeticException` という人間にもわかりやすい型で教えてくれるのだ。

> Every exception is represented by an instance of the class Throwable or one of its subclasses (§11.1). Such an object can be used to carry information from the point at which an exception occurs to the handler that catches it.

　メソッドの処理結果として（正常時の結果の型とは別に） `Throwable` を継承するオブジェクトを好きなように投げることができるので、このオブジェクトの中にエラーに関する情報などを入れれば、呼び出し元に例外についての詳細な情報も通知することができる。

　つまり、正常な場合と異常な場合とで異なる型の値を呼び出し元に返せるようにすることが、「例外」の役割の１つなわけだ。

## 大域脱出の手段としての例外

　また、「Chapter 11. Exceptions」では次のようにも書かれている：

> During the process of throwing an exception, the Java Virtual Machine abruptly completes, one by one, any expressions, statements, method and constructor invocations, initializers, and field initialization expressions that have begun but not completed execution in the current thread. This process continues until a handler is found that indicates that it handles that particular exception by naming the class of the exception or a superclass of the class of the exception (§11.2). If no such handler is found, then the exception may be handled by one of a hierarchy of uncaught exception handlers (§11.3) - thus **every effort is made to avoid letting an exception go unhandled.**

※強調は筆者による

　例外が発生したその場で処理されなかったとしても、呼び出し元を遡っていって、処理してくれるハンドラ（＝型が合致するcatch節）を探すことで、なんとかして例外が処理されるように努力を尽くすというようなことを言っている。

　つまり、こういうことだ：

```java:Java
static float variance ( int[] a ) {
    int s = 0;
    int av = average(a);
    for ( int v : a ) {
        v -= av;
        s += v*v;
    }
    return s / a.length;
}

public static void main(string[] args) {
    int a[] = new int{};
    try{
        System.out.println(variance(a));
    } catch ( ArithmeticException e ) {
        System.err.println(e)
    }
}
```

　`average` を呼び出している `variance` では例外を処理していないが、それを呼び出している `main` で見事に例外が補足されて処理されている。
　ここで、`average` 自身の実行はもちろん、それを呼び出している `variance` も一気に飛び越えて、`main` に実行が戻ってきている。こうした入れ子になった関数呼び出しを一気に巻き戻すことを「大域脱出」と呼ぶ。

　古式ゆかしいエラーを示す値を返す方法では、経験上、戻り値が無視されることが多かったと上で書いてあった。そのため、たとえその場では無視されてしまったとしても、呼び出し元の誰かが処理してくれることを期待して、その誰かの元へと一気に脱出することが、「例外」のもうひとつの役割だと言えよう。

　実際のところ、JavaScriptのように変数の型がない動的型付け言語では、return文で任意の型の値が返せてしまう：

```javascript:JavaScript
function average ( arr ) {
    if ( arr.length == 0 ) return new Error("division by zero");
    return arr.reduce((x, y) => x+y) / arr.length;
}

let r = average([]);
if ( r instanceof Error ) {
    console.log(`Something wrong!: ${r}`);
} else {
    console.log(`OK: ${r}`);
}
```

　そのため、動的型付け言語においては、事実上この大域脱出のみが例外機構の役割と言ってもいいだろう。
　つまり、return文で普通に返ってきたのではなく、throw文で大域脱出してきた場合には「なんか普通と違うことがあったな」と捉えるという不文律の上に、JavaScriptの「例外」は例外機構として成立している。

```javascript
function average ( arr ) {
    if ( arr.length == 0 ) throw new Error("division by zero");
    return arr.reduce((x, y) => x+y) / arr.length;
}

try {
    let r = average([]);
    console.log(`OK: ${r}`);
} catch ( e ) {  // if文よりもこう書いたほうが、「なんか普段と違う」感がある
    console.log(`Something wrong!: ${e}`);
}
```

　もちろん、これ自体は良い約束事だ。[^4]

[^4]: 裏を返せば、JavaScriptのtry-throw-catchのようなものを例外機構と認めている人にとっては「例外機構＝大域脱出」でしかないということを示しています。勝手な印象ですが、「例外」がないことをdisっている人の９割は大域脱出の部分しか意識していないように思います。

　なるほど、よく考えられてるわー。

> ほれみろ、やっぱり例外は叡智の結晶であってイケてる機能なのだ。そして、それがない言語はイケてないんだ！

　ちょっ！待てって。そんな結論あせんなし。


# 分けて議論しようじゃないか

　いわゆる「例外」が議論されるとき、上の２つが無意識にごっちゃになって議論されていることが話が混乱する原因じゃねーのかな？でも、この２つが不可分であると誰が決めたわけ？
　別々に提供されていても、例外機構としては問題ないんじゃないか？むしろ、別々になっているほうが良いこともあるんじゃね？

> は？別々？そんなの例外機構じゃねーよ

　だから決めつけんなし！まずは考えてみるべ。


## 複数の値を返せる多値返却

　Go言語の場合、throw に頼らずとも、異なる型の値を返すことができる：

```golang:Go
func average ( a []int ) (int, error) {  // 値を２つ返す関数
    if len(a) == 0 {
        return 0, errors.New("division by zero")
    }
    s := 0
    for _, i := range a {
        s += i
    }
    return s / len(a), nil
}

func main () {
    a := []int{}
    r, err := average(a)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("OK: %d\n", r)
}
```

　このように、正常時の結果とエラー時の原因を表す型の値（普通は `error` インターフェースを実装した値）の２つを関数から返すようにして、エラーの値をチェックするように書くのがGo言語の例外処理の流儀である。
　なので、try-throw-catchなんてものがなくても、例外処理はちゃんとできるのだ。

> 複数の値が返せるとか、キモっｗ
> そんな変な言語なんて誰が使ってんだよ？ｗｗ

　は？まじで言っちゃってんの？
　多値を返せる言語なんていくらでもあんべ。

　90年台にはCGIのデファクトスタンダードだったPerlだって↓

```perl:Perl5
sub average {
    my $a = shift;
    return (0, "division by zero") if @$a == 0;
    my $s = 0;
    $s += $_ foreach @$a;
    return $s / @$a, undef;
}

my ($r, $e) = average([]);
if ( defined $e ) {
    die $e;
}
print "OK: $r\n";
```

　機械学習の流行で今をときめくPythonだって[^5]↓

```python:Python
def average(a):
    if len(a) == 0:
        return 0, Exception("division by zero")
    s = 0
    for i in a:
        s += i
    return s / len(a), None

av, err = average([])
if err is not None:
    print err
    exit(1)
print av
```

[^5]: これは多値というより、タプルという１つの値なのでは？という意見もありましょうが、細かいことは置いといてください。

　超美しい型システムで有名なHaskellさんだって↓[^6]

```haskell:Haskell
average :: [Int] -> (Int, String)
average as = if (length as) == 0
                 then (0, "division by zero")
                 else ((foldl (+) 0 as) `div` (length as), "")
                                 
main = do (r, e) <- return (average [])
          if e /= "" then putStrLn e
                     else putStrLn $ "OK: " ++ show r
```

[^6]: これも多値というよりタプルなんですが、細かいことは(ry また、Haskellならこういう場合は`Maybe` なり `Either` なりを使うべきってご意見もあるでしょうが、モナドの説明をするにはこの余白はあまりにも狭す(ry

　みんな多値返却できるわ。
　むしろ、[引数が複数とれるのに返り値は複数かえせないことのほうがキモい](https://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E5%A4%9A%E5%80%A4#H-1ybck1o)んじゃ！

　多値返却がないから、「例外」とかいう大仰そうなもの持ち出してきて自分の無力さを偉そうに自慢するなんて、まじイケてねーわーｗ むしろ痛えわーｗｗ

> で、でも、それ大域脱出のほうができてねーじゃん！

　お？じゃあ、次いっちゃう？


## 大域脱出がないと誰が言った？

　たしかにGo言語にはthrowもcatchもないけど、誰も大域脱出ができないなんて言ってない。
　`panic` と `defer`、`recover`という機能があるんだぜ：

```golang:Go
func average ( a []int ) int {
    if len(a) == 0 {
        panic(errors.New("division by zero"))  // ここから↓のdeferまで一気に脱出する
    }
    s := 0
    for _, i := range a {
        s += i
    }
    return s / len(a)
}

func variance ( a []int ) float64 {
    s := 0.0
    av := average(a)
    for _, i := range a {
        t := (float64)(i - av)
        s += t * t
    }
    return s / (float64)(len(a))
}

func main () {
    defer func(){
        if e := recover(); e != nil {
            log.Fatal("Caught error: ", e)
        }
    }()
    a := []int{}
    r := variance(a)
    fmt.Printf("OK: %f\n", r)
}
```

　このように、`panic` を呼ぶとその呼び出し元の `defer` があるところにまで遡っていって、`defer` が見つかったらそれが実行される[^7]。そして、`defer` の中では `recover` を使って `panic` が投げた値を捕捉（catch）できるというわけ。
　ね？ちゃんと大域脱出できてんしょ？
　どこの `panic` でどんな値が投げられてくるのかは型チェックされないから、実質的には上で書いたJavaScriptの `throw` と同じことが実現できてるっつーわけ。

[^7]: 正確にいうと、`defer` が実行されるのは `panic` が呼ばれたときだけではありません。`defer` が書かれた関数から抜けるときに必ず実行されます。try-finally を知っている人にとっては、finally節と同じだと思えばいいでしょう。Goでは `finally` のなかで `catch` に相当する処理を行うわけですね。

> `defer` とか `recover` とか、予約語のセンスが意味わかんねーんだけど・・・

　それ言ったら、Javaやってないヤツからしたら、`throw` とか `catch` とか意味わかんねーんですけど？
　日本語しかしゃべれねーヤツが英語わかんねーとかブーたれてんのと一緒だべ？まじイケてねーこと言ってねーで、他言語も勉強しろや。
　それにな、[慣れるとこっちの書き方のほうがいいなって思える点もいろいろあんだよ](https://www.kaoriya.net/blog/2014/04/17/)！ブツクサいってねーで、慣れろ！！

## そんなにtry-catch風に書きたいなら

　こういうこともできる↓

```golang:Go
package main

import (
    . "github.com/mattn/go-try/try"
    "errors"
    "fmt"
)

func average (a []int) int {
    if len(a) == 0 {
        panic(errors.New("division by zero"))  // throwの代わりにpanicで例外を投げる
    }
    s := 0
    for _, i := range a {
        s += i
    }
    return s / len(a)
}

func main() {
    Try(func() {
        av := average([]int{})  // 空配列を渡しているので、
        fmt.Println(av)         // この行は実行されない
    }).Catch(func(e error) {
        fmt.Println(e)          // averageからここに飛んでくる
    })
}
```

　Go言語なら、その気になればtry-catch風の書き方をライブラリとして実装できるというわけだ。
　ただし、これは推奨された書き方ではない[^8]。

[^8]: 念のため、mattnさんのライブラリが良くないというわけではなく、Go言語の流儀ではないということです。

## なぜ Go は「大域脱出」を例外処理として採用しなかったのか？

> そんなんあるなら、じゃあなんで標準ライブラリで `if f, err := os.Open(...); err != nil { ... }` とかやってんだよ？全部 `panic` 使ってJavaっぽくすりゃ良かったじゃん？

　あー、それな。
　それについては、[Dave Cheneyさんが歴史をおって解説してくれている](https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right)。
　詳しくは全文を読んでほしいけど、一部を引用しておこう：

> **Java exceptions ceased to be exceptional at all, they became commonplace.** They are used from everything from the benign to the catastrophic, differentiating between the severity of exceptions falls to the caller of the function.

　いろんなことに例外を使った結果として、「Javaの例外はまったく例外的なものではなくなってしまった」と。そして、どの「例外」がどれくらい深刻なものなのか（つまり、正常な状態に復帰させるべき例外はどれで、すぐさまシステムを停止させるべき例外はどれか）を見分けるのは（例外を起こした側ではなくて）関数の呼び出し側に丸投げされることになってしまった。

　極めつけは、

> because every exception Java extends java.Exception any piece of code can catch it, even if it makes little sense to do so, leading to patterns like
> 
> `catch (e Exception) { // ignore }` [^9]

　こういうパターンがまかり通るようになってしまった。

[^9]: このコード片は引用元の記事からそのまま転載しています。懸命なJavarista[^10]諸氏はお気づきでしょうが、これは正しいJavaコードではありません。正しくはこう書くべきでしょう： `catch (Exception e) { /* ignore */ }`

[^10]: http://www.nilab.info/z3/20120708_04.html

　このパターンがどんなに理に適っていないとしても、また、わかったつもりで使っているのだとしても、利用しているメソッドがどんなExceptionを投げてくるのか完全にはわからない以上、知らないうちに重要な例外を握りつぶしてしまうということが起こる。
　自分でも気づかないうちに例外を握りつぶしてしまうことは、システムを予測不可能な状態にしてしまうし、もちろんデバッグも困難にさせる。

　Java Language Specification の「[Chapter 11. Exceptions](https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html)」には次のようにも書かれている：

> **Experience shows that too often such funny values are ignored or not checked for by callers**, leading to programs that are not robust, exhibit undesirable behavior, or both.

　例外が無視されて誰にも処理されずに終わってしまうことのないよう、なるべく誰かに catch されるようにした結果、ある意味で catch しすぎてしまったとも言える。その結果、robustなプログラムを書くための弊害となってしまったのであれば、なんとも皮肉な話である。

> そ、そんな Exception を catch するだけして無視しちゃうような乱暴なコード、誰も書かないし・・・

　そうな、アンタはそうなのかもな。
　でも、もしウソだと思うんなら、[GitHubを見てから](https://github.com/search?l=Java&q=%22catch+%28Exception+e%29%22+ignore&type=Code) 語り合おうや。

　一方でGoでは、例外は呼び出し元が責任をもって処理をするべきという考えを持っている。言い換えれば、システムが "例外的な状態" におちいったときには呼び出し元が "正常な状態" へと戻すことが期待されている。そして、["正常な状態" へと戻すことが期待されないような状態におちいったときには `panic` を使うべき](https://golang.org/doc/effective_go.html#panic) という約束の上に、Goの例外処理は成り立っている。
　本来あってはならないような状態に陥ってしまった場合には、その場を取り繕うようなことはせず、さっさとプログラムを停止して、コードを修正すべきだという考えに立脚しているのだ。
　プログラムを早く修正できるようにするため、なるべく早期にプログラムを停止し、異常な状態におちいった箇所のなるべく近くで状態を調べ上げレポートするべきという信条にもとづいているのである。（これは [Fail-fast](https://en.wikipedia.org/wiki/Fail-fast) の考え方である）

　それから、[並行処理を書くにはthrow-try-catchは適さないって話](https://tmrtmhr.info/tech/why-does-golang-not-have-exceptions/#%E4%BE%8B%E5%A4%96%E6%A9%9F%E6%A7%8B%E3%81%8C%E4%B8%A6%E8%A1%8C%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%A7%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%E7%90%86%E7%94%B1) もある。
　もともとGoの設計ゴールのひとつは、 [並行処理を安全に効率よく書けるプログラミング言語](https://golang.org/doc/faq#What_is_the_purpose_of_the_project) となることだった。そのため、並行処理と相性の悪い try-catch 方式は、Goの推奨されたやり方にはなれなかったのだ。まぁ、詳しくは読んどいてくれや。


# それ、本当に型安全だと思ってんの？

> ちょぉっと待っっったーーー！！

　む、新手か！？

> さっきから黙って聞いていれば、メソッドがどんな Exception を投げてくるかわからないだと？どうやらキサマは検査例外のことも知らんようだな？
> Java言語仕様には [検査例外（Checked exceptions)](https://docs.oracle.com/javase/specs/jls/se8/html/jls-11.html#jls-11.2) という極めて洗練された静的型チェックの仕組みがあるということを！！！
> 
> たとえば、一見正しそうなこのJavaプログラム、

```java:Java
import java.io.*;

class Main {
    public static void main(String[] args) {
        new File("tempfile").createNewFile();
    }
}
```

> これをコンパイルしようとすると、ほれ、このとおりエラーとなる：

```
5: error: unreported exception IOException; must be caught or declared to be thrown
        new File("tempfile").createNewFile();
                                          ^
1 error
```

> エラーメッセージが示しているように、`createNewFile` は `IOException` を投げるメソッドであり、呼び出し元がそれを catch していないことをコンパイラが検知してくれるのだ。
> なぜJavaではこのようなことが可能なのかというと、Javaでは各メソッドが自らが投げる可能性のある例外を型情報として持っているのだ。たとえば、[`createNewFile` のシグネチャはこのとおり](https://docs.oracle.com/javase/jp/7/api/java/io/File.html#createNewFile())：

```java
public boolean createNewFile()
                      throws IOException
```

> 自ら `IOException` を投げると宣言している。
> こういった例外を投げるメソッドを使っているプログラムのコンパイルを通すには、このように例外をちゃんと catch するか、

```java:Java
public static void main(String[ ] args) {
    try{
        new File("tempfile").createNewFile();
    } catch (IOException e) {
        System.err.println(e);
    }
}
```

> もしくは、自分の呼び出し元が正しく catch して処理できるように、自らもまた `IOException` を投げることを宣言しなくてはならない：

```java:Java
public static void main(String[ ] args) throws IOException {
    new File("tempfile").createNewFile();
}
```

> 従って、どんな例外が投げられるのかわかっているため、"知らずに重要な例外を握りつぶす" などという妄挙は起こり得ぬのだ！
> 見たか、Java言語の型システムの素晴らしさをぉぉぉ！！

（ちっ、めんどくさいヤツ来たな…）

　もちろん、俺だって検査例外のことを知らないわけじゃない。
　[Dave Cheneyさんも書いているように](https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right)、検査例外はC++の「例外」の問題点をうまく克服しているいいアイデアだって、最初はみんな思ってた。新しいものが発明されるときってのは、いつだってそうさ。
　でも、歴史がいつだってそう証明してくれているように、現実はそんなにうまくはいかなかった。

　例として、入力としてユーザー名とパスワードを受け取って認証結果のtrue/falseを返すメソッドについて考えてみよう。
　パスワードを照合する実装としては、/etc/passwd のようなファイルを使う場合や、LDAPのようなデータベースに問い合わせる場合などいろいろ考えられるから、実装が切り替えられるように抽象化しておきたい。
　これは `Authenticator` という次のようなインターフェースで表せるだろう：

```java:Java
interface Authenticator {
    boolean authenticate(String name, String password);
}
```

　そして、これを実装する `FileAuthenticator` クラスはこう書けそうだ：

```java:Java
class FileAuthenticator implements Authenticator {
    @Override
    public boolean authenticate(String name, String password) throws FileNotFoundException {
        File f = new File("/etc/passwd");
        FileReader r = new FileReader(f);
        ...(略)
    }
}
```

　でもこれ、コンパイル通らないんだぜ:

```
error: authenticate(String,String) in FileAuthenticator cannot implement authenticate(String,String) in Authenticator
        public boolean authenticate(String name, String password) throws FileNotFoundException {
                       ^
  overridden method does not throw FileNotFoundException
1 error
```

　なんで怒られてるかっつーと、「オーバーライド元のメソッド（インターフェースの宣言）が `FileNotFoundException` を投げるって宣言してないのに勝手に投げるな」って言われてるわけ。

> それはそうだろう。呼び出し元は `Authenticator` を使うとしか思っていないのだから、`Authenticator` が投げないと言っている例外を勝手に投げられては、検査例外のコンパイル時チェックが働かなくなってしまうではないか。仕様にそった正しい動作である。

　じゃあ、呼び出し元にファイルが開けなかったってどうやって知らせるわけ？

> 例外を投げる必要があるのであれば、インターフェースでそれを明示するべきである。つまり、こう書くべきである：

```java:Java
interface Authenticator {
    boolean authenticate(String name, String password) throws FileNotFoundException;
}
```

　だよね。そうなるよね。
　じゃあ、これに加えてLDAPを使った実装を作ろうとすると `LdapException` が投げられる可能性もあるんだけど、どうすんの？

> こ、こう書くのである…

```java:Java
interface Authenticator {
    boolean authenticate(String name, String password) throws FileNotFoundException, LdapException;
}
```

　あとさ、MySQLに問い合わせる実装も追加するかもしんないんだけど・・・・それ、毎回インターフェース宣言書き換えなくちゃいけないの？
　っつーか、実装を抽象化してるはずのインターフェースが、`FileNotFoundException` だったり `LdapException` だったりって実装固有の情報を持っちゃってるって、抽象化としてどうなの？

> ぐぬぬ・・・・・・

　というように、[検査例外は実装の詳細を不用意にさらけ出している](https://www.ibm.com/developerworks/jp/java/library/j-jtp05254/)という批判は昔からある問題だ。

　実装元のインターフェースが書き換え可能な場合はまだマシ[^11]で、実装しなくちゃいけないインターフェースがサードパーティのフレームワークのものだったりすると、書き換えて対応することすらできない。

[^11]: これをマシと言ってしまっていいのかは、意見がわかれるところでしょうね。

　こういうことはよくあるわけで、実際にはどうすりゃいいの？っていうと、こういうワークアラウンドがよく知られている：

```java:Java
class FileAuthenticator implements Authenticator {
    @Override
    public boolean authenticate(String name, String password) {
        try {
            File f = new File("/etc/passwd");
            FileReader r = new FileReader(f);
            ...(略)
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}
```

　例外を `RuntimeException` でラップして投げ直してしまえばいい。

　この `RuntimeException` は非検査例外って呼ばれてるものの一種で、コイツとコイツのサブクラスはコンパイル時のチェックを受けない[^12]。なので、`authenticate` メソッドのシグネチャでも宣言しなくていい。
　もし `RuntimeException` で例外を隠してしまわないようにしたいというのなら、[プログラムを大きく書き換えなくてはいけない](https://www.ibm.com/developerworks/jp/java/library/j-ce/index.html)。多くの人はそんなことしたくないから、結果として、こういうワークアラウンドが蔓延することになるわけだ。

[^12]: 補足すると、`Error` も非検査例外の一種であり、Javaの型階層の中ではこの `Error` あるいは `RuntimeException` とそれらのサブクラスのみが非検査例外であり、それ以外のすべての `Throwable` が検査例外と決められています。

　というわけで、さっきの「メソッドがどんな例外を投げるのかはわかっている」というのはウソで、メソッドシグネチャを見ただけでは、どんな `RuntimeException` が飛んでくるのかはわからない。
　しかもタチの悪いことに、みんな大好きヌルポ（`NullPointerException`） がこの `RuntimeException` のサブクラスだったりする[^13]。

[^13]: あるいは `NullPointerException` や `ArrayIndexOutOfBoundsException` といった如何にもコーディングのミスで起きてしまいそうな例外が `RuntimeException` ではなく `Error` のサブクラスだったとしたら、Java の例外処理はもう少し平和な世界になっていたのかもしれない・・・・・・と起こり得なかった世界線について思いを馳せることもあります。

　なのでさっきの例を使って、

```java:Java
// ボクは FileNotFoundException と LdapException を投げることがあるから気をつけてね！
interface Authenticator {
    boolean authenticate(String name, String password) throws FileNotFoundException, LdapException;
}

public class Main {
    // 実はバグってて、ヌルポを投げることがある実装↓
    private static Authenticator auth = new MyAuthenticator();

    public static void main(String[] args) {
        try{
            boolean ok = auth.authenticate(args[0)], args[1]);
            if ( ok ) {
                System.out.println("OK");
                System.exit(0);
            }
        } catch (Exception e) {
            // FileNotFoundException か LdapException が投げられるかもしれない
            // けど、どちらの場合も失敗として無視すればいいや
        }
        System.out.println("NG");
        System.exit(1);
    }
}
```

　とかやって、`NullPointerException` も握りつぶしてしまって、なかなかバグに気づけないということが起こるわけだ。

　検査例外が完全に間違ったアイデアだったとまでは言わない[^14]。でも、検査例外はひとつの問題を解決すると同時に、別の問題を作り出してしまったんだ。つまり、トレードオフがある。しかし、そのトレードオフに表面上は気づきにくいがゆえに、罪が深い。
　Javaよりもあとに設計された言語である C# や、Javaの後継とすら言われることのある [Scala が検査例外を採用しなかった](https://alvinalexander.com/scala/how-to-declare-scala-methods-throws-exceptions)[^15]のには、それなりの理由があるってわけだ。

[^14]: 実際、近年設計された言語であるSwiftはJavaの検査例外に似た機能を持っています。ただし、投げられる値の型は書かないという大きな違いがあります。これは「失敗する可能性のある計算とそれ以外 **だけ** は区別できるようにしよう」という試みで、実質的にはHaskellの`Maybe`と同じコンセプトと考えられます。[投げられる値の型を１つだけ指定できるようにしようという議論もされている](https://qiita.com/koher/items/e4c1d88981291c35d571)らしいですが、その場合にもやはりHaskellの`Either`に相当するものと言えます。Javaの失敗をよく踏まえて設計されていると感じます。

[^15]: なお、JavaからScalaのメソッドを呼び出したときの互換性を確保するために、`@throws` アノテーションを使って例外を投げることをJavaの呼び出し元に知らせることはできる。でも、アノテーションをつけた場合でも、やっぱりScalaの世界ではコンパイル時チェックされません。

　Goも同じだ。Java後の歴史をよく反省した上で、抜本的な解決策として大域脱出を「例外」として使わない道を選んだんだ[^16]。
　Goに try-throw-catch がないことで、まるで Java よりもすごい退化した言語のようにdisるヤツらがいるが、そういうおまいらこそが、Java後の世界から何も学ばず進化できてないんだってこと、そろそろ自覚しねーと、まじでやべぇぞ。

[^16]: この選択そのものに対する異議はあってもいいと思っています。が、異議をとなえるなら、それに代わるベターな対案を提示するべきでしょう。その意味では、Haskell や Scala の `Either` を使う例外処理のようなコンポーザブルな手法と比較するのは有意義でしょう。


# ある関数言語話者の感想

> ずーっと横で聞いてたんだけどさ、

　うおっ！？また新手かよ！

> キミたち、大域脱出くらいでよくもまぁ、そんなに熱くなれるよね。
> 大域脱出なんて、言語で直接サポートしなくても、ライブラリとして提供すればよくない？[こんなかんじ](https://en.wikibooks.org/wiki/Scheme_Programming/Continuations#C.2B.2B-like_Exceptions_with_Continuations)で↓

```scheme:Scheme
(require 'macro) ; Required to use this with SCM. Other Scheme implementations
                 ; may not require this, or it may even be an error.
(define *cont-stack* '())

(define (push-catch cont)
  (set! *cont-stack* (cons cont *cont-stack*)))

(define (pop-catch)
  (let ((retval (car *cont-stack*)))
    (set! *cont-stack* (cdr *cont-stack*))
    retval))

(define (throw exn)
  (if (null? *cont-stack*)
      (error "Can't use throw outside of a try form")
      ((car *cont-stack*) exn)))

(define-syntax try
  (syntax-rules (catch)
    ((try (body ...)
	  catch exception (catch-body ...))
     (call-with-current-continuation
      (lambda (escape)
	(dynamic-wind
	    (lambda ()
	      (let ((exception
		     (call-with-current-continuation
		      (lambda (cc)
			(push-catch cc)
			#f))))
		(if exception
		    (begin catch-body ... (escape)))))
	    (lambda ()
	      (begin body ...))
	    (lambda () (pop-catch))))))))
```

```scheme
> (try ((display "one\n")
        (throw "some-error\n")
        (display "two\n"))
   catch err ((display err)))
one
some-error
```

（※[SCM](http://people.csail.mit.edu/jaffer/SCM.html)で実行してください）

> あれ？もしかしてキミたちの言語、[継続](http://practical-scheme.net/docs/cont-j.html)が第一級の計算対象になってないの？
> それやばくない？例外がどうとか言ってる前に、そっちのほうがまじでイケてなくない？

　・・・・・・・。
　おあとがよろしいようで。


# References

- [Alvin Alexander - How to declare that a Scala method can throw an exception](https://alvinalexander.com/scala/how-to-declare-scala-methods-throws-exceptions)
- [Brian Goetz - Javaの理論と実践: 例外をめぐる議論 - チェックすべきか、チェックせずにおくべきか](https://www.ibm.com/developerworks/jp/java/library/j-jtp05254/)
- [Dave Cheney - Why Go gets exceptions right](https://dave.cheney.net/2012/01/18/why-go-gets-exceptions-right)
- [Elliotte Harold - ヒント: 例外をスローできない場合 - 例外を使用しないスーパークラスを扱う](https://www.ibm.com/developerworks/jp/java/library/j-ce/index.html)
- [The Go Authors - Effective Go](https://golang.org/doc/effective_go.html)
- [The Go Authors - Frequently Asked Questions (FAQ)](https://golang.org/doc/faq)
- [Google - Google C++ Style Guide 日本語訳](https://ttsuki.github.io/styleguide/cppguide.ja.html)
- [LLVM Project - LLVM Coding Standard](https://llvm.org/docs/CodingStandards.html)
- [mattn - try/catch/finally for go](https://github.com/mattn/go-try/)
- [methane - Go にジェネリクスがなくても構わない人たちに対する批判について](https://methane.hatenablog.jp/entry/2017/09/19/Go_%E3%81%AB%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%82%AF%E3%82%B9%E3%81%8C%E3%81%AA%E3%81%8F%E3%81%A6%E3%82%82%E6%A7%8B%E3%82%8F%E3%81%AA%E3%81%84%E4%BA%BA%E3%81%9F%E3%81%A1%E3%81%AB%E5%AF%BE%E3%81%99)
- [Oracle - Java Language Specification](https://docs.oracle.com/javase/specs/jls/se8/html/index.html)
- [Shiro Kawai - Scheme:多値](https://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E5%A4%9A%E5%80%A4#H-1ybck1o)
- [Shiro Kawai - なんでも継続](http://practical-scheme.net/docs/cont-j.html)
- [Taro MURAOKA - または私は如何にして例外するのを止めて golang を愛するようになったか](https://www.kaoriya.net/blog/2014/04/17/)
- [tmrtmhr - なぜGo言語はエラー返却に例外機構を使わないのか](https://tmrtmhr.info/tech/why-does-golang-not-have-exceptions/)
- [Wikibooks - Scheme Programming/Continuations](https://en.wikibooks.org/wiki/Scheme_Programming/Continuations)
- [Wikipedia - Fail-fast](https://en.wikipedia.org/wiki/Fail-fast)
- [Yuta Koshizawa - SwiftはどのようにJavaの検査例外を改善したか](https://qiita.com/koher/items/e4c1d88981291c35d571)