Visual Basic 初級講座
VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

 

Visual Basic 中学校 > 初級講座 >

第12回 エラーと例外処理

今回はエラーが発生したときの処理を記述する方法を説明します。VBではエラーが発生したときに備えてあらかじめエラー処理をプログラムしておくことができるような構文が用意されています。

この回の要約

・「例外」という言葉は「エラー」という言葉とほとんど同じ使われ方をしている。

・Try 〜 Catch 〜 End Tryを使うと例外処理を記述できる。

・On Errorを使っても例外処理を記述できる。

・On Error Resume Nextを使うと、すべてのエラーを無視することができる。

 

1.「例外処理」という言葉

 

「例外処理」という言葉はプログラマ以外の人にはあまりなじみがない言葉だと思います。また、ちょっと意味があいまいな言葉になりつつある気もしますので初めに今回のテーマである「例外処理」という言葉についてはっきりさせておきます。

例外処理とは、プログラムで想定されていなかったエラーが発生した場合に実行される処理のことです。

プログラムには必ずバグがあります。バグのないプログラムは現在の人類の科学水準では作成できません。バグのないプログラムの作り方を発見した人はノーベル賞級といわれています。

バグがあるということは、プログラムを作った人(つまりプログラマ)が想定していなかったエラーが発生する可能性があるということです。したがって、そのようなエラーが発生したときの処理が必要となるわけです。

以上が基本的な言葉の意味なのですが、ちょっとずつ違う意味でも言葉が使われているので注意してください。

たとえば、想定していたエラーに対する処理も「例外処理」と読んだりします。この場合「例外処理」=「エラー処理」というニュアンスになります。このことから単純に「例外」と言った場合「エラー」のことを指していたりもします。

ほとんどの場合「例外」=「エラー」と考えれば話のつじつまは合うようです。当サイト Visual Basic 中学校でも「例外」と「エラー」という言葉はとくに区別しません。

 

2.エラー

 

さて、割り算をするプログラムを作ります。TextBox1TextBox2に入力された数字でわり算をしましょう。

プログラムは次のようになります。もちろんこのプログラムはバグ満載です。

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

End Sub

■リスト1:わり算プログラム。バグあります。

CDec(読み方:CDec = シーデク)は値を数値型に変換する関数です。なくてもとりあえず動作しますが、ちょっとわけありで使用します(※1)

※1:この段階では理解しなくても良いと思いますが、CDecがない場合浮動小数点型への自動型変換が発生します。その結果計算結果に誤差が出る場合があります。また浮動小数点型の場合、0で割ったり、0/0を実行したときの値が非数値になるなど初学者にはわかりにくい仕様になります。さらに、この後で記述するDivideByZeroExceptionも発生しなくなるので説明の都合上も不便です。

試しにTextBox1に「10」、TextBox2に「5」と入力してボタンをクリックすると、ちゃんと「2」と表示されます。一応の機能はこれで良いわけです。

このプログラムにどんなバグがあるかわかりますか?

たとえば、TextBox1TextBox2に数値ではなく文字を入力した状態でボタンをクリックしてみてください。次のようなエラーが発生します。

■画像1:型変換エラー

'System.InvalidCastException'のハンドルされていない例外が microsoft.visualbasic.dllで発生しました。

追加情報:文字列 "TextBox1"から型'Decimal'へのキャストが有効ではありません。

また、数値を入力した場合でもTextBox2に「0」を入力した場合はエラーになります。この場合は次のエラーが発生します。

■画像2:0で除算した場合のエラー。小学校で習うが、通常は数字を0で割ることはできない。

'System.DivideByZeroException'のハンドルされていない例外が WindowsApplication1.exe で発生しました。

追加情報:0 で除算しようとしました。

メッセージ中に出てくる「キャスト」とは型の変換のことです。この場合、わり算をするために数値に変換しなければならないのでキャストが発生します。これらのメッセージのその他の意味は後で説明します。今は2種類のエラーが発生することを覚えておいて、エラーが発生した場合の例外処理を実際に記述して見ます。

VBでは例外処理を記述するにはTry構文(読み方:Try = トライ)か、On Errorステートメント(読み方:On Error = オン エラー)を使います。次から一つずつ説明します。

 

3.Try構文

 

まずTry構文で例外処理を書いてみます。Try構文を使うと堅牢で構造化された例外処理を記述することができます。便利さや書きやすさを重視する場合はTryではなく、後で説明するOn Errorを使った方が良い場合もあります。

Try構文で例外処理を書くと次のようになります。

Try

    MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

Catch ex As Exception

    MsgBox("数値を入力してください。", MsgBoxStyle.Exclamation)

End Try

■リスト2:単純な例外処理

自分では「Try」だけ書いて[Enter]キーを押すと後の部分は自動的に挿入されます。

この状態で先ほどと同じ操作でエラーを発生させてみると、エラーにはならず「数値を入力してください。」と表示されます。

このようにTry構文はTry以下のコードでエラーがあった場合には、直ちにCatch(読み方:Catch = キャッチ)以下のコードを実行します。つまり、Catch節が例外処理ということになるのです。もちろん、エラーがなかった場合はCatch節のコードは実行されません。

この例は説明のために単純化してあるのでTry節もCatch節も1行しかありませんが、実際には複数行になる場合がほとんどです。プロシージャ内部をまるごと一つのTry構文でくくってしまうことも珍しくありません。

ところで、エラーは2種類あるのに例外処理は1つしかありません。これで十分ならよいのですが、エラーごとに処理をわけることもできます。

今回の例だと次のように分けることができます。

Try

    MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

Catch ex As InvalidCastException

    '数値ではない場合
   
MsgBox("数値を入力してください。", MsgBoxStyle.Exclamation)

Catch ex As DivideByZeroException

    '0で割ろうとした場合
   
MsgBox("0で割ることはできません。", MsgBoxStyle.Exclamation)

Catch ex As Exception

    'その他のエラーが発生した場合
   
MsgBox("その他のエラーが発生しました。", MsgBoxStyle.Critical)

End Try

■リスト3:例外の種類に応じて処理を分岐する

コメントをつけたので分かるかとも思いますが、Catchを複数記述することによりエラーに応じた処理をすることが可能になります。

どのCatch節でどのエラーを処理するかはCatch節のAsの後ろで決まります。

エラーメッセージをもう一度見てもらえば分かるのですが、エラーが発生するとそれに応じた「…Exception」という文言が割り当てられます。数値ではない場合は「InvalidCastException」(読み方:InvalidCastException = インバリドキャストエクセプション)、0で割ろうとした場合は「DivideByZeroException」(読み方:DivideByZeroException = ディバイドバイゼロエクセプション)ですね。実はこれはクラスの名前なのです。Catch節のAsでこのクラス名を指定すると、そのCatch節はそのエラーが起こったときの処理をする部分となります。

Asの部分でException(読み方:Exception = エクセプション)と記した場合はすべての例外を処理することになります。

Catch節の中でエラー、すなわち例外を表すクラスにアクセスするには ex. と打ちます。そうするとその例外クラスが持っているメンバにアクセスできます。(exの部分を手動で変更している場合は、その変更した名前でアクセスします。)

たとえば、次のようにすると例外処理として例外クラスのメッセージを表示することができます。

Try

    MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

Catch ex As Exception

    MsgBox(ex.Message)

End Try

■リスト4:

博士のワンポイントレッスン
V太:えっと、ようするに、エラーを区別するにはCatch節で「なんとかException」という名前で区別するということですよね。なんかわかったようなわからないような気分なんですけど…。
博士: 名前というかクラスじゃな。確かに例外ごとにクラスが決まっていてそのクラスにより例外を区別するというのははじめのうちはわかりにくいかもしれん。
B子:例外っていろんな種類があるんでしょう?全部に「なんとかException」というクラスが用意されているなんてとても覚えられそうにないわ…。
まぁよく出てくる例外は決まっているからなれてくればそう不自由はせんよ。VBもVB6のころは番号でエラーを区別していたのじゃがな、7番のエラーはメモリ不足、13番のエラーは型変換エラーという具合にのぉ。懐かしいのぉ。
そっちの方が僕には分かりやすいです…。
VB.NET2002以降になっても少し後で解説するErrを使えば番号でエラーを区別することはできるぞ。でも、ここはがんばって例外クラスに慣れておいた方が将来のためじゃ。

参考に代表的な例外クラスを表にしておくぞ。

例外クラス 読み方 簡単な説明
InvalidCastException インバリドキャストエクセプション 型変換エラー
NullReferenceException ヌルリファレンスエクセプション インスタンシングされていないクラスの非共有メンバへのアクセス
ArgumentException オーギュメントエクセプション メソッドやプロパティの引数が間違っている
FileNotFoundException ファイルノットファウンドエクセプション ファイルが見つからない。
IndexOutOfRangeException インデックスアウトオブレンジエクセプション 配列やコレクションの範囲外のItemへのアクセス

 

4.Try構文の補足

 

さて、Try構文については以上を理解していればほとんど問題ありませんが、もう少し複雑な処理もできますので紹介しておきます。

まず、Try構文中でFinally節(読み方:Finally = ファイナリー)を使うことで例外が発生してもしなくても必ず実行する処理を記述することができます。たとえば、次の例ではエラーの有無にかかわらず、Label1に答えかメッセージを表示します。

Dim Answer As String

Try

    Answer = CDec(TextBox1.Text) / CDec(TextBox2.Text)

Catch ex As Exception

    Answer = "!Error"

Finally

    Label1.Text = Answer

End Try

■リスト5:後処理の記述

Throwステートメント(読み方:Throw = スロー)を使うと例外が発生したことを呼び出し元に伝えることもできます。この場合は呼び出し元の例外処理が実行されます。この機能はメソッドを自作するなどしている場合にそのメソッド内で例外が発生した時に、そのメソッド内ではなく、メソッドを呼び出した部分で例外処理をしたいときに使います。

特にクラスを自作する場合は要点を抑えて上手に使ってください。

Try

    MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

Catch ex As Exception

    Throw ex

End Try

■リスト6:例外を呼び出し元に伝達する。言い換えるとわざと例外を発生させていることになる。

他にもWhen句(読み方:When = ホエン)を使って、例外の発生を制御したり、Catch As Exception内で例外クラスによって処理を分けたりできるわけですが、これらはまず必要ありませんし、プログラムがわかりにくくなるのでできるだけ使ってほしくありません。ですからここではこれらの説明はしません。

 

5.On Error

 

On Errorにもいろいろな記述方法がありますが、一般にOn Errorによる例外処理は簡単で書きやすいです。

たとえば、On Error Resume Next (読み方:Resume = リジューム) と記述するだけで、それ以降のすべてのエラーは無視されます。

On Error Resume Next

MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

■リスト7:On Error Resume Nextですべてのエラーを無視する。

このプログラムを実行すると、エラーのない場合のみ答えが表示されます。

もちろん、On Error Resume Nextの下には何行でもコードを書くことができます。そして、エラーの発生した行だけが無視され、その他の行はちゃんと実行されます。

これは私から見ると常識を無視した便利さです。TryだのCatchだのぐだぐだ書くのに比べなんと手軽なことでしょう!

ただし、当然無視してはいけないエラーもありますし、エラーが発生した行を無視した結果、全体として実行結果が異常になる場合も考えられますからなんでもかんでもOn Error Resume Nextというわけには行きません。

一応、On Error Resume Nextを使っても次のようにすることで例外処理を記述することはできます。

On Error Resume Next

MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

If Err.Number <> 0 Then

    MsgBox(Err.Number & " " & Err.Description)

End If

■リスト8:On Error Resume Nextによる例外処理

このコードの中に登場するErrErrObjectクラス(読み方:ErrObject = エラーオブジェクト)を表していて、VBで発生した例外を自動的に監視してくれています。VBプログラマはいつでもErrを使って例外が発生しているのかいないのか、発生しているとしたらどんな例外が発生しているのか確認することができます。Err自体はTryブロックの中でも使用することができます。

ErrObjectの持つメソッドやプロパティをを使うと、Try構文を使っているのと同じように例外の種類に応じて処理を分岐させたりできるのですが、それをやるんだったら素直にTry構文を使うべきと思います。

 

さらに、On Error Goto(読み方:Goto = ゴートゥー)を使うと、エラーが発生したときに指定した行ラベルに処理を飛ばすことができます。次のようになります。

    On Error GoTo ErrorHandle

    MsgBox(CDec(TextBox1.Text) / CDec(TextBox2.Text))

    Exit Sub

ErrorHandle:

    MsgBox(Err.Number & " " & Err.Description)

■リスト9:On Error Gotoによる例外処理

行ラベルというのは、この例で言う ErrorHandle: のことで、後ろに「:」のある目印のことです。行ラベル自体には何の機能もありません。単にGotoでどこに飛ぶが指定する目印としての役割があるだけです。

なお、この例ではExit Subがないと、エラーがない場合にもエラーメッセージが表示されてしまうので注意してください。

博士のワンポイントレッスン
V太:うーんと、例外処理にはTryOn Errorの2種類があるんだね。そして、どっちを使っても同じことができる…と。
B子:あら、ちゃんと理解できたみたいね。
でも、TryOn Errorとどっちを使えばいいのかなぁ…?
Tryに決まっているじゃない。On Error Resume Nextなんて無茶もいいところだわ。On Error Gotoはプログラムがわかりにくくなりそうだし。
博士:うーむ。確かにTryを使う人の方が多数派のようじゃ。それにTryはVBだけではなくC++, C#. Java, D, Delphiなどでもほとんど同じように使うことができる。
ほらね
じゃが、On Errorにも利点があるのも確か。やはり両方を理解したうえでうまく使い分けるのが一番じゃろう。ただし、何の考えもなく両方を適当に使っていると、プログラムに統一感がなくなってごちゃごちゃしてしまうので注意したいところじゃな。

 

 

6.まとめと哲学

 

以上で説明したことで例外処理の95%くらいは大丈夫なはずです。

VBの基本構文の説明はどうしても淡々と構文の使い方を説明していくスタイルになってしまいます。まぁ私も「容赦なく専門用語を使う公式ヘルプよりもこっちの方がわかりやすい」と言ってもらえればとりあえず嬉しいのですが、どうしてもプログラムする上で細かい構文以上に大切な哲学の部分をもらしてしまいます。

そこで今回は少しだけ、エラー処理の哲学を語ります。

まず、例外処理の最大の目的は「ユーザーのデータを保護する」ことです。作業中のデータが保護できれば一番良いのですが、わるくてもファイルの内容やデータベースなどのデータは絶対に壊してはいけません。もし、想定外の自体が発生し、ユーザーのデータが保護できるか分からない状況になった場合は、素直に処理を中断して、異常事態が発生したことをユーザーに告げるメッセージを表示しましょう。

エラーメッセージが表示されるというのは結構かっこ悪いです。しかし、ユーザーのデータを危険にさらすよりは何倍もましです。繰り返しますが「異常事態が発生した場合は、処理を中断して、ユーザーにメッセージを表示」することが重要です。できれば、復旧方法も表示するとベターです。たとえば、「再起動して処理をやり直してください。」とか。

想定されているエラーや、ユーザーのデータを壊す恐れがない場合は、かっこ悪いし邪魔なので、いちいちエラーメッセージを表示するのはやめましょう。ユーザーの気が付かないところでログなどにメッセージを書き込んでおいて必要があればそれを見ることもできるという風にしておいた方が良いでしょう。