90年代後半に戻ると、フロー制御として例外を使用するコードベースでかなり作業しました。テレフォニーアプリケーションを駆動するための有限状態マシンを実装しました。最近、MVC Webアプリを開発しているので、そのことを思い出します。
どちらにもController
sがあり、次にどこへ行くかを決定し、データを宛先ロジックに提供します。 DTMFトーンなど、昔ながらの電話のドメインからのユーザーアクションは、アクションメソッドのパラメーターになりましたが、ViewResult
のようなものを返す代わりに、StateTransitionException
をスローしました。
主な違いは、アクションメソッドがvoid
関数であったことだと思います。私はこの事実で私がしたことのすべてを覚えているわけではありませんが、その仕事以来、15年前のように私はこれをプロダクションコードで見たことがないため、その他の仕事。これはいわゆるアンチパターンだと思った。
これは事実ですか、もしそうなら、なぜですか?
更新:私が質問をしたとき、私はすでに@MasonWheelerの答えを念頭に置いていたので、私の知識に最も追加された答えを選びました。彼も正解だと思います。
これに関する詳細な議論は Ward's Wiki にあります。一般に、制御フローの例外の使用はアンチパターンであり、多くの注目すべき状況があり、言語固有です( examplePython を参照)咳例外咳。
なぜ一般的にはそれがアンチパターンであるかについての簡単な要約として:
より詳細な情報については、ウォードのwikiでの議論を読んでください。
この質問の複製も参照してください here
例外が設計されたユースケースは、「この時点で適切に処理できない状況が発生しました。それを処理するための十分なコンテキストがないためです。しかし、私を呼び出したルーチン(または呼び出しのさらに先の何か)スタック)それを処理する方法を知っている必要があります。」
2番目の使用例は、「深刻なエラーが発生したところです。現在、データの破損やその他の損傷を防ぐために、この制御フローから抜け出すことは、続行するよりも重要です。」
これらの2つの理由のいずれかで例外を使用していない場合は、おそらくより良い方法があります。
例外は継続とGOTO
と同じくらい強力です。それらは普遍的な制御フロー構造です。
一部の言語では、これらはonlyユニバーサル制御フロー構成です。たとえば、JavaScriptには継続もGOTO
もありません。適切な末尾呼び出しさえありません。したがって、JavaScriptで高度な制御フローを実装する場合は、例外を使用するhaveを使用します。
Microsoft Voltaプロジェクトは、任意の.NETコードをJavaScriptにコンパイルする(現在は廃止された)研究プロジェクトでした。 .NETには例外があり、そのセマンティクスはJavaScriptに正確にマッピングされていませんが、さらに重要なことにThreadsがあり、なんらかの方法でJavaScriptにマッピングする必要があります。 Voltaは、JavaScript例外を使用してVolta継続を実装し、Volta継続に関してすべての.NET制御フロー構造を実装することでこれを行いました。それらはhad例外を制御フローとして使用します。なぜなら他の制御フロー構造はない十分に強力だからです。
あなたはステートマシンについて言及しました。 SMを適切なテールコールで実装するのは簡単です。すべての状態はサブルーチンであり、すべての状態遷移はサブルーチンコールです。 SMは、GOTO
、コルーチンまたは継続を使用して簡単に実装することもできます。ただし、Javaにはこれらの4つはありませんが、doesには例外があります。したがって、これらを制御フローとして使用することは完全に許容されます。(まあ、実際、正しいの選択は、おそらく適切な制御フロー構造を備えた言語を使用することですが、Javaに悩まされる場合もあります。)
制御フローに例外を使用することは一般にはアンチパターンと見なされますが、例外があります(しゃれは意図されていません)。
例外は例外的な状況を意図していると1000回言われてきました。切断されたデータベース接続isは例外的な状態です。数字のみを許可する必要がある入力フィールドに文字を入力するユーザーis not。
不正な引数で関数が呼び出される原因となるソフトウェアのバグ。 null
許可されていない場合、is例外条件。
例外的でないものに例外を使用することで、解決しようとしている問題に不適切な抽象化を使用しています。
ただし、パフォーマンスが低下することもあります。一部の言語には、多かれ少なかれ効率的な例外処理の実装があるため、選択した言語に効率的な例外処理がない場合、パフォーマンス面で非常にコストがかかります*。
しかし、Rubyなどの他の言語には、制御フローの例外に似た構文があります。例外的な状況は、raise
/rescue
演算子によって処理されます。ただし、例外のような制御フロー構造にはthrow
/catch
を使用できます**。
したがって、例外は一般には制御フローに使用されませんが、選択した言語には他のイディオムがある場合があります。
*パフォーマンスを犠牲にして例外を使用する例:以前は、パフォーマンスの低いASP.NET Webフォームアプリケーションを最適化するように設定されていました。大きなテーブルのレンダリングが約int.Parse()
を呼び出していることがわかりました。平均的なページに1,000個の空の文字列があり、約処理される1000の例外。コードをint.TryParse()
に置き換えることで、1秒間削った!すべての単一ページのリクエストに対して!
** throw
とcatch
は他の多くの言語の例外に関連付けられているキーワードであるため、他の言語からRubyにアクセスするプログラマにとって、これは非常に混乱する可能性があります。 。
例外を使用せずにエラー状態を処理することは完全に可能です。一部の言語、特にCには例外がありませんが、人々はそれでも非常に複雑なアプリケーションを作成できます。例外が役立つ理由は、同じコードで2つの本質的に独立した制御フローを簡潔に指定できることです。1つはエラーが発生した場合と1つは発生しなかった場合です。それらがないと、次のような場所にコードが作成されます。
status = getValue(&inout);
if (status < 0)
{
logError("message");
return status;
}
doSomething(*inout);
または、エラーステータスとして1つの値を持つタプルを返すなど、言語で同等です。多くの場合、「高価な」例外処理がいかに高いかを指摘する人は、上記のような余分なif
ステートメントをすべて無視してください。例外を使用しない場合に追加します。
このパターンはエラーやその他の「例外的な条件」を処理するときに最も頻繁に発生しますが、私の意見では、このようなボイラープレートコードを他の状況で見始めると、例外を使用することについてかなり良い議論があります。状況と実装によっては、状態マシンで例外が有効に使用されていることがわかります。これは、2つの直交制御フローがあるためです。1つは状態を変更するもので、もう1つは状態内で発生するイベントです。
ただし、これらの状況はまれであり、例外を(意図的にしゃれた)例外にする場合は、他のソリューションよりも優れていることを示す準備をしておく必要があります。このような正当化のない逸脱は、当然アンチパターンと呼ばれます。
Pythonでは、例外はジェネレータと反復の終了に使用されます。 Pythonには非常に効率的なtry/exceptブロックがありますが、実際に例外を発生させるとオーバーヘッドが発生します。
Pythonにはマルチレベルのブレークまたはgotoステートメントがないため、例外を使用することがあります。
class GOTO(Exception):
pass
try:
# Do lots of stuff
# in here with multiple exit points
# each exit point does a "raise GOTO()"
except GOTO:
pass
except Exception as e:
#display error
このような例外の使用法をスケッチしましょう:
アルゴリズムは、何かが見つかるまで再帰的に検索します。したがって、再帰から戻って、結果が見つかるかどうかを確認してから戻り、それ以外の場合は続行する必要があります。そしてそれは繰り返し再帰の深さから繰り返し戻ってきます。
追加のブール値found
が必要なほか(クラスにパックする必要があります。そうしないと、おそらくintだけが返されます)、再帰の深さについても同じポストリュードが発生します。
このようなコールスタックの巻き戻しは、例外の対象です。ですから、私には後藤のような、より即時的で適切なコーディング手段のようです。必要ない、まれな使用法、おそらく悪いスタイルですが、要点です。プロローグカットオペレーションと同等。
これに答える最も簡単な方法は、長年にわたって進歩したOOPを理解することです。OOPで行われるすべて問題)は必要な作業を中心にモデル化されています。
メソッドが呼び出されるたびに、呼び出し元は「この作業を行う方法はわかりませんが、方法はわかっているので、私のためにそれを行います」
これには難点がありました:呼び出されたメソッドが一般に作業方法を知っているが、常にではない場合はどうなりますか?コミュニケーションの方法が必要でした「私はあなたを助けたかったのですが、本当にそうしましたが、それはできません。」
これを伝える初期の方法論は、単に「ゴミ」の値を返すことでした。おそらく正の整数を期待しているので、呼び出されたメソッドは負の数を返します。これを達成する別の方法は、どこかにエラー値を設定することでした。残念ながら、どちらの方法でも、ボイラープレートlet-me-check-over-here-to-make-sure-everything's-kosherコードが発生しました。物事がより複雑になるにつれて、このシステムは崩壊します。
大工、配管工、電気技師がいるとします。あなたはシンクを修理するために配管工をしたいので、彼はそれを調べます。 「申し訳ありませんが、修正できません。壊れています。」地獄、彼がそうだった場合、それはさらに悪いことです見て、去って、そして彼がそれを修正することができなかったとあなたに手紙を送るために。彼があなたが望んでいたことをしなかったことに気付く前に、あなたは今あなたのメールをチェックしなければなりません。
「あなたのポンプが機能していないように見えるので、修正できませんでした。」
この情報を使用して、電気技師に問題を調べてほしいと結論付けることができます。おそらく電気技師は大工に関連する何かを見つけるでしょう、そしてあなたは大工にそれを修理してもらう必要があります。
一体、あなたはあなたが電気技師を必要とすることさえ知らないかもしれません、あなたはあなたが必要とする誰を知らないかもしれません。あなたは家の修理ビジネスのちょうど中間管理職であり、あなたの焦点は配管です。したがって、問題についてボスであると伝え、次にheが電気技師に修正するように指示します。
これがモデリングの例外です。分離された方法での複雑な障害モード。配管工は電気技師について知る必要はありません-チェーンの誰かが問題を解決できることを知る必要さえありません。彼は彼が遭遇した問題について報告するだけです。
では、例外のpointを理解することが最初のステップです。次はアンチパターンとは何かを理解することです。
アンチパターンとしての資格を得るには、
最初のポイントは簡単に満たされます-システムは機能しましたよね?
2点目は粘着性です。例外を通常の制御フローとして使用する主な理由は、それが目的ではないためです。プログラムの機能の特定の部分は、目的が比較的明確である必要があり、その目的を共同で選択すると、不要な混乱が生じます。
しかし、それはdefinitiveの害ではありません。それは物事を行うには不十分な方法であり、奇妙ですが、アンチパターンですか?いいえ。ただ…奇妙です。