10週間ウェブ開発講座

裏メニュー第六週

例外処理

プログラム実行中に発生しうる不測の事態を「例外」と呼びます。
この例外を上手に処理し、処理を続行できる状態に持っていくことを「例外処理」と呼びます。

日経ソフトウェアから、例外処理についての解説を一部分引用します。

例えば,ある関数(あるいはメソッド,以下同)内で例外が発生したときの対処方法として,
関数の返り値として例外の種類に応じた値を返す方法があります。
関数の呼び出し元は,関数の返り値をチェックすることによって,例外の種類に応じた処理を行うわけです。

この方法はわかりやすいのですが,あらゆる例外に対処しようとすると,
関数の呼び出し元で返り値をチェックするためのコードが煩雑になるという問題点があります。
チェックのためのコード量がプログラム本来の処理以上にふくれ上がり,
プログラムの見通しが悪くなることもしばしばです。

そこで,最近のプログラム言語の多くは,例外処理を簡潔に記述できる
「構造化例外処理(SEH:Structured Exception Handling)」の機能を備えています。
関数には,不測の事態が起こったときには
「例外オブジェクト」を生成することで外部に例外の発生を知らせる仕組みを作り込んでおきます。

例外オブジェクトの役割と機能について学びましょう。

例外オブジェクトを用いない例外処理

先ほど引用した記述によると、例外が発生したときの通知方法には:

  1. 関数の返り値として例外の種類に応じた値を返す方法
  2. 例外オブジェクトを生成することで外部に例外の発生を知らせる方法

の二通りの方法があるとされています。
まずは前者の「関数の返り値として例外の種類に応じた値を返す方法」について考えましょう。

  1. <?php
  2.  
  3. function validate($mail_address) {
  4.     // それほど厳密でないメールアドレスのチェック
  5.     // ref) http://blog.livedoor.jp/dankogai/archives/51189905.html
  6.     if (1 === preg_match('/^[a-zA-Z0-9_-]+\@[a-zA-Z0-9_-]+(?:\.\w+)+$/', $mail_address, $matches)) {
  7.         return true;
  8.     } else {
  9.         return false;
  10.     }
  11. }
  12.  
  13. // 電子メールアドレス(のみ)が要素となっていることが期待される配列
  14. $mail_address_list = array('foo@example.com',
  15.                            'bar@example.com',
  16.                            'error_mail_address');
  17.  
  18. foreach ($mail_address_list as $mail_address) {
  19.     if (validate($mail_address)) {
  20.         echo "{$mail_address} は(たぶん)電子メールアドレスです(...なんじゃないかなあ)。\n";
  21.         // 本当はここから色々な処理が走る...
  22.     } else {
  23.         echo "エラー!\n";
  24.         // 本当はここからエラー処理が走る...
  25.     }
  26. }
  27.  
  28. ?>

ソースコード中のコメントにある通り配列$mail_address_listは電子メールアドレスが要素となっていることが期待されています。
しかし、3番目の要素('error_mail_aaddress')は明かに電子メールアドレスの形式をしていません。
したがって、この場合はx行目から始まるif文の中でelse節のエラー処理に入ることになります。

これはこれで上手く機能するプログラムだと言えます。
しかし、例えばプログラマがelse節を書き忘れてしまうとどうなるでしょうか?
もしかしたら「処理すべきデータの数と、処理したデータの数が合わない...」と途方に暮れてしまうかもしれません。

このような「関数の返り値から処理を分岐させる」例外処理とは異なり、
プログラマに例外処理の記述を強要する方法があります。
それは「例外オブジェクトを生成することで外部に例外の発生を知らせる方法」です。

例外オブジェクトを用いた例外処理

ここで「例外オブジェクトを生成することで外部に例外の発生を知らせる方法」について説明します。

先ほどのプログラムを少し変更して次のようにします。

  1. <?php
  2.  
  3. function validate($mail_address) {
  4.   // それほど厳密でないメールアドレスのチェック
  5.   // ref) http://blog.livedoor.jp/dankogai/archives/51189905.html
  6.   if (1 === preg_match('/^[a-zA-Z0-9_-]+\@[a-zA-Z0-9_-]+(?:\.\w+)+$/', $mail_address, $matches)) {
  7.     return true;
  8.   } else {
  9.     throw new Exception('メールアドレスではない。');
  10.   }
  11. }
  12.  
  13. // 電子メールアドレス(のみ)が要素となっていることが期待される配列
  14. $mail_address_list = array('foo@example.com',
  15.                            'bar@example.com',
  16.                            'error_mail_address');
  17.  
  18. foreach ($mail_address_list as $mail_address) {
  19.   if (validate($mail_address)) {
  20.     echo "{$mail_address} は(たぶん)電子メールアドレスです(...なんじゃないかなあ)。\n";
  21.     // 本当はここから色々な処理が走る...
  22.   } else {
  23.     echo "エラー!\n";
  24.     // 本当はここからエラー処理が走る...
  25.   }
  26. }
  27.  
  28. ?>

これを実行してみましょう。次のようになるはずです。

foo@example.com は(たぶん)電子メールアドレスです(...なんじゃないかなあ)。
bar@example.com は(たぶん)電子メールアドレスです(...なんじゃないかなあ)。
PHP Fatal error:  Uncaught exception 'Exception' with message 'メールアドレスではない。' in /path/to/script.php:9

Fatal error(致命的なエラー)が検出され、プログラムがそこで停止しています。
これはvalidate関数が例外を投げたのにvalidate関数の呼出し側が例外をキャッチしなかったことが原因です。
例外を「投げる」や「キャッチする」ということがどういうことなのかを説明する前に、
次のプログラムを実行してみてください。

  1. <?php
  2.  
  3. function validate($mail_address) {
  4.   // それほど厳密でないメールアドレスのチェック
  5.   // ref) http://blog.livedoor.jp/dankogai/archives/51189905.html
  6.   if (1 === preg_match('/^[a-zA-Z0-9_-]+\@[a-zA-Z0-9_-]+(?:\.\w+)+$/', $mail_address, $matches)) {
  7.     return true;
  8.   } else {
  9.     throw new Exception('メールアドレスではない。');
  10.   }
  11. }
  12.  
  13. // 電子メールアドレス(のみ)が要素となっていることが期待される配列
  14. $mail_address_list = array('foo@example.com',
  15.                            'bar@example.com',
  16.                            'error_mail_address');
  17.  
  18. foreach ($mail_address_list as $mail_address) {
  19.   try {
  20.     if (validate($mail_address)) {
  21.       echo "{$mail_address} は(たぶん)電子メールアドレスです(...なんじゃないかなあ)。\n";
  22.     }
  23.   } catch (Exception $exception) {
  24.     echo '例外をキャッチしました: ' . $exception->getMessage() . "\n";
  25.   }
  26. }
  27.  
  28. ?>

これを実行してみましょう。次のようになるはずです。

foo@example.com は(たぶん)電子メールアドレスです(...なんじゃないかなあ)。
bar@example.com は(たぶん)電子メールアドレスです(...なんじゃないかなあ)。
例外をキャッチしました: メールアドレスではない。

今度はプログラムが最後まで走りきりましたね。

このプログラムにおける、x行目から始まる try {...} の部分をtryブロックと呼びます。
tryブロックに例外が投げられ得る部分を含めます。
またx行目から始まる catch (...) {...} の部分をcatchブロックと呼びます。
catchブロックには例外が投げられたときの処理を記述します。
catchブロックではExceptionクラス(またはその子クラス)のオブジェクトを使用できます
(この例では$exceptionがExceptionクラスのオブジェクトです)。

try,catchは次の形式で記述します。

try {
  例外が発生するかもしれない箇所
} catch (例外クラス 例外オブジェクト) {
  tryブロックで発生した例外の尻ぬぐい
}

上記の通り、tryとcatchは常に一セットになります。

throwとtry,catchの機構を使用することで例外処理の記述を強制することが可能になります。
先ほど見たように、例外が発生する場所でtry,catchを記述されていないとプログラムは強制的に終了してしまいます。
適切な箇所でthrowとtry,catchを使用することで、プログラムの質を向上させることが可能です。

例外クラスの作成

全ての例外クラスはExceptionクラスの子クラスです。
Exceptionクラスの概要は次の通りです。

  1. <?php
  2.  
  3. class Exception {
  4.   /* プロパティ */
  5.   protected string $message; // 例外メッセージ
  6.   private string $string; // 内部の例外名
  7.   protected int $code; // 例外コード
  8.   protected string $file; // 例外がスローされたファイル名
  9.   protected int $line; // 例外がスローされた行
  10.   private array $trace; // スタックトレース
  11.  
  12.   /* メソッド */
  13.   public __construct ([ string $message = "" [, int $code = 0 [, Exception $previous = NULL ]]]);
  14.   final public string getMessage ( void );
  15.   final public Exception getPrevious ( void );
  16.   final public int getCode ( void );
  17.   final public string getFile ( void );
  18.   final public int getLine ( void );
  19.   final public array getTrace ( void );
  20.   final public string getTraceAsString ( void );
  21.   public string __toString ( void );
  22.   final private void __clone ( void );
  23. }
  24.  
  25. ?>

Exceptionクラスを継承して独自の例外クラスを作成することも可能です。

宿題

  • 本文でも少し触れていますが「関数の返り値による例外処理」と「例外クラスによる例外処理」の差異を自分の言葉でまとめてください。
  • 「tryブロックのネスト(入れ子)」とは何か調べてください。また、それがどういう場面で有効なのかも併せて調べてください。
  • PDOが投げるPDOExceptionという例外があります。PDOExceptionを意図的に発生させ、例外をキャッチして例外メッセージと例外コードを出力するプログラムを書いてください。

参考資料


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-10-10 (土) 22:33:22 (330d)