Google の Go 言語には設計上の選択として例外はなく、Linux で有名な Linus は例外をクズと呼んでいます。なんで?
15 に答える
例外を使用すると、例外がスローされると不変条件が壊れ、オブジェクトが一貫性のない状態になるようなコードを簡単に記述できます。基本的に、作成するステートメントのほとんどすべてがスローされる可能性があり、それを正しく処理できることを覚えておく必要があります。そうすることは、トリッキーで直感に反する可能性があります。
簡単な例として、次のようなものを考えてみましょう。
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
FrobManager
意志を仮定するとdelete
、FrobObject
これは問題ないようですよね?FrobManager::HandleFrob()
またはそうではないかもしれません...または またはoperator new
が例外をスローした場合を想像してください。この例では、の増分はm_NumberOfFrobs
ロールバックされません。したがって、 のこのインスタンスを使用する人Frobber
は、オブジェクトが破損している可能性があります。
この例はばかげているように見えるかもしれません (わかりました、それを作成するために少し手を伸ばさなければなりませんでした :-)) が、要点は、プログラマーが常に例外を考えていない場合、および状態のすべての順列が確実にロールされるようにすることです。スローがあるたびに、この方法で問題が発生します。
例として、ミューテックスについて考えるように考えることができます。クリティカル セクション内では、いくつかのステートメントに依存して、データ構造が破損していないこと、および他のスレッドが中間値を認識できないことを確認します。これらのステートメントのいずれかがランダムに実行されない場合、苦痛の世界に陥ります。次に、ロックと並行性を取り除き、それぞれの方法についてそのように考えます。必要に応じて、各メソッドをオブジェクト状態の順列のトランザクションと考えてください。メソッド呼び出しの開始時に、オブジェクトはクリーンな状態である必要があり、最後にもクリーンな状態である必要があります。その間、変数foo
はと矛盾する可能性がありますbar
、しかしあなたのコードは最終的にそれを修正します。例外とは、どのステートメントでもいつでも中断できることを意味します。個々のメソッドごとに、それを正しく取得してロールバックするか、スローがオブジェクトの状態に影響を与えないように操作を順序付ける責任があります。間違えた場合 (そしてこの種の間違いを犯しやすい)、呼び出し元は中間値を見ることになります。
C++ プログラマーがこの問題の究極の解決策として好んで言及する RAII のような方法は、これを防ぐのに大いに役立ちます。しかし、それらは特効薬ではありません。スロー時にリソースを確実に解放しますが、オブジェクトの状態の破損や呼び出し元が中間値を参照することについて考える必要がなくなるわけではありません。そのため、多くの人にとって、コーディング スタイルの基準により、例外はないと言う方が簡単です。記述するコードの種類を制限すると、これらのバグを導入するのが難しくなります。そうしないと、間違いを犯しやすくなります。
C++ での例外セーフ コーディングについては、本全体が書かれています。多くの専門家がそれを誤解しています。それが本当に複雑で、非常に多くのニュアンスがある場合は、その機能を無視する必要があるという良い兆候かもしれません. :-)
Goに例外がない理由は、Go言語設計FAQで説明されています。
例外も同様の話です。例外のための多くの設計が提案されていますが、それぞれが言語と実行時にかなりの複雑さを追加します。その性質上、例外は関数にまたがり、おそらくゴルーチンにも及びます。それらには幅広い意味があります。それらがライブラリに与える影響についても懸念があります。それらは、定義上、例外的ですが、それらをサポートする他の言語での経験は、ライブラリとインターフェイスの仕様に大きな影響を与えることを示しています。一般的なエラーを、すべてのプログラマーが補償する必要のある特別な制御フローに変えることを奨励することなく、それらが真に例外的なものになることを可能にする設計を見つけることは素晴らしいことです。
ジェネリックと同様に、例外は未解決の問題のままです。
言い換えれば、彼らは、満足のいく方法でGoの例外をサポートする方法をまだ理解していません。彼らは、例外自体が悪いと言っているのではありません。
更新-2012年5月
Goの設計者は、今やフェンスを降りてきました。彼らのFAQは今これを言っています:
try-catch-finallyイディオムのように、例外を制御構造に結合すると、複雑なコードが生成されると考えられます。また、プログラマーは、ファイルを開かないなどの通常のエラーを例外としてラベル付けするように促される傾向があります。
Goは別のアプローチを取ります。単純なエラー処理の場合、Goの複数値の戻り値により、戻り値をオーバーロードすることなくエラーを簡単に報告できます。正規のエラータイプとGoの他の機能を組み合わせると、エラー処理が快適になりますが、他の言語のエラー処理とはかなり異なります。
Goには、真に例外的な状態を通知して回復するための2つの組み込み関数もあります。回復メカニズムは、エラー後に関数の状態が破棄される一部としてのみ実行されます。これは、大惨事を処理するのに十分ですが、追加の制御構造を必要とせず、適切に使用すると、クリーンなエラー処理コードを生成できます。
詳細については、延期、パニック、および回復の記事を参照してください。
つまり、簡単な答えは、複数値のリターンを使用して別の方法でそれを行うことができるということです。(そして、とにかく例外処理の形式があります。)
...そしてLinuxの名声のLinusは例外をがらくたと呼んでいます。
Linusが例外がくだらないと考える理由を知りたい場合は、そのトピックに関する彼の著作を探すのが最善です。私がこれまでに追跡した唯一のことは、C++のいくつかの電子メールに埋め込まれているこの引用です:
「C++の例外処理全体が根本的に壊れています。特にカーネルの場合は壊れています。」
彼が特にC++の例外について話しているのであって、一般的な例外について話しているのではないことに気付くでしょう。(そして、C ++例外には、正しく使用するのが難しいいくつかの問題があるようです。)
私の結論は、Linusは例外(一般的に)を「がらくた」とはまったく呼んでいないということです!
例外自体は悪くありませんが、例外が頻繁に発生することがわかっている場合は、パフォーマンスの面で高くつく可能性があります。
経験則として、例外は例外的な状態にフラグを立てる必要があり、プログラム フローの制御には使用しないでください。
「例外的な状況でのみ例外をスローする」ことに同意しません。一般的には正しいですが、誤解を招く可能性があります。例外は、エラー状態 (実行の失敗) です。
使用する言語に関係なく、Framework Design Guidelines : Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition) のコピーを入手してください。例外のスローに関する章にはピアがありません。初版からの引用 (第 2 版は私の仕事です):
- エラー コードを返さないでください。
- エラー コードは簡単に無視できますが、多くの場合無視されます。
- 例外は、フレームワークでエラーを報告する主要な手段です。
- 経験則として、メソッドがその名前が示すとおりに動作しない場合は、メソッド レベルの失敗と見なされ、例外が発生します。
- 可能であれば、通常の制御フローに例外を使用しないでください。
例外の利点 (API の一貫性、エラー処理コードの場所の選択、堅牢性の向上など) に関するメモのページがあります。いくつかのパターン (Tester-Doer、Try-Parse) を含むパフォーマンスに関するセクションがあります。
例外と例外処理は悪くありません。他の機能と同様に、誤用される可能性があります。
golang の観点からは、例外処理を行わないことで、コンパイル プロセスがシンプルかつ安全に保たれると思います。
Linus の観点からは、カーネル コードはすべてコーナー ケースに関するものであることを理解しています。したがって、例外を拒否することは理にかなっています。
例外は、現在のタスクを中止しても問題がなく、一般的なケースのコードがエラー処理よりも重要である場合に意味があります。ただし、コンパイラからのコード生成が必要です。
たとえば、Web やデスクトップ アプリケーションのコードなど、ほとんどの高レベルのユーザー向けコードでは問題ありません。
例外自体は「悪い」ものではありません。例外が処理される方法であるため、悪い傾向があります。これらの問題のいくつかを軽減するために、例外を処理するときに適用できるガイドラインがいくつかあります。これらのいくつかには以下が含まれます(ただし、これらに限定されません)。
- プログラムフローを制御するために例外を使用しないでください。つまり、ロジックのフローを変更するために「catch」ステートメントに依存しないでください。これは、ロジックの周りのさまざまな詳細を隠す傾向があるだけでなく、パフォーマンスの低下につながる可能性があります。
- 返された「ステータス」の方が理にかなっている場合は、関数内から例外をスローしないでください。例外的な状況でのみ例外をスローしてください。例外の作成は、コストがかかり、パフォーマンスを大量に消費する操作です。たとえば、ファイルを開くためのメソッドを呼び出し、そのファイルが存在しない場合、「FileNotFound」例外をスローします。顧客アカウントが存在するかどうかを判断するメソッドを呼び出す場合は、ブール値を返し、「CustomerNotFound」例外を返さないでください。
- 例外を処理するかどうかを決定するときは、例外で何か役立つことができる場合を除いて、「try...catch」句を使用しないでください。例外を処理できない場合は、コールスタックをバブルアップさせるだけです。そうしないと、例外がハンドラーによって「飲み込まれ」、詳細が失われる可能性があります(例外を再スローしない限り)。
典型的な議論は、特定のコード片からどのような例外が発生するか (言語によって異なります) を知る方法がないこと、および例外が s に似すぎているgoto
ため、実行を精神的に追跡することが困難であるというものです。
http://www.joelonsoftware.com/items/2003/10/13.html
この問題については、確かにコンセンサスはありません。Linus のような筋金入りの C プログラマーの観点から言えば、例外は間違いなく悪い考えです。ただし、典型的な Java プログラマーの状況は大きく異なります。
例外は悪くありません。これらは、C++ の最も洗練された点である C++ の RAII モデルにうまく適合します。例外セーフではないコードが既にたくさんある場合、それらはそのコンテキストでは不適切です。Linux OS のような非常に低レベルのソフトウェアを作成している場合、それらは悪いものです。コードにたくさんのエラー リターン チェックを散らかすのが好きなら、それらは役に立ちません。例外がスローされたときのリソース制御の計画がない場合 (C++ デストラクタが提供する)、それらは悪いことです。
理論的には彼らは本当に悪いです。完璧な数学の世界では、例外的な状況を取得することはできません。関数型言語を見てください。副作用がないので、例外的な状況のソースは事実上ありません。
しかし、現実は別の話です。私たちは常に「予期しない」状況を抱えています。これが例外が必要な理由です。
ExceptionSituationObserverのシンタックスシュガーとしての例外を考えることができると思います。例外の通知を受け取るだけです。これ以上何もない。
Goでは、「予期しない」状況に対処する何かを紹介すると思います。彼らはそれを例外としてより破壊的ではなく、アプリケーションロジックとしてより破壊的に聞こえるようにしようとしていると私は推測することができます。しかし、これは私の推測です。
私は他のすべての回答を読んでいないので、これはすでに言及されている可能性がありますが、1つの批判は、プログラムが長い連鎖で壊れ、コードをデバッグするときにエラーを追跡するのが難しくなるということです. たとえば、ToString() を呼び出す Wah() を呼び出す Bar() を Foo() が呼び出した場合、間違ったデータを ToString() に誤ってプッシュすると、Foo() のエラーのように見えてしまいます。
C++ の例外処理パラダイムは、Java の部分的な基礎を形成し、.net にもいくつかの優れた概念が導入されていますが、いくつかの重大な制限もあります。例外処理の主な設計意図の 1 つは、メソッドが事後条件を満たしていること、または例外をスローすることを保証できるようにすること、およびメソッドが終了する前に必要なクリーンアップが確実に行われるようにすることです。残念ながら、C++、Java、および .net の例外処理パラダイムはすべて、予想外の要因によって予想されるクリーンアップの実行が妨げられる状況を処理する適切な手段を提供できません。これは、予期しないことが起こった場合にすべてが停止する危険を冒さなければならないことを意味します (例外を処理する C++ のアプローチは、スタックの巻き戻し中に発生します)。
Even if exception handling would generally be good, it's not unreasonable to regard as unacceptable an exception-handling paradigm that fails to provide a good means for handling problems that occur when cleaning up after other problems. That isn't to say that a framework couldn't be designed with an exception-handling paradigm that could ensure sensible behavior even in multiple-failure scenarios, but none of the top languages or frameworks can as yet do so.
さて、ここで退屈な答え。それは本当に言語に依存していると思います。割り当てられたリソースが例外によって取り残される可能性がある場合は、回避する必要があります。スクリプト言語では、アプリケーション フローの一部を放棄またはオーバージャンプするだけです。それ自体は嫌なことですが、致命的なエラーを例外で回避することは受け入れられる考えです。
エラー信号については、一般的にエラー信号を好みます。すべては、API、ユースケースと重大度、またはロギングで十分かどうかによって異なります。throw Phonebooks()
また、代わりに動作を再定義しようとしています。「例外」は行き止まりであることが多いという考えですが、「電話帳」にはエラー回復または代替実行ルートに関する役立つ情報が含まれています。(良いユースケースはまだ見つかっていませんが、試してみてください。)
- 例外が処理されないことは一般的に悪いことです。
- 例外処理の仕方が悪いのは (もちろん) 悪いことです。
- 例外処理の「良し悪し」は、コンテキスト/スコープと適切性に依存し、それを行うためではありません。