はじめに
ここでは,WEB+DB PRESS Vol.51 特集2「~現場の知恵と経験,絞り出しました~“巧い”メソッド設計」連動記事として,エラー処理の考え方について解説します。
エラー処理のプログラミング
「エラー処理」という言葉を聞くと,Javaプログラマなら,例外処理を思い浮かべるかもしれません。あるいはメソッドの戻り値で成否を判定することを思い浮かべるかもしれません。しかし,これはエラー処理の一部分にすぎません。
まず,エラー処理はなぜ必要なのかを再確認してみましょう。もしプログラムがエラーに対処するように作られていない場合,プログラムの実行中にエラーが発生すると,その後どのような動きとなるかは不定となります。不定状態に陥ったプログラムはもはや機能を果たしているとは言えず,異常終了(クラッシュ),停止(ハングアップやフリーズ),誤った結果を出す(異常動作),などの事態を招きます。
巧いプログラム,すなわち信頼性の高いプログラムは,プログラムが動作するときに発生する可能性のあるさまざまなエラーを想定し,想定したエラーに対処するエラー処理コードが書かれており,プログラムが不定状態に陥ることのないように作り込まれています。
エラー処理とは,想定したエラーに対してプログラムが意図通りに動作するための処理のことを意味します。
本節では,エラーにはどのような種類があるのか,発生したエラーに対してどのような対処を行うか,エラーを検出する方法,検出したエラーの処理方法について述べます。
エラーの種類
まず,エラー処理の対象となるエラーの種類を分けます。想定するべきエラーの数は多岐に渡りますが,エラーが発生する原因に着目してエラーの種類を絞り込むことができます。エラーの種類(原因)の例を表1に示します。
表1 エラーの種類とエラー例
エラーの種類(原因) | 例 |
ユーザとのインタフェース | ユーザの誤入力,誤操作 悪意あるユーザの不正アクセス |
プログラム内部のバグ | 仕様,設計,実装,設定バグ |
プログラム外部の異常 | ハードウェアの故障,ディスク空容量の欠乏
|
ユーザとのインタフェースが原因で発生するエラーは,プログラム内でエラー対処ができないので,ユーザに誤りを知らせ,正しい方法で操作をやりなおすことを促します。
プログラム内のバグが原因で発生するエラーは,ユーザには対処できません。プログラム外部の異常が原因で発生するエラーは,ユーザが正しく操作し,プログラムにバグがありません。
エラーの対処
ユーザとのインタフェースが原因のエラーに対しては,ユーザに誤った入力・操作を取り消し,正しい入力・操作をやりなおしてもらうことが対処になります。
そこで,ユーザに対して何が間違ったのかを指摘して,正しい入力・操作を示す必要があります。通常は,エラーが発生した直後にユーザインタフェース(画面)を用いて知らせます。たとえば,「日付の指定が間違っています。予約日は本日から30日以内で指定してください」のように示します。よく見かける,「不正な操作です」「内部エラーが発生しました」といったエラーメッセージを画面に表示するのは適切ではありません。
プログラム内部のバグが原因のエラーに対しては,開発者にプログラムを修正してもらうことが本質的な対処となります。ユーザ自身が開発者でない限り,エラーが発生した時点でバグを修正することはできません。そこで,プログラムでは,ユーザにエラーが発生したことを知らせ,プログラムを安全に終了させ,エラーが発生した詳しい状況を開発者に伝えるという対処を行います。
安全に終了させるのは,バグによるエラーが発生したあとにプログラムを続行しても,以降のプログラムの実行結果が正しいことを保証できないためです。間違ったまま動作し続けると,誤った結果を出力し,データを破壊し,危険な制御状態になり被害を持たらします。間違って動作することで発生する被害を防ぐために,プログラムを停止させます。
エラーの発生を知らせる際,ユーザに知らせる内容と開発者に伝える内容は異なる点を意識しましょう。ユーザに知らせる内容は,プログラムがエラーにより終了することとその理由,取ってほしいアクションとなります。ユーザは開発者ではないことを前提とし,開発者にしか意味がわからないメッセージをユーザに出さないようにします。開発者に伝える内容は一般的にはログを使います。例外のスタックトレースをエラー通知画面に表示するのは不適切です。
プログラム外部の異常が原因のエラーに対しては,管理者に外部の異常状態を解消してもらうことが本質的な対処となります。ユーザ自身が管理者ではないこともあるので,エラーが発生した時点で異常状態を解消できません。そこで,プログラムでは,可能であれば異常状態からの回復を試み,回復ができない場合はユーザにエラーが発生したことを知らせ,異常状況を管理者に伝える対処を行います。
異常状態からの回復の例としては,ネットワークやサーバの一時的な過負荷で発生したエラーがあります。一時的な負荷は時間をあけると解消していることが期待できるので,リトライ処理を行います。
ユーザに知らせる内容と管理者に伝える内容は異なります。しかし,管理者は開発者ではないので,ログを使用する場合でも開発者向けのものとは異なる内容となります。開発者用と管理者用でログレベルを分けるか,ログ出力先を分け,ログメッセージも開発者ではない管理者に理解できる内容とします。
表2に,まとめを示します。
表2 エラーの原因と対処
エラーの原因 | 対処者 | ユーザへの通知 | プログラムでの対処 |
ユーザインタフェース | ユーザ | 誤りと正しい方法 | 再操作を受け付ける |
プログラム内のバグ | 開発者 | バグの発生 | 発生状況の保存と安全な終了 |
プログラム外部異常 | 管理者 | 異常の発生 | 発生状況の保存と回復処理 |
コラム「バグだからといってプログラムは終了させず,なるべく動かし続けろ」
たとえバグがあってもプログラムのほかの機能が動く可能性があるのだから,いちいちプログラムを終了させるのではなく,動かし続けろ,という意見を聞くことがあります。
試験においては引き続きほかの試験項目を消化できるので作業効率が上がる,運用においてはプログラムが止まると迷惑をかける(あるいはシステム停止という事故扱いされる),などの理由に基づくものと思われます。
一見もっともらしいと思われますが,落とし穴があります。
バグで機能が正常に果たせない状態に陥ったプログラムの動作を継続しても,その結果が正しいことは保証できません。プログラムのバグには,ほかの機能へは一切影響を及ぼさないという気の利いた配慮はありません。バグが発生しても動かし続けるということは,「運がよければほかの機能は動きますが,運が悪ければ連鎖的に動作が不定に陥ります。」と言っているにすぎません。たいていの場合は運が悪いほうに倒れ,誤った結果を出力し,データを損い,状況を悪くする結果になります。
エラーの検出
プログラムの実行中に発生するエラーを検出するためには,エラーを検査するコードをプログラム中に記述する必要があります。
ユーザとのインタフェースが原因のエラーを検出するには,ユーザからの入力・操作を受ける個所で,必ず入力・操作の検査を行います(図1)。入力内容の検査は,入力したテキストや選択内容に対するバリデーションチェックを行います。操作の検査は,状態遷移を構成し,その状態での可能な操作であるかを検査します。エラーを検出したら,前節で述べたユーザとのインタフェースが原因のエラー対処を行います。
図1 ユーザインタフェースのエラー検出
![図1 ユーザインタフェースのエラー検出例 図1 ユーザインタフェースのエラー検出]()
プログラム内部のバグが原因のあらゆるエラーを検出する万能な方法は存在しません。そこで,通常はプログラムの内部を分割してその分割点において,入力データ,出力データ,状態検査を行います。オブジェクト指向プログラミングでは,プログラムの動作はメソッドを呼び出して行われ,状態はオブジェクトに保持されます。そこで,分割点における検査としては,メソッドの引数の値と戻り値,インスタンスの状態,などが正しい範囲にあるかの判定を行います。エラーを検出したら,前節で述べたプログラム内部のバグが原因のエラー対処を行います。詳細については,WEB+DB PRESS Vol.51特集2「“巧い”メソッド設計」第6章「メソッドのエラー処理を考える」で解説していますので参考にしてください。
プログラム外部の異常が原因のエラーは,プログラムの外部に作用するシステムコールなどのAPIの呼び出しが失敗したことで検出できます。しかし,中にはAPI呼び出しではエラーとならずに,タイムアウトなどの補助手段を使わないと検出できないエラーもあります。TCP/IPネットワーク通信におけるデータの送受信がその例です。
検査内容は,システムコール(Javaの場合は,OSやミドルウェアのシステムコールは直接呼び出すことがないので,Javaのクラスライブラリのメソッド呼び出し)の呼び出し結果を調べることになります。エラーを検出したら,前節で述べたプログラム外部の異常が原因のエラー対処を行います。