12

私は現在、Haskellで例外を使用する正しい方法に頭を悩ませようとしています。例外がどのように機能するかは簡単です。私はそれらを解釈する正しい方法の明確な絵を得ようとしています。

基本的な立場は、適切に設計されたアプリケーションでは、例外がトップレベルに逃げてはならないということです。例外は明らかに、設計者が予期していなかったものです。つまり、異常な実行時の発生(ファイルが見つからないなど)ではなく、プログラムのバグ(ゼロ除算など)です。

stderrそのために、すべての例外をキャッチし、「これはバグです」というメッセージを出力する単純なトップレベルの例外ハンドラーを作成しました(プログラムを終了するために例外を再スローする前に)。

ただし、ユーザーがCtrl+Cを押したとします。これにより、例外がスローされます。明らかに、これはプログラムのバグではありません。ただし、このようなユーザーの中止を予測して対応できない場合は、バグと見なされる可能性があります。したがって、プログラムはこれをキャッチして適切に処理し、終了する前に必要なクリーンアップを実行する必要があります。

ただし、これを処理するコードは、例外をキャッチし、リソースなどを解放してから、例外を再スローします。したがって、例外がトップレベルになったとしても、それが処理されなかったことを必ずしも意味するわけではありません。すぐに終了したかったということです。

だから、私の質問:この方法でフロー制御に例外を使用する必要がありますか?明示的にキャッチするすべての関数はUserInterrupt、例外を再スローするのではなく、明示的なフロー制御構造を使用して手動で終了する必要がありますか?(しかし、発信者はどのようにして終了することを知っていますか?)UserInterruptトップレベルに到達しても大丈夫ですか?しかし、その場合、ThreadKilled同じ議論で、それも大丈夫ですか?

要するに、割り込みハンドラーはUserInterrupt(そしておそらくThreadKilled)のために特別なケースを作るべきですか?HeapOverflowまたははどうですかStackOverflowそれはバグですか?それとも「プログラムの制御が及ばない状況」なのか?

4

2 に答える 2

7

例外が存在する場合のクリーンアップ

ただし、このようなユーザーの中止を予測して対応できない場合は、バグと見なされる可能性があります。したがって、おそらくプログラムはこれをキャッチして適切に処理し、終了する前に必要なクリーンアップを行う必要があります。

ある意味であなたは正しいです — プログラマーは例外を予測する必要があります。しかし、それらを捕まえることによってではありません。代わりに、 などの例外セーフ関数を使用する必要がありますbracket。例えば:

import Control.Exception

data Resource

acquireResource :: IO Resource
releaseResource :: Resource -> IO ()

workWithResource = bracket acquireResource releaseResource $ \resource -> ...

このようにして、プログラムが Ctrl+C によって中止されるかどうかに関係なく、リソースがクリーンアップされます。

例外はトップレベルに到達する必要がありますか?

さて、私はあなたの別の声明に対処したいと思います:

基本的な立場は、適切に設計されたアプリケーションでは、例外がトップレベルに逃げるべきではないということです。

よく設計されたアプリケーションでは、例外は中止するための完全に優れた方法であると私は主張します。これに問題がある場合は、何か間違ったことをしています (たとえば、終了時にクリーンアップ アクションを実行したいのですが、これは ! で実行mainする必要がありbracketます)。

私がプログラムでよく行うことは次のとおりです。

  1. 起こりうるエラー (うまくいかない可能性のあるもの) を表すデータ型を定義します。それらのいくつかは、しばしば他の例外をラップします。

    data ProgramError
      = InputFileNotFound FilePath IOException
      | ParseError FilePath String
      | ...
    
  2. ユーザーフレンドリーな方法でエラーを出力する方法を定義します。

    instance Show ProgramError where
      show (InputFileNotFound path e) = printf "File '%s' could not be read: %s" path (show e)
      ...
    
  3. タイプを例外として宣言します。

    instance Exception ProgramError
    
  4. 気が向いたときはいつでも、これらの例外をプログラムでスローします。

例外をキャッチする必要がありますか?

InputFileNotFound予想される例外は、より多くのコンテキストを提供するために、キャッチしてラップする必要があります (例: で)。予期しない例外についてはどうですか?

ユーザーに「これはバグです」と表示して、ユーザーに問題を報告してもらうことには、ある程度の価値があると思います。これを行う場合は、予測する必要がありますUserInterrupt— あなたが言うように、これはバグではありません。どのように扱うべきThreadKilledかは、アプリケーションによって異なります。

ただし、これは「優れた設計」とは正反対であり、対象とするユーザーの種類、ユーザーに何を期待するか、ユーザーがプログラムに何を期待するかに大きく依存します。応答は、例外を出力するだけのものから、「申し訳ありませんが、開発者にレポートを送信しますか?」というダイアログまでさまざまです。

于 2013-01-26T20:03:10.753 に答える
4

このようにフロー制御に例外を使用する必要がありますか?

はい。Breaking from a loopを読むことを強くお勧めします。これは、コード ブロックを早期に終了するための抽象化にすぎない方法Eitherとその核心を示しています。EitherT例外は、エラーが原因で終了するこの動作の特殊なケースにすぎませんが、それが時期尚早に終了する唯一のケースである理由はありません。

于 2013-01-26T17:31:25.420 に答える