お知らせ

電子会議

ライブラリ

FDelphi サイト全文検索

Delphi FAQ一覧

サンプル蔵



FDelphi FAQ
15番会議室「FAQ編纂委員会」に寄せられた「よくある質問の答え」

[Q]
 構造化例外の言葉の意味は分かりましたが,その良さが理解できません. 具体例をあげて使い方を説明して下さい.

[A]
参考資料: http://www.njk.co.jp/ にわかりやすい図がある

<<< 従来のエラー処理 >>>
 下記 Proc00 は引数の値によってはゼロで割ってしまう実行時エラー
が生じてプログラムがハングしてしまう可能性があります.それで,演算の
前に除数がゼロでないことを確かめて,結果が正しければError値をFalseに,
演算不能なら Error値を True にするというチェック機能を実装しました.

function Proc00(const X,Y: integer; var Error: boolean): integer;
begin
  if Y = 0 then begin    // エラーチェック
    Error := True;
    Exit;
  end;
  Result := X div Y;     // 正常処理
  Error := False;
end;

 この関数を呼んだ人は,エラーコードを見て正しいことを確かめてから
次のステップに進むことができます.もし誤りならそこで中断して,
自分を呼んだ人に対して,また結果の正当性を返すことになるはずです.

// 中間手続きのコード
procedure function Proc0(var Error: boolean);
var X,Y,Z,.....
begin
  Z := Proc00(X,Y, Error);  // 実行してみる
  if Error then begin       // 異常だ
    Exit;                   // 異常なら処理を中断して戻る
  end;
                 // 正常終了なら
  ProcNext;      // 次のステップ
  .....          // 他にも可能性があれば全部判断が必要
end;

この Proc0 を呼んだ人はこの結果を判断して,もしダメならさらに
上位の呼出元に対して結果を返す条件分岐のコードを書かなければなりません.
最終的に最上位の手続きの中で「演算に失敗しました」というダイアログを
表示してユーザーに知らせる義務があるとすると,そこから呼び出される
手続きは,すべてその戻り値にエラーのあり無しを設定するコードを記述する
必要が生じます.
 ところが,そもそもエラーが起きるかどうかは一番ネストが深い Proc00
を呼び出してみなければわからず,しかも中間の Proc0 などではエラーが
起きたからと言ってなにかをやる必要があるわけでもないのに,上位の
呼出元に結果を返すためだけのコードや,結果のための引数を取り扱わな
ければいけないという煩雑さが生じているのです.

  Proc
    |
    +------Proc0
              |
              +-------Proc00
                       |
                !!! エラー発生 !!!
                       |
              ←←←←←
              |   ↑・・・エラーの伝達(戻り値など)
    ←←←←←
    |  ↑・・・エラーの伝達(戻り値など)
    |
エラーの処置(ダイアログ表示等)
    |
 本当にエラーが起きたかどうかを知りたいのは最上位の呼出元である Proc 
手続きだけで,中間のProc0にしてみれば単に処理を中断して呼び出し元に
リターンしてくれればいいだけなのですが,呼出元に結果を伝えるための
コードや,自分の処理を中断するためのif文もたくさん必要になります.


<<<<<<<<<<<< 構造化例外を使うと >>>>>>>>>>>>

 構造化例外の扱いには3つの立場があります.

・呼び出される手続きの側で,正常に任務を完遂できなくなったことを
 呼出元に知らせる立場
  → 例外オブジェクトを生成する(投げるとも言う)ひと

・呼び出した手続きが異常終了したことを知る立場
  → 例外オブジェクトをキャッチするひと

・例外について関与しない立場
  → 例外をキャッチせず,呼出元にそのままスルーするひと

try ... except ... end; は上記2番目の「例外をキャッチして処理する」
構文ですが,「処分」と言った方がわかりやすいかも.なぜなら except で
例外を引っかけると,その後は例外(という事態)は終了して正常状態に
戻ってしまうからです.

 さて,上記 Proc00 の構造化例外版は下記のようになります.

function Proc00(const X, Y: integer): integer;
begin
  if Y = 0 then begin
    raise Exception.Create('ゼロで割っちゃいかんでっせ(ーー;');
  end;
  Result := X div Y;
end;

中間手続き Proc0 には,何も書く必要がありません.

// 上記手続きを呼び出すネストの中間の手続き
procedure Proc0;
var Z: integer;
begin
  Z := Proc00(0,0);
  .....          // 他に可能性があっても何も書く必要なし
end;

 そして最上位の呼出元である Proc にもダイアログを書くコードが
要らないんです.どうです,すっきりしてしまったと思いませんか?
これだけで,もしException が生成,raise されれば,
   +-------------------------------------+
     |  × ゼロで割っちゃいかんでっせ(ーー;  |
     |            [ O K ]              |
   +-------------------------------------+
というダイアログが表示されて,しかも処理は中断されて,手続きの呼出
ネストも最上位の呼出元(Delphiだとメッセージループかな)まで戻って
きてるのです.
ネストの中間の手続きには,例外に関して一切のコードを書く必要はあり
ません.従来の方法で必要だったエラーコードのための余計な引数も
必要ありません.

  Proc
    |
    +------Proc0
              |
              +-------Proc00
                       |
                !!! エラー発生 !!!
                       |
    ←←←←←←←←←←
    |   ↑・・・例外時の制御は別ルート,中間は素通り
    |
必要ならエラーのキャッチ.ほっておけば素通り

 もし例外の発生をキャッチして独自の処理を行いたければ

// 例外をキャッチして独自処理を追加する
procedure AnotherProc;
begin
  try
    Proc0;           // 実行してみる
  except             // 例外のキャッチ
    ShowMessage('ちょっと言葉遣いがわりぃんだけど');
    raise;           // 例外を再生成して呼び出し元へ伝達
  end;
end;

てな感じで,処理を追加したりすることもできます.
 ここにある raise は「例外の再生成」というやつですが,これを
やめれば「じゃん!」音と共に出る例外ダイアログは抑制され,例外
オブジェクトはここの except部で消滅します.つまり適切なリカバリが
可能ならそれを記述すれば,たとえば例に挙げた演算では例外時にそれ
なりの値を設定して次の処理へつなげる等の代替手段を提供すること
で,そこから後は通常の処理を継続することも可能になります.

// サイレント例外にしてしまう
procedure Proc;
var Z: integer;
begin
  try
    Proc0;           // 実行してみる
  except             // 例外のキャッチ
    // 代替手段を記述
  end;               // 例外オブジェクトはここで消滅
                     // 次のステップは正常に続行できる
end;

 ところで「その1」で説明した try ... finally ... end; の構文
では,例外はキャッチされはしません(そのまま上位の呼出元に伝達
される)が,finally 部のコードは中断の対象にならず,必ず実行さ
れてからリターンします.

 なお,構造化でない例外処理として「エラー処理のためのハンドラ」
を別途定義してそれを間に挟んで呼び出すとか,エラーの時に飛んでゆく
ラベル OnErrorGoTo などの構文もありますが,先に説明した
「従来のエラー処理」が持っている根本的な問題点,つまり深くネスト
した手続きの呼出や,一つの手続きの中でエラーの可能性のある場所が
多数ある場合をすっきりと記述できない,等は解決できません.
[例]
// 例外が起きうる手続き
function Proc00(const X, Y: integer): integer;
begin
  if Y = 0 then begin
    raise Exception.Create('ゼロで割っちゃいかんでっせ(ーー;');
  end;
  Result := X div Y;
end;

// 上記手続きを呼び出すネストの中間の手続き
procedure Proc0;
var Z: integer;
begin
  Z := Proc00(0,0);
end;

// サイレント例外にしてしまう
procedure Proc;
var Z: integer;
begin
  try
    Proc0;           // 実行してみる
  except             // 例外のキャッチ
    // 代替手段を記述
  end;               // 例外オブジェクトはここで消滅
                     // 次のステップは正常に続行できる
end;

// 例外をキャッチして独自処理を追加する
procedure AnotherProc;
begin
  try
    Proc0;           // 実行してみる
  except             // 例外のキャッチ
    ShowMessage('ちょっと言葉遣いがわりぃんだけど');
    raise;           // 例外を再生成して呼び出し元へ伝達
  end;
end;

// 大元の呼出もと
procedure TForm1.FormClick(Sender: TObject);
begin
  AnotherProc;  // 独自処理追加の例
  // Proc;      // サイレントの例
end;


ここにあるドキュメントは NIFTY SERVEの Delphi Users' Forum FDELPHIに寄せられる質問の中から、よくある質問への回答を FDELPHIのメンバーがまとめたものです。 したがって、これらの回答はボーランド株式会社がサポートする公式のものではなく、掲示されている内容についての問い合わせは受けられない場合があります。

Copyright 1996-1998 Delphi Users' ForumFAQ編纂委員会