GoSuda

GoにはなぜTry-Catchがないのだろうか?

By Rabbit Princess
views ...

Goは意図的にtry-catchをサポートせず、panic-recover構文のみをサポートする。これはtry-catchを利用したエラー処理に慣れている多くの開発者たちの不満を買っている。それでは、try-catchを入れない理由は何だろうか?それはtry-catchがいくつかの問題点を持っているからである。

try-catch-finally

トライキャッチ(try-catch)構文は、プログラム実行中(ランタイム)に発生する可能性のあるエラー状況と例外状況を処理するための構文である。また、finally構文は、例外発生の有無にかかわらず、必ず実行されなければならないコードを記述する。

エラー処理と責任

80年代と90年代には、エラー処理は非常に単純だった。エラーメッセージは「フロッピーディスクがいっぱい」、「ドライブにフロッピーディスクがない」、「フロッピーディスク書き込み権限がない」がすべてであり、この時代の開発者は、エラーが発生すると、該当エラーをエラー処理地点までthrowして共通化して処理した。このような状況でtry-catch構文は効率的に動作した。

しかし、時間が経つにつれて状況は変化した。ビジネスロジックは複雑になり、データベースとトランザクションが生まれ、ネットワークを通じて数多くのAPIを呼び出しながら、数多くのリクエストメッセージを解析しなければならなくなり、さらには並行性プログラミングの登場によって、メインではない他のスレッドでエラーを処理しなければならなくなった。

エラーはもはや一箇所で処理できないほど複雑になり、ある一箇所ですべてのエラーを責任を持つことができなくなった。ここでトライキャッチに深刻な問題が発生する。

try-catchの責任転嫁

try-catchは、一言で言えば、エラーを引き起こした主体がエラー発生に対する責任(後処理)を誰かに押し付ける方法である。その押し付ける対象はcatch構文であることも、自身の親メソッドであることも、自身の親の親の親の親の…誰かであることもある。言い換えれば、エラー処理が多くなり複雑になる世界で、try-catchが選んだ方法はまさに「誰かがやってくれるだろう」ということなのである。下記のコードを見てみよう。

1try {
2    data = readFile("hello.txt");
3    structuredData = parseData(data);
4    insertDBStatus(structuredData[1]);
5    startHTTPServer(structuredData[2]);
6} catch (Exception e) {
7    e.printStackTrace();
8}

上記のコードの問題点は、printStackTraceを処理する主体が何なのかも明確でなく、どのコードでエラーが発生したのかも分からなくなる点である。さらに、try命令文の中に多くのロジックが生じるほど、問題はさらにひどくなる。しかし逆説的に、開発が複雑になればなるほど、開発者たちは責任を転嫁するトライキャッチ構文に中毒になり、エラー処理に対する悩みをせず、責任感も薄れ、結局はエラーと例外処理の本質について忘れてしまった。それでは、golangはどのようにこの問題を解決したのだろうか?

panic, recover

Goの長所の一つは、開発者を悪い道に陥らせず、良い開発者にするためのいくつかのシステムがあるという点だろう。panic - recoverもまたその例として挙げられる。 ​ パニックとリカバーは一見するとtry-catchと異なる点がないように見えるが、問題を起こした時、責任を外部に転嫁しないという点で異なる。Goでパニックが発生した時は、該当の値を外部に返すよりも、パニックが発生した位置で解決しなければならない。これは開発者にエラー処理に対する責任を付与し、該当エラーをどこで、誰が、どのように処理すべきかをより深く悩むように誘導する。ユーザーに自律性を保障しながらも、考える余地を残しているわけだ。

また、panicという単語の選定も非常に優れていると言えるが、try-recoverとは異なり、panic-recoverは単語だけで例外状況ではなく、明白なエラー状況でのみ使用しなければならないという圧迫を開発者に与える。自然に開発者はrecoverを乱用せず、必要な場所でのみ使用するようになる。これはGoで簡潔なコードを書くのに大きな助けとなる。