作成日 2009/5/5
最終更新日 2009/5/5

例外の伝播の方法

 例外の伝播(※1、※2)について説明します。
 実は、この内容はオブジェクト指向との関係が良くわからなかったので、HPに載せるかどうか迷っていました。しかし、プログラミングをする上では避けて通れないことであるため、載せることにしました。

-----
※1:関数やメソッド内で発生した例外(エラーと言ったほうがわかりやすいかもしれない)を、関数やメソッドの呼び出し元に伝えること。
※2:伝播は「でんぱ」と読みます。「でんぱん」ではありません。

1.例外の伝播(でんぱ)の方法
2.関数の戻り値(int型)で例外を返す場合
3.関数の引数(out引数、型は構造体やクラス)で例外を返す場合
4.例外オブジェクトをスロー(throw)する。VB6、VBAであればraiseする場合
5.まとめ
6.参考文献など


1.例外の伝播(でんぱ)の方法

 まずは、例外の伝播(でんぱ)の方法を紹介します。

 例外の伝播というと、よくわからないので、言い換えると、
「関数やメソッド内で発生した例外(エラーと言ったほうがわかりやすいかもしれない)を、関数やメソッドの呼び出し元につたえること」
です(※1)。

 で、伝播の方法ですが、だるまの今までのシステム開発経験では以下の3つがありました。
  1. 関数の戻り値(int型)で例外を返す。
  2. 関数の引数(out引数、型は構造体やクラス)で例外を返す。
  3. 例外をスロー(throw)する。VB6、VBAであればraiseする。
 これらについて、特徴とメリット・デメリットを説明します。
  
-----
※1:スレッドが関係してくる場合は、スレッド内で発生した例外をスレッドを生成したスレッドに伝える方法についても考える必要があります。
この場合の伝播の方法は関数やメソッドの場合とは違います。ただし、難しいのでここでは考えないことにします。
このページのトップへ

2.関数の戻り値(int型)で例外を返す場合

 C言語でよく見かける例外の伝播の方式です。
 printf関数 のような標準関数からbind関数(ネットワーク関連の関数)、WindowsやMacOSなどのOSが提供しているAPIの多くの関数の例外の伝播方式がこれです。
 戻り値では返しエラーが発生したかどうかを返し、その詳細はerrnoを調べることによってできるようにしている場合もあります。また、戻り値がint型ではなく、構造体のポインタ型で失敗した場合にNULLを返すような場合もあります。
 あ、もちろんC言語以外の言語(C++、Java、VB6、VBA)でもこの方式で例外を伝播させることは可能です。

■例(言語:VBA)

Module1

Option Explicit
Public Const ERR_NO_ERR As Integer = 0 '正常終了
Public Const ERR_DIVIDE_0 As Integer = -1 '0で割ろうとしたときのエラーコード

'x÷yを計算し、結果をzに格納します
'戻り値:yが0のとき:ERR_DIVIDE_0
'     :上記以外の場合:ERR_NO_ERR
Function divide(ByVal x As Double, ByVal y As Double, ByRef z As Double) As Integer
    If y = 0 Then
        divide = ERR_DIVIDE_0
        Exit Function
    End If
   
    z = x / y
    divide = ERR_NO_ERR
   
End Function

'メイン関数
Sub main()
   
    Dim ret As Double
    Dim errNo As Integer
   
    errNo = divide(1, 0, ret)
    If errNo <> ERR_NO_ERR Then
        Debug.Print "エラーが発生しました"
        Exit Sub
    End If
    Debug.Print ret
   
End Sub

 例のdivide関数のようにエラーが発生したときに、それ用のコード値を返し、呼び出し元(メイン関数)ではその戻り値を判断します。

■メリット
  1. 例外の伝播方法が直感的でわかりやすい。
■デメリット
  1. 呼び出し側が例外処理を書き忘れても(if文を忘れても)、コンパイルエラーとならず、バグの原因となることがある。
  2. 呼び出し側のソースにif分岐が多くなる(正常系処理と異常系処理の混在)ことによる可読性の低下が起こる。
  3. エラーコードを数値型で定義するのですが(例でも2つ定義しています)、エラーコードの重複が起こりやすいです(エラーコードの重複は、もちろんバグです)。
  4. 格納できる情報が少ない(できれば、例外の詳細や、どのソースの何行目で起こったのか、トレースなどが欲しいかな)。
    それと、例外を伝播させる途中で別の例外に置き換えたい場合がある場合があります(例外の連鎖)。それに対処できない。
  5. 例を見てもらえばわかるのですが、divide関数の戻り値はエラーコードではなく、xをyで割った結果とした方が自然です。そうなっていない分、可読性が悪くなっています。
  6. もし、設計時にうっかり戻り値をエラーコードではなく、xをyで割った結果としてしまっていると(この方が自然なので良く起こる)、後で修正が大変(関数の戻り値や引数の変更(リファクタリング)は厄介であるため)。
このページのトップへ

3.関数の引数(out引数、型は構造体やクラス)で例外を返す場合

 CORBAで定義されるIDLをC言語へマッピングしたときや、JNI(Java Native Interface)にてC言語のソースを書いたときはこの方式です。
 あ、もちろんC言語以外の言語(C++、Java、VB6、VBA)でもこの方式で例外を伝播させることは可能です。

■例(言語:VBA)

module2

Option Explicit

Type ErrInformation 'エラー情報を格納するユーザー定義型
    message As String
    type As Integer
End Type

Public Const ERR_NO_ERR As Integer = 0 '正常終了
Public Const ERR_DIVIDE_0 As Integer = -1 '0で割ろうとしたときのエラーコード

'x÷yを計算し、結果を返します
'y=0だった場合、errInfo.typeにERR_DIVIDE_0を設定します。

Function divide(ByVal x As Double, ByVal y As Double, ByRef errInfo As ErrInformation) As Double
    If y = 0 Then
        errInfo.type = ERR_DIVIDE_0
        errInfo.message = "0で割ることは出来ません"
        Exit Function
    End If
   
    divide = x / y
   
End Function

'メイン関数
Sub main()
   
    Dim ret As Double
    Dim errInfo As ErrInformation
   
    ret = divide(1, 0, errInfo)
    If errInfo.type <> ERR_NO_ERR Then
        Debug.Print "エラーが発生しました"
        Debug.Print errInfo.message
        Exit Sub
    End If
    Debug.Print ret
   
End Sub


 ErrInformationというユーザー定義型を定義し、エラーメッセージを設定できるようにしました。
※例ではtypeには数値を入れています。これだと、int型で例外を伝播させていたときと同じように、ミスによりエラーコードの重複が発生してしまうことがありますが、ユーザー定義型(もしくはクラス)を工夫することにより、そのようなことが起こりにくくすることは、もちろん可能です。

■メリット
  1. 例外の伝播方法が直感的でわかりやすい。
  2. int型よりは多くの情報を持たせることが出来る。 
■デメリット
  1. 呼び出し側が例外処理を書き忘れても、コンパイルエラーとならず、バグの原因となることがある。
  2. 呼び出し側のソースにif分岐が多くなる(正常系処理と異常系処理の混在)ことによる可読性の低下が起こる。
  3. もし、設計時にエラー用のout引数を忘れていた場合、後で修正が大変(関数の戻り値や引数の変更(リファクタリング)は厄介であるため) 
このページのトップへ

4.例外オブジェクトをスロー(throw)する。VB6、VBAであればraiseする場合

 C++、Java、VB6、VBAなど、比較的新しい(?)言語でサポートされている例外の伝播方式です(※1)。例外を呼び出し元に伝えるときにthrow(VB6、VBAではerr.raise)を使い、呼び出し元ではcatch(VB6、VBAではエラーハンドラへ分岐)します(よくわからないと思うので例を見てください)。
 これの特徴は例外をthrowすると、キャッチしているコードまでの処理が行われないというすばらしい特徴があります(よくわからないと思うので例を見てください)。

■例(言語:VBA)

module3

Option Explicit

'x÷yを計算し、結果を返します
'y=0だった場合、11番のエラーを投げます。

Function divide(ByVal x As Double, ByVal y As Double) As Double
    If y = 0 Then
        Err.Raise 11
    End If
   
    divide = x / y
   
End Function

'メイン関数
Sub main()
   
    Dim ret As Double
   
    On Error GoTo ErrorHandler
   
    ret = divide(1, 0)
    Debug.Print ret
   
    Exit Sub

ErrorHandler:
    Debug.Print "エラーが発生しました"
    Debug.Print Err.Description

End Sub


 メイン関数の「ret = divide(1,0)」にてエラーが発生したとき、処理がErrorHandlerまで飛びます(重要)。

■メリット
  1. エラー情報にいろいろな情報を詰め込むことが出来る(除くVB6、VBA…)。
  2. Javaの場合、例外処理を書き忘れるとコンパイルエラーとなるので(※2)、エラー処理の実装漏れによるバグを減らすことが出来る。
    また、仕様変更やバグ対応などで後になってスローする例外の種別が増えることになっても、上記2つの方法に比べれば対処しやすい(コンパイルエラーとなり、場所がすぐにわかるため)
  3. 呼び出し側のソースで、正常系処理と異常系処理が分離されることにより、可読性が良くなる
  4. 例外を処理せず、そのまま伝播させるのが楽。
  5. 継承関連により例外の種類を分別することが出来る(除くVB6、VBA…)。
  6. 関数の引数に余計な引数(エラー用のout引数や、戻り値格納用のout引数)が不要。
■デメリット
  1. 詳しくはわからないのですが、実行時間の見積もりしにくくなる(?)らしい(※3)。
  2. プログラミング初心者(※4)はこんなの知らないし思いつかない(多少、敷居が高い)。

-----
※1:C言語ではtry,catch,throwはサポートされていません。それをlongjmp関数、setjmp関数を使って同じようなことをやろうとかは考えない方が良いと思います。
よく、goto文は使わない方が良いといいます。しかし、所詮gotoは関数内の別の場所に飛ぶだけなので注意して使用すれば問題はないと思います。しかし、このlongjmpとsetjmpを使用するとなんと、別の関数にジャンプできちゃいます。でも、try,catch,throwの基礎となるので説明しているサイトが存在します。詳しいことはGoogleで「longjmp」で検索してください。
※2:java.lang.Exceptionを継承した例外クラスは無視できない。RantimeExceptionもしくはErrorクラスを継承した例外クラスは例外処理をしなくてもよい。
※3:C++ではtry,catch,throwによる例外処理が言語レベルでサポートされているのですが、組み込み用のC++(Embedded C++)ではサポートされていません
※4:だるまの今までの経験では、プログラミング初心者でない人もこの方法を知らなかったりすることがあるようです。勘弁して欲しいです。
このページのトップへ

5.まとめ

 まとめです。上記3つの方法のメリットとデメリットをまとめました。


例外の伝播の方法 戻り値(int型)で例外を返す out引数で例外を返す 例外オブジェクトをスローする
伝播の方法のわかりやすさ


(経験者でも知らなかったりする場合がある)

コードの可読性

×
(正常系と異常系のソースが混在。関数の戻り値が本来の戻り値でなくなる)

×
(正常系と異常系のソースが混在。)


(正常系と異常系のソースが分離。エラー処理のために余計な引数が不要)

その他 ×:エラー処理をしなくてもコンパイルエラーにならないので、漏れの発見が遅れる
× :エラーコードの管理が面倒
× :保持できる情報量が少なすぎ
×:エラー処理をしなくてもコンパイルエラーにならないので、漏れの発見が遅れる :Javaの場合、エラー処理をしていなかった場合コンパイルエラーとなるので、漏らすことがない
このページのトップへ

6.参考文献など

 このページを作成するのに参考にしたページです。

番号

リンク先の名称

リンク先の説明

リンクした日

1 例外処理 - Wikipedia  例外処理についての説明があります 2009/5/5 
2 JNI実験ページ JNIでのエラー処理方法が書いてあります 2009/5/5
3 EC++ Japanese Home Page 組み込み用C++のページです 2009/5/5
4 エラー処理とログ出力 エラー処理についていろいろ書かれています 2009/5/5

Prev Up Next  Top
このページのトップへ

このページの利用によって発生した、いかなる損害について、このホームページの作成者は責任を負いません。
このページの間違いや嘘を見つけた方、このページに書いて欲しい情報がある方はメールをお願いします。

Microsoft 、Windows 、Visual Basic および Excel は米国Microsoft Corporationの米国およびその他の国における登録商標または商標です。
ここではExcel® をエクセル、Visual Basic® for Applications をVBAと表記する場合があります。
Mac 、Mac OS 、Mac OS X は米国Apple Computer,Inc.の登録商標または商標です。
Java及びすべてのJava関連の商標及びロゴは、米国及びその他の国における米国Sun Microsystems,Inc.の商標または登録商標です。
その他、社名および商品名、システム名称などは、一般に各社の商標または登録商標です。

このホームページの作成者はこれらの会社とはいっさい関係がありません。