GoogleのGo言語には設計上の選択として例外はありません。Linuxの名声のLinusは例外を駄目と呼んでいます。どうして?
GoogleのGo言語には設計上の選択として例外はありません。Linuxの名声のLinusは例外を駄目と呼んでいます。どうして?
回答:
例外を使用すると、例外がスローされて不変条件が壊れ、オブジェクトが不整合な状態になるコードを簡単に記述できます。それらは基本的にあなたが作るほとんどすべてのステートメントが潜在的に投げることができることを覚えて、それを正しく処理することを強制します。そうすることはトリッキーで直観に反することがあります。
簡単な例として、次のようなものを考えてみましょう。
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
仮定するFrobManager
だろう、このルックスOK、右?または、多分そうではない...か、または例外がスローされた場合を想像してみてください。この例では、の増分はロールバックされません。したがって、このインスタンスを使用している人は誰でもdelete
FrobObject
FrobManager::HandleFrob()
operator new
m_NumberOfFrobs
Frobber
しているユーザーは、オブジェクトが破損している可能性があります。
この例はばかげているように見えるかもしれません(そう、私は少し伸ばして1つを作成しなければなりませんでした:-))。しかし、重要なのは、プログラマーが例外を常に考えておらず、状態のすべての順列が確実にロールされるようにすることです。スローがあるときはいつでも、あなたはこのようにトラブルに巻き込まれます。
例として、ミューテックスと同じように考えることができます。クリティカルセクション内では、いくつかのステートメントを使用して、データ構造が破損していないこと、および他のスレッドが中間値を認識できないことを確認します。これらのステートメントのいずれかがランダムに実行されない場合、苦痛の世界に陥ります。次に、ロックと並行性を取り除き、そのような各メソッドについて考えます。可能であれば、各メソッドをオブジェクト状態の順列のトランザクションと考えてください。メソッド呼び出しの開始時に、オブジェクトはクリーンな状態であり、最後にクリーンな状態である必要があります。その間、変数foo
は以下と矛盾する可能性がありますbar
、しかしあなたのコードは最終的にそれを修正します。例外とは、ステートメントのいずれかがいつでもユーザーに割り込む可能性があるということです。責任は個々のメソッドごとにあなたにあり、それを正しくしてそれが起こったときにロールバックするか、スローがオブジェクトの状態に影響しないように操作を順序付けます。あなたがそれを間違えた場合(そしてこの種の間違いを犯しがちです)、呼び出し元は中間値を見ることになります。
RAIIのようなメソッドは、C ++プログラマーがこの問題の究極の解決策として言及するのが大好きで、これから保護するための長い道のりです。しかし、それらは特効薬ではありません。スローでリソースを確実に解放しますが、オブジェクトの状態の破損や中間値を参照する呼び出し元について考える必要がなくなります。したがって、多くの人にとって、コーディングスタイルのフィアットにより、例外はないと言う方が簡単です。です。記述するコードの種類を制限すると、これらのバグを導入するのが難しくなります。そうしないと、間違いを犯しやすくなります。
C ++での例外安全なコーディングについての本はすべて書かれています。多くの専門家が間違っています。それが本当に複雑で、ニュアンスが非常に多い場合は、その機能を無視する必要があることを示す良い兆候かもしれません。:-)
Goに例外がない理由は、Go言語の設計に関するFAQで説明されています。
例外も同様です。例外の設計は数多く提案されていますが、それぞれの設計により、言語とランタイムが大幅に複雑になります。まさにその性質上、例外は関数にまたがっておそらくゴルーチンにまで及ぶ。それらは幅広い意味を持っています。彼らが図書館に及ぼす影響についても懸念があります。それらは、定義上、例外的でありながら、それらをサポートする他の言語での経験であり、ライブラリおよびインターフェースの仕様に大きな影響を与えることを示しています。一般的なエラーを特別な制御フローに変えてすべてのプログラマーに補正することを奨励することなく、本当に例外的なものにすることができる設計を見つけるとよいでしょう。
ジェネリックと同様に、例外は未解決の問題です。
言い換えれば、彼らはGoで例外をサポートする方法をまだ満足のいく方法で理解していません。彼らは例外自体が悪いと言っているのではありません。
更新-2012年5月
囲碁の設計者は、フェンスから降りてきました。彼らのFAQは今これを言っています:
例外を制御構造に結合すると、try-catch-finallyイディオムのように、コードが複雑になります。また、プログラマーに、ファイルを開くのに失敗したなどの通常のエラーが多すぎることを例外として示すように促す傾向もあります。
Goは別のアプローチをとります。単純なエラー処理の場合、Goの複数値の戻りにより、戻り値をオーバーロードせずにエラーを簡単に報告できます。Goの他の機能と相まって、標準的なエラータイプはエラー処理を快適にしますが、他の言語のエラー処理とはかなり異なります。
Goには、真に例外的な状態を通知して回復するための組み込み関数もいくつかあります。回復メカニズムは、エラーの後に破壊される関数の状態の一部としてのみ実行されます。これは、大惨事を処理するのに十分ですが、追加の制御構造を必要とせず、適切に使用すると、エラー処理コードがクリーンになります。
詳細については、遅延、パニック、および回復の記事を参照してください。
つまり、簡単に言えば、複数値の戻り値を使用すると、別の方法でそれを行うことができます。(とにかく、例外処理の形式があります。)
...そして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やデスクトップアプリケーションのコードなど、ほとんどの高レベルのユーザー向けコードでは問題ありません。
例外自体は「悪い」ものではありません。それは、例外がしばしば処理される方法であり、悪い傾向があります。これらの問題のいくつかを軽減するために例外を処理するときに適用できるいくつかのガイドラインがあります。これらの一部が含まれます(ただし、これらに限定されません)。
Option<T>
は、null
現在ではなくを返すことでこれを解決します。たとえば、Java 8で導入されたばかりで、Guava(およびその他)からヒントを得ています。
典型的な引数は、特定のコード(言語によって異なります)からどの例外が発生するかを知る方法がないこと、およびgoto
sに非常に似ているため、実行を精神的に追跡することが困難であるというものです。
http://www.joelonsoftware.com/items/2003/10/13.html
この問題については、コンセンサスはありません。LinusのようなハードコアCプログラマーの観点からは、例外は間違いなく悪い考えだと思います。ただし、典型的なJavaプログラマーは状況が大きく異なります。
setjmp
/ もありlongjmp
、これはかなり悪いです。
例外は悪くありません。これらは、C ++の最もエレガントなC ++のRAIIモデルにうまく適合します。例外安全ではないコードの束がすでにある場合、それらはそのコンテキストでは良くありません。Linux OSのような本当に低レベルのソフトウェアを書いているなら、それは悪いことです。一連のエラー戻りチェックでコードを散らかしたい場合は、役に立ちません。例外がスローされたときにリソース制御の計画がない場合(C ++デストラクタが提供するもの)は、問題があります。
したがって、例外の優れたユースケースは...
あなたがプロジェクトにいて、すべてのコントローラー(約20の異なる主要なコントローラー)がアクションメソッドで単一のスーパークラスコントローラーを拡張するとします。次に、すべてのコントローラが、オブジェクトB、C、Dを呼び出す場合と、F、G、Dを呼び出す場合とで、互いに異なる処理を実行します。大量の戻りコードがあり、すべてのコントローラーが異なる方法で処理していた多くの場合、ここで例外が役立ちます。私はそのすべてのコードを破壊し、「D」から適切な例外をスローし、それをスーパークラスのコントローラーアクションメソッドでキャッチしました。これで、すべてのコントローラーが一貫しています。以前は、Dはエンドユーザーに伝えたいが、できなかった複数の異なるエラーケースに対してnullを返していましたが、私はしませんでした
はい、各レベルとリソースのクリーンアップ/リークを心配する必要がありますが、通常、どのコントローラーにもクリーンアップするリソースがありません。
私たちに例外があったら神に感謝します。そうでなければ、私は巨大なリファクタリングに参加し、単純なプログラミング問題であるはずの何かに多くの時間を浪費していました。
理論的には、彼らは本当に悪いです。完璧な数学の世界では、例外的な状況に陥ることはありません。関数型言語を見てください。これらには副作用がないため、例外的な状況のソースは事実上ありません。
しかし、現実は別の話です。私たちは常に「予期しない」状況を抱えています。これが例外が必要な理由です。
例外はExceptionSituationObserverの構文シュガーとして考えることができると思います。例外の通知を受け取るだけです。これ以上何もない。
Goを使用すると、「予期しない」状況に対処する方法が紹介されると思います。彼らはそれを例外として、そしてアプリケーションロジックとしてより破壊的に聞こえないようにしようとすることを推測することができます。しかし、これは私の推測です。
C ++の例外処理パラダイムは、Javaの部分的な基礎を形成し、次に.netにもいくつかの優れた概念が導入されていますが、いくつかの厳しい制限もあります。例外処理の主要な設計意図の1つは、メソッドが後条件を満たしているか例外をスローすることを確実にし、メソッドが終了する前に行う必要があるクリーンアップが確実に行われるようにすることです。残念ながら、C ++、Java、および.netの例外処理パラダイムはすべて、予期しない要因によって予期されるクリーンアップが実行されない状況を処理するための適切な手段を提供できません。これは、予期しない事態が発生した場合にすべてが突然停止するリスクを負わなければならないことを意味します(スタックの巻き戻し中に例外を処理するC ++のアプローチが発生します)。
例外処理が一般的に適切であるとしても、他の問題の後にクリーンアップするときに発生する問題を処理するための適切な手段を提供できない例外処理パラダイムを許容できないと見なすことは不合理ではありません。つまり、フレームワークは、複数の障害が発生した場合でも適切な動作を保証できる例外処理パラダイムを使用して設計できなかったというわけではありませんが、トップの言語やフレームワークはまだそうすることができません。
私は他のすべての答えを読んだことがないので、これについてはすでに言及しましたが、1つの批判として、プログラムが長いチェーンで中断され、コードのデバッグ時にエラーを追跡することが難しくなります。たとえば、Foo()がToString()を呼び出すWah()を呼び出すBar()を呼び出すと、誤って誤ったデータがToString()にプッシュされ、Foo()のエラーのように見えます。
さて、ここで退屈な答え。それは本当に言語に依存すると思います。例外によって割り当てられたリソースが残される可能性がある場合は、それらを回避する必要があります。スクリプト言語では、アプリケーションフローの一部を単に破棄またはジャンプします。それ自体は嫌いですが、致命的なエラーを例外で回避することは許容できる考えです。
エラー信号については、私は一般的にエラー信号を好みます。すべては、API、ユースケースと重大度、またはロギングで十分かどうかに依存します。また、私はその振る舞いを再定義しようとしていますthrow Phonebooks()
。「例外」はしばしば行き止まりであるという考えですが、「電話帳」には、エラー回復または代替の実行ルートに関する役立つ情報が含まれています。(良いユースケースはまだ見つかりませんが、引き続き試してください。)
私にとって、問題は非常に簡単です。多くのプログラマは例外ハンドラを不適切に使用しています。言語リソースが多いほど良いです。例外を処理できるようにするのは良いことです。不正使用の1つの例は、検証されない整数でなければならない値、または除算され、ゼロの除算がチェックされない可能性のある別の入力です...例外処理は、より多くの作業とハードな思考を回避する簡単な方法かもしれません汚いショートカットを作成して例外処理を適用したい場合があります...アルゴリズムによって処理された問題の一部がその性質上不明確である場合、「プロのコードは決して失敗しない」というステートメントは幻想かもしれません。おそらく、本質的に未知の状況では、例外ハンドラーが役立ちます。良いプログラミングの実践は議論の問題です。