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構文には、例外発生の有無にかかわらず、必ず実行されなければならないコードを記述します。

エラー処理と責任

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

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

エラーはもはや一箇所で処理できないほど複雑になり、どこか一箇所ですべてのエラーを責任を負うことができなくなりました。ここでtry-catchに深刻な問題が発生します。

try-catchの責任転嫁

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

1try {
2    data = readFile("hello.txt"); // ファイル「hello.txt」を読み込む
3    structuredData = parseData(data); // データを解析する
4    insertDBStatus(structuredData[1]); // データベースステータスを挿入する
5    startHTTPServer(structuredData[2]); // HTTPサーバーを開始する
6} catch (Exception e) {
7    e.printStackTrace(); // スタックトレースを出力する
8}

上記のコードの問題点は、printStackTraceを処理する主体が何であるかが明確でなく、どのコードでエラーが発生したのかも分からなくなる点です。さらに、try文の中にロジックが増えれば増えるほど、問題はさらにひどくなります。しかし、逆説的ではありますが、開発が複雑になればなるほど、開発者は責任を転嫁するtry-catch構文に依存し、エラー処理について考慮せず、責任感も薄れ、最終的にはエラーと例外処理の本質について忘れてしまいました。では、Go言語はこの問題をどのように解決したのでしょうか?

panic, recover

Goの利点の一つは、開発者を悪い道に導かず、良い開発者に育成するための様々なシステムがあることでしょう。panic - recoverもその例として挙げられます。 ​ panicとrecoverは一見するとtry-catchと違いがないように見えますが、問題を引き起こした際に、責任を外部に転嫁しないという点で異なります。Goでpanicが発生した場合、その値を外部に転嫁するのではなく、panicが発生した場所で解決しなければなりません。これは開発者にエラー処理に対する責任を付与し、そのエラーをどこで、誰が、どのように処理すべきかをより深く考えるように促します。ユーザーに自律性を保証しつつも、考える余地を残していると言えます。

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