フォーラム


ゲスト  

ようこそ ゲスト さん。このフォーラムに投稿するには 登録が必要です。

ページ: [1]
トピック: 例外を賢く使え (前半) [Delwiki]
RAN
メンバー
投稿数: 34
例外を賢く使え (前半) [Delwiki]
on: 2013/04/14 08:38 Sun

原文 http://www.lemanix.com/nick/articles/1374.aspx

イントロダクション
例外処理機能は非常に初期に、Delphi言語に追加され、コードに対する考え方・記述法を根本的に変えました。不幸なことに、ほぼ10年も使われているにもかかわらず、どのように例外が働くか、特に例外がどのように使われるべきかについて、まだ多くの間違った考え方や理解があります。この文書では、例外処理はどのように動作すべきかについて見ていきます。まず例外の誤用と誤操作から始め、それに基づいて、適切な例外処理の使用法について論じます。不適切に使われた例外処理は問題とエラーを減らすより、その原因となってしまいます。適切に使用されれば、すっきりと記述され、上手に設計された、メンテナンスの簡単なコードを作る手助けをします。この記事は、読者が例外処理の構文と動作法について知っている事を前提とします。

第一章 構造化例外処理
例外はオペレーティング・システムの機能です。この機能により、プログラマーはプロセスの実行を直接停止する事が出来ます。が、割り込みの実行は必要な時、コール・スタック内のどんな場所でも「停止」できます。構造化例外処理は、言語機能と優良な設計の組み合わせです。つまり優良な設計では、コード記述時に有用な仮定を作成し、吟味し、仮定が間違っていると判明した場合、正しく対応する為に例外を利用します。これらの仮定はよく事前条件と呼ばれます・・・それらはメソッドが成功する為に、Trueでなければならない事です。例えば、レコードをデータベースから削除するメソッドの事前条件は、実行前にユーザーがデータベースにログインする事かもしれません。プログラムのユーザー・インターフェースは、ログイン後のみメソッドを実行するように構成されているので、プログラマーは、この事前条件は常にTrueであると仮定するかもしれません。しかし、データベース・サーバーがクラッシュした時、どうしますか?全てのメソッド開始時に、確実にサーバーと接続しているかチェックすれば、プログラマーはログイン問題に対処できるでしょう。しかしログイン問題は、この事前条件だけではありません。ログイン問題は、コード複製の原因となりかねません。またプログラマーは、新しいメソッドを書く時に、関連するチェック項目を全て含むことを忘れるかもしれません。構造化例外処理は、それら全ての問題をエレガントに解決します。構造化例外処理は、事前条件が満たされない場合、メソッドが失敗する事、更にメソッドは、開発者が認識し対応できるような方法で失敗する事を保証します。

第二章 こんな例外はいらない
私は沢山のコンサルティングに関与しましたが、その中には、悪戦苦闘中(最もありがちな原因は、粗悪なデザイン)で、初めから上手く行っていない、コードの書き直しの真っ最中の、既存のプロジェクトも含まれて居ます。私が見た、もっともありがちなコーディング・エラーの1つは、例外の誤用です・・・時々本当に醜悪な誤用があります。記事を始めるに当たって、まずは例外ハンドリング使用で「してはいけない」事をいくつか検討してみましょう。

例外を食べるな
たぶん最もありふれた・・・そして本当に言語道断な・・・私の見た例外の誤用は、例外を「食べてしまう」事です。以下のようなコードを、本当によく見かけます。

 try
時々アクセス違反を見つけるのを難しくする、いくつかのルーチン;
except
end;

 
オエッ!ご覧の通り、このコードは呼び出されたルーチンで発生する、全ての例外を「食べて」しまいます。try ブロック内のこんなコードは、本当にしばしば、見つけにくいエラーを発生させます。こういうエラーを追跡するのは本当に難しい仕事です。プログラマは安易な方法で、例外を食べてしまいます。例外を食べてしまう理由は、エンド・ユーザーが例外メッセージを見ない様にしたいだけ、なんて言う場合も時々あります。''しかし、もしそれが最終目的だとしたら、開発者はそれ以外のコードから、エラーを隠さないような方法で、それをすべきです''。このコードのせいで確実に、ユーザーはエラーメッセージを全く見る事はありません。このコードから発生する可能性のある、全ての単独例外(データベース例外、メモリ不足例外、ハードウェア障害などなど)は、隠蔽されてしまいます。これは、プログラムが成功を表示しているのに、間違った結果を返しているかもしれない、という意味です。給料ミスやそれ以上に悪い結果に終わる可能性のあるエラーが黙って発生してしまうより、失敗を明示的に表示してくれる方が、まだマシです!例外をあっさり食べてしまっても許されると、考えても良い唯一のケースは、例外がモジュールの境界を越えて、伝達されるのを防ぐ必要がある場合だけです。モジュール内プログラミングをしている場合、例えば、DLL内で動作するコードがある場合、開発者は、どんな例外も、現在対象となっているモジュールから逃がすべきではありません。この場合、空の例外ハンドラを使用すれば、それは可能です。しかし、そのようなケースではない場合、このような例外ハンドラは、いまいましいエラーであり、恐怖のコーディングと、みなされるべきです。そのような状況でも、開発者は、どうにかして例外のログを取るべきです。または何とかして、例外を知らせるべきです。''例外を食べるという意味は、エラーについての情報・・・つまりエラーを簡単に直すことが可能な情報・・・を永久になくしてしまうと言う事です。顧客は決して、エラーがあるとは気づかないでしょう。また気づいた時でさえ、どうして起きたのか、どうやって直すのか、開発者は理解できないでしょう。''

結論:例外を食べるなかれ

一般的な例外をトラップするなかれ
時々、こんなコードを見かけます。

 try
問題を発生させるかもしれない、いくつかのコード;
except
on E: Exception do
begin
MessageDlg(E.Message, mtWarning, [mbOK], 0);
end;
end;

 
「これは、カフェインなしのダイエット・コーラを飲んでいるようなものだ」と思います。言い換えれば、なぜわざわざ、こんな事をするんでしょう?このコードは、どこかで表示されそうな例外を、表示する以上の事は何もしません。実際には、これはもう1つ別のこともします。つまり例外を急停止してしまいます。この例外はローカルで操作され、現在のスコープを抜ける事を許されません。その上、これは全ての例外を捕まえてしまいます。開発者がトラップしたいと全く思っていない例外も含んでしまいます。この構文を使う事を考えても良い唯一のケース・・・それは例外を全部食べてしまうより、ちょっとだけましなだけの事・・・は、呼び出しているルーチンが、どんな例外も操作したくないと言う事が分かっている場合。または呼び出しているルーチンが、特定の例外を操作すると予期できる場合です。例えば、TClientDatasetにはOnReconcileErrorイベントがあります。これもまた、例外をイベントに渡します。もしClientdatasetで、いくつかのバッチ処理を行っている場合、この例外がスタックへ、ぶくぶくぶくと知らせに行ってしまう事を認めると、ループが停止してしまいます。この場合、開発者は一般的に、イベント・ハンドラへ渡される、全ての例外をトラップしたいかもしれません。

例外を期待するなかれ
例外の生成と操作は、処理能力の点で相当高くつきます。よって、開発者は当然のものとして例外を生成すべきではありません。更に例外の性質上、開発者は意図的に例外を生成したり、エラーチェックの為に例外を使用すべきではありません。

例えば、開発者が、以下のような事(この例は明らかに不自然です)をしようとするかもれません。

 function StringIsInteger(aStr: string): Boolean;
var
Temp: integer;
begin
Result := True;
try
Temp := StrToInt(aStr);
except
Result := False;
end;
end;

 
このコードは、当然ながら開発者が意図した事をします。つまりStringが妥当なIntegerを保持しているかどうか判定します。が、これも、たぶん沢山の例外を発生させ、パフォーマンスを害します。特に、falseを返すと予想される回数が多い場合です。''パフォーマンスの問題は別にしても、これは例外ハンドリングの間違った使い方です。その理由は、それがメソッドの事前条件を侵害しないからです。このメソッドは明らかに、integer以外の引数を受け入れるように、設計されています。よって、そのようなケースを処理する為に例外メソッドを、内部的にでさえ、使用すべきではありません。このメソッドのより良い実装は、SysUnitのTryStrToInt関数を使うことです。''

一般的な発信システムとして、例外処理システムを使うなかれ

 type
TNormalBehaviorException = class(Exception);

begin
通常の処理であり、どのようなエラーも起こさない、いくつかのコード;
raise TNormalBehaviorException.Create('全く普通で' +
'期待した通りの物’);
end;

 
開発者は、呼び出しコードに何か情報を発信する手段として、上記のコードを記述したいという、誘惑にかられるかもしれません。特に、カスタム例外ハンドラに、呼び出しルーチンへ返信する追加情報を持たせたい場合です。例外は、情報を運ぶツールであるのと同時に、プログラムの流れを制御する仕組みである事を思い出してください。開発者が単にメッセージを送りたいだけの場合に例外を発生させてしまうと、プログラムの流れに、予期しない影響を与えてしまいます。情報を発信するようにする事は、イライラの元かもしれません。アプリケーションが件の例外を見失っていない場合(例外を、常に補足し操作している場合)や、エラーメッセージを表示しない場合(EAbortを継承したカスタム例外が、結果的にユーザーに対してエラーメッセージを表示しない場合)でさえ、そうです。まず第一に、これはCPUに負担が大きい情報発信・送信方法です。第二に、このエラーは設計時に表示され、このコードの利用者を激怒させるでしょう(非常に人気のあるサードパーティ製ライブラリは、このような事をします。これは気が散ります)。一般的なルールとして、開発者が例外を発生させる場合、IDEでその例外を無視するように、利用者に設定(例外リストへの追加)を求めるなら、何が何でも、この例外を発生させるのか、二回は考えるべきです。

ページ: [1]
WP Forum Server by ForumPress | LucidCrew
バージョン: 1.7.5 ; ページロード: 0.042 sec.