適切な例外メッセージを書く方法


101

現在、コードレビューを行っていますが、気付いていることの1つは、例外メッセージが例外の発生場所を繰り返しいるように見える例外の数です。例えば

throw new Exception("BulletListControl: CreateChildControls failed.");

このメッセージの3つの項目はすべて、残りの例外から解決できます。スタックトレースからクラスとメソッドを知っており、それが失敗したことを知っています(例外があるため)。

例外メッセージにどのメッセージを入れるか考えました。最初に、一般的な理由(たとえばPropertyNotFoundException- 理由)で例外クラスが存在しない場合は作成し、それをスローすると、メッセージに何が問題だったかを示します(たとえば、「ノード1234でプロパティ 'IDontExist'が見つかりません」 "- 何が)。どこにありStackTraceます。とき(該当する場合)ログに終わる可能性があります。どのように開発者がうまく(および修正)するためのものです

例外をスローするためのその他のヒントはありますか?特に、新しいタイプと例外メッセージの作成に関して。


4
これらはログファイル用ですか、それともユーザーに提示するものですか?
ジョンホプキンス

5
デバッグ専用。ログに記録される場合があります。ユーザーには表示されません。私は例外メッセージをユーザーに提示するのが好きではありません。
コリンマッカイ

回答:


72

私は、例外の後に来るものにさらに答えを向けます:それは何のために、ソフトウェアはどのように振る舞うべきか、ユーザーは例外で何をすべきか?私がキャリアの初期に出会った素晴らしいテクニックは、常に3つの部分で問題とエラーを報告することでした:コンテキスト、問題と解決策。このディシプリンを使用すると、エラー処理が大幅に変更され、オペレーターが使用するソフトウェアが大幅に改善されます。

以下に例を示します。

Context: Saving connection pooling configuration changes to disk.
Problem: Write permission denied on file '/xxx/yyy'.
Solution: Grant write permission to the file.

この場合、オペレーターは何をすべきか、どのファイルに影響を与える必要があるかを正確に知っています。また、接続プーリングの変更が行われなかったため、繰り返す必要があることも知っています。

Context: Sending email to 'abc@xyz.com' regarding 'Blah'.
Problem: SMTP connection refused by server 'mail.xyz.com'.
Solution: Contact the mail server administrator to report a service problem.  The email will be sent later. You may want to tell 'abc@xyz.com' about this problem.

私はサーバー側のシステムを作成し、私のオペレーターは一般的にハイテクに精通した第一線のサポートです。対象読者が異なるが同じ情報を含むデスクトップソフトウェアについては、メッセージの書き方を変えます。

このテクニックを使用すると、いくつかの素晴らしいことが起こります。多くの場合、ソフトウェア開発者は自分のコードの問題を解決する方法を最もよく知っているので、コードを書いているときにこのようにソリューションをエンコードすることは、多くの場合、情報が不足しているため、ソリューションを見つけることに不利なエンドユーザーにとって非常に有益ですソフトウェアが正確に何をしていたか。Oracleエラーメッセージを読んだことがある人なら誰でも私が言っていることを知っているでしょう。

頭に浮かぶ2番目の素晴らしいことは、例外で解決策を説明しようとしているときに、「Xをチェックし、AならばB、そうでなければC」を書いているときです。これは、例外が間違った場所でチェックされているという非常に明確で明白な兆候です。プログラマーはコード内の事柄を比較する能力があるので、「if」ステートメントをコード内で実行する必要がありますが、なぜユーザーを自動化できるものに関与させるのですか?おそらく、コードの深部からのものであり、誰かが怠zyなことを行い、任意の数のメソッドからIOExceptionをスローし、何が間違っていたのかを具体的に説明できない呼び出しコードのブロックでそれらすべてから潜在的なエラーをキャッチした可能性がありますコンテキストとその修正方法。これにより、きめの細かいエラーを記述し、コード内の適切な場所でそれらをキャッチして処理し、オペレーターが実行する必要のある手順を適切に明確にすることができます。

ある会社では、ソフトウェアを非常によく理解し、エラー報告を増やし、解決策を提案する独自の「ランブック」を保持する一流のオペレーターがいました。これを認識するために、ソフトウェアには例外としてランブックへのWikiリンクが含まれるようになりました。これにより、基本的な説明が利用できるようになりました。

この手法を試してみれば、独自のコードを作成する際にコードで例外に名前を付けるべきことが明らかになります。 NonRecoverableConfigurationReadFailedExceptionは、オペレーターに対してより詳細に説明しようとしているもののちょっとした略記になります。私は冗長であることが好きで、コードに触れる次の開発者が解釈しやすくなると思います。


1
+1これは優れたシステムです。どちらがより重要か:情報が伝わることを確認するのか、短い言葉を使用するのか?
マイケルK

3
私は含めて、コンテキスト、問題、解決策の解好きのための1
WebDev

1
このテクニックはとても便利です。私は間違いなくそれを利用するつもりです。
キッドダイヤモンド

コンテキストは不要なデータです。スタックトレースに既に存在します。解決策をとることが望ましいですが、常に可能/有用とは限りません。ほとんどの問題は、アプリケーションを正常に停止するか、保留中の操作を無視して、次回成功することを期待してアプリケーションのメイン実行ループに戻るようなものです...例外クラスの名前は、解決策が明らかになるようにするFileNotFound必要がありConnectExceptionます
ジバンコア

2
@ThomasFlinkowの例では、トレースのスタックトレースにinit()、execute()、cleanup()が含まれます。ライブラリの優れた命名スキーマとクリーンでわかりやすいAPIを使用すると、文字列の説明は不要です。また、システム全体に壊れた状態を持ち込まないでください。一意のIDを持つトレースおよびログ記録は、アプリケーションのフロー/状態を説明できます。
ジバンコア

23

、このより最近の質問私は、例外はまったくメッセージを含んでいてはならないポイントを作りました。私の意見では、彼らがするという事実は大きな誤解です。私が提案しているのは

例外の「メッセージ」は、例外の(完全修飾)クラス名です。

例外は、正確に何が起こったのかについてできるだけ多くの詳細を独自のメンバー変数に含める必要があります。たとえば、には、IndexOutOfRangeException無効であることがわかったインデックス値と、例外がスローされた時点で有効だった上限値と下限値が含まれている必要があります。このように、リフレクションを使用すると、次のようなメッセージを自動的に作成できます。IndexOutOfRangeException: index = -1; min=0; max=5これは、スタックトレースとともに、問題のトラブルシューティングに必要なすべての客観的な情報である必要があります。「インデックス-1が0と5の間ではない」のようなきれいなメッセージにフォーマットしても、値は追加されません。

特定の例では、NodePropertyNotFoundExceptionクラスには、見つからなかったプロパティの名前と、プロパティが含まれていないノードへの参照が含まれます。これは重要です。ノードの名前を含めるべきではありません。実際のノードへの参照を含める必要があります。あなたの特定の場合、これは必要ではないかもしれませんが、それは原則の問題であり、好ましい考え方です。人間によるユーザビリティは重要ですが、二次的な懸念にすぎません。

これは、あなたがキャリアのある時点で目撃したかもしれない非常にイライラする状況を処理します。そこでは、メッセージテキスト内で何が起こったのかに関する重要な情報を含む例外をキャッチしたかもしれません。何が起こったのかを理解するためにテキストの文字列解析を行わなければなりませんでした。メッセージテキストが基礎となるレイヤーの将来のバージョンで同じままであることを期待し、プログラムが他の国で実行します。

もちろん、例外のクラス名は例外のメッセージなので(および例外のメンバー変数は特定の詳細です)、これはすべての異なるメッセージを伝えるために多くの異なる例外が必要であることを意味します。それは結構です。

今、時々、コードを書くときに、すぐにthrowステートメントをコーディングし、新しい例外クラスを作成するために行っていることを中断する代わりにコードの記述を続行したいという誤った状況に遭遇しますすぐそこに投げます。これらの場合、GenericException実際には文字列メッセージを構築時パラメーターとして受け入れるクラスがありますが、この例外クラスのコンストラクターは、このクラスのFIXME XXX TODOすべてのインスタンス化が必要であると述べている非常に大きな巨大な明るい紫色のコメントで飾られていますソフトウェアシステムがリリースされる前、できればコードがコミットされる前に、より特殊な例外クラスのインスタンス化に置き換えられました。


8
C ++のようにGCを持たない言語を使用している場合、スタックに送信する例外に任意のデータへの参照を配置する場合は、十分に注意する必要があります。参照しているものは何でも、例外がキャッチされるまでに破壊されている可能性があります。
セバスチャン・レッド

4
@SebastianRedlはい。また、nodeオブジェクトがusing-disposable(C#の場合)またはtry-with-resources(Javaの場合)句で保護されている場合、C#およびJavaにも同じことが当てはまります。例外が処理される場所で有用な情報を取得するためにアクセスする。これらの場合、オブジェクト自体ではなく、オブジェクトの概要のようなものを例外内に保存する必要があると思います。すべての場合にこれを一般的に処理する絶対確実な方法を考えることはできません。
マイクナキス

13

一般的なルールとして、例外は、有用な情報(期待値、実際の値、考えられる原因/解決策など)を提供することにより、開発者が原因を特定するのに役立ちます。

組み込み型が意味をなさない場合は、新しい例外型を作成する必要があります。特定のタイプを使用すると、他の開発者は特定の例外をキャッチして処理できます。開発者が例外の処理方法を知っているが、タイプがの場合、開発者はException適切に処理できません。


+1-期待値と実際の値は非常に便利です。質問に与えられた例では、あなたは、単にメソッドが失敗したことを言うべきではありませんが、それが失敗した理由(。失敗した基本的には、正確なコマンドを、そして失敗原因事情)
フェリックスDombek

4

.NETではthrow new Exception("...")(質問の著者が示したように)絶対にしないでください。例外はルートの例外タイプであり、直接スローされることは想定されていません。代わりに、派生した.NET例外タイプの1つをスローするか、Exception(または別の例外タイプ)から派生する独自のカスタム例外を作成します。

例外をスローしないのはなぜですか?Exceptionをスローしても例外を説明することは何も行わず、呼び出し元のコードにcatch(Exception ex) { ... }、一般的には良いことではないようなコードを強制的に記述させるためです。:-)。


2

例外に「追加」したいのは、例外またはスタックトレースに固有ではないデータの要素です。それらが「メッセージ」の一部であるか、記録時に添付する必要があるかどうかは興味深い質問です。

既に指摘したように、例外はおそらく何を教えてくれます。スタックトレースはおそらくどこを教えてくれますか。 doh!もちろん」。本番コードでエラーを記録する場合、これはさらに当てはまります。テストシステムに存在しないライブシステムに侵入した不正なデータに頻繁に噛まれました。エラーの原因となっている(または原因となっている)データベース内のレコードのIDを知るのと同じくらい簡単なことで、時間を大幅に節約できます。

だから... .NETの場合、ログに記録された例外データコレクションに追加(cf @Plip!):

  • パラメーター(これは少し興味深いことがあります-シリアル化されない場合はデータコレクションに追加できず、1つのパラメーターが驚くほど複雑になることがあります)
  • ADO.NETまたはLinqからSQLなどに返される追加データ(これも少しおもしろいです!)。
  • それ以外は明らかではないかもしれません。

もちろん、いくつかのことは、最初のエラーレポート/ログにそれらが含まれていない限り、必要なことを知らないでしょう。あなたがそれらを必要とするまであなたが得ることができることに気づかないいくつかのこと。


0

例外何ですか?

(1)ユーザーに何か問題があったことを伝えますか?

これは最後の手段である必要があります。なぜなら、あなたのコードは、例外よりも「良い」何かを割り込んで表示するからです。

「エラー」メッセージには、が間違っていたのか、エラー状態から回復するためにユーザーが何ができるのかを明確かつ簡潔に示す必要があります。

例:「このボタンをもう一度押さないでください」

(2)開発者に間違った時期を知らせる?

これは、その後の分析のためにファイルにログインするようなものです。スタックトレースは、開発者教えてくれるのコードが壊れたの。メッセージは、再び、が間違っていたかを示す必要があります。

(3)何かがうまくいかなかったことを例外ハンドラー(つまりコード)に伝えますか?

例外のタイプは、どの例外ハンドラーがそれを見るかを決定し、Exceptionオブジェクトで定義されたプロパティーは、ハンドラーがそれを処理できるようにします。

例外のメッセージはまったく無関係です。


-3

できるなら新しいタイプを作成しないでください。それらは、さらなる混乱、複雑さを引き起こし、より多くのコードを維持することにつながります。彼らはあなたのコードを拡張する必要がありました。例外階層を設計するには、多くのテストとテストが必要です。それは後の考えではありません。多くの場合、組み込みの言語例外階層を使用するのが最善です。

例外メッセージの内容は、メッセージの受信者に依存します。そのため、その人の靴に身を置く必要があります。

サポートエンジニアは、エラーの原因をできるだけ早く特定できる必要があります。短い説明文字列と、問題のトラブルシューティングに役立つデータを含めます。可能な場合は常にスタックトレースを含めます。これが唯一の真の情報源になります。

システムの一般ユーザーにエラーを提示するかどうかは、エラーの種類によって異なります。たとえば、ユーザーが別の入力を提供することで問題を解決できる場合は、簡潔で説明的なメッセージが必要です。ユーザーが問題を解決できない場合は、エラーが発生したことを表明し、エラーをサポートに記録/送信するのが最善です(上記のガイドラインを使用)。

また、大きな「HALT ERROR!」をスローしないでください。アイコン。それはエラーです-それは世界の終わりではありません。

要約すると、システムのアクターとユースケースについて考えてください。それらのユーザーの靴に身を置きます。助けてください。いいね システム設計でこのことを事前に考えてください。ユーザーの観点から-これらの例外ケースとシステムがそれらを処理する方法は、システムの通常のケースと同じくらい重要です。


10
同意しません。言語APIが正確なニーズに対応していない場合に、独自の例外を実装する多くの正当な理由があります。1つの理由は、複数のことが失敗する可能性のあるメソッドでは、さまざまなタイプの例外に対してさまざまなcatch句を記述して、正確な問題に対応できるからです。もう1つの方法は、抽象化のさまざまなレイヤーを表す例外の複数のレイヤーを分離できることです。ここで、例外が属する正確なレイヤーをそのタイプでエンコードできます。「例外」または「IllegalStateException」とメッセージ文字列を使用するだけではあまり役に立ちません。
Felix Dombek

1
私も同意しません。例外タイプは、それを使用する呼び出しコードにとって意味があります。たとえば、フレームワークを呼び出しているときに内部でFileDoesNotExistExceptionが発生する場合、フレームワークの呼び出し元としては意味がありません。代わりに、カスタム例外を作成し、スローされた例外を内部例外として渡すことをお勧めします。
bytedev
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.