連載:VB 6ユーザーのための
これならマスターできるVB 2005超入門

第4回 トラブルは水際で防げ〜入力時のチェックとエラー処理

羽山 博
2006/11/22
Page1 Page2 Page3 Page4

■統一感のある構造化例外処理とFinallyの落とし穴

 今回、目的としている入力チェックのコードは取りあえずこれでうまく動くようになった。だが、先ほどのコードはどうも美しくない。同じコードが重複して登場している。

 そこで、最後にコードをブラッシュ・アップしておこう。例外が起こったときの処理をCatch以下にまとめてしまえば、美しくなるはず。そこで、桁数チェックの部分で、商品コードが8桁でないときには、わざと例外を発生させることにする。それをCatchさせる(受ける)わけだから……そう、例外をThrowして(投げて)やればよい。大まかなロジックは次のようになるだろう。

Try

  If 桁数が8桁でない Then
    例外をThrowする
  Else
    Integer.Parseを呼び出す(例外があれば自動的にThrowされる)
  End If

Catch

  それぞれの例外処理

Finally

  共通の処理(後始末)

End Try

 Integer.Parseメソッドに、数字以外の文字が与えられた場合には、Exception型はException型でも、System.FormatException型がThrowされる。自分でThrowする例外はSystem.ApplicationException型を使えばよい。そうすれば、例外の種類によって処理を分けることができる。共通の処理はFinally以下に書けばいいので、次のようなコードになるはずだ。

' 商品コード・テキストボックスのValidatingイベント・ハンドラ
Private Sub lengthCheck(ByVal sender As System.Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtCode.Validating
  Dim msg As String
  lblAlert.Text = ""
  If ActiveControl Is txtCode Then
    Exit Sub
  End If
  Try
    If txtCode.TextLength <> 8 Then
      Throw New System.ApplicationException("商品コードは8桁です") '
    Else
       Integer.Parse(txtCode.Text)
    End If
    Exit Try '
  Catch el As System.ApplicationException '
    msg = el.Message
  Catch ef As System.FormatException '
    msg = "数字以外の文字が含まれています"
  Finally '
    e.Cancel = True
    lblAlert.Text = msg
    txtCode.Select(0, txtCode.TextLength)
  End Try
End Sub
共通の処理をFinally以下にまとめた
  New句を使ってSystem.ApplicationExceptionオブジェクトを作る。引数にメッセージを与えておくと、後でこのメッセージが利用できる。それをThrowする。
  チェックに引っ掛からなかったらTryを抜ける(後述するが、ここは思ったとおりの動きをしないので注意)。
  System.AppicationExceptionをCatchしたときは、そのMessageプロパティ(引数で指定した「商品コードは8桁です」という文字列)を変数msgに代入する。
  System.FormatExceptionをCatchしたときには、「数字以外の文字が含まれています」という文字列を変数msgに代入する。
  これまで見てきた処理。フォーカスの移動をキャンセルし、メッセージを表示した後、入力されたテキストを選択する。

 ところが、このコードはうまく動かない。実はFinally以下のコードは、Try〜End Tryブロックを抜けるときには、最後に必ず実行される。 で「Exit Try」を実行すると、そのままブロックの外に出るような気がするが、この場合もやはりFinally以下は実行される。 を「Exit Sub」に書き換えても同じ。従って、エラーでない場合にも、Finally以下が実行され、フォーカスの移動ができなくなってしまう。

 普通、Finally部分は、使ったオブジェクトの解放などの後始末をするのに使う。Select CaseやFor〜Nextなどの感覚でとらえると違和感があるかもしれないが、途中で抜け出す場合にも必ずやっておかなくてはいけない後始末を書くわけだ。これも理にかなった仕様といえるだろう。

 そんなわけで、共通の処理については、Subプロシージャにまとめるのがいい。今回は後始末は不要なので、Finally部分は書かなくてもいい。これは以下のリストを見るだけで十分理解できるだろう。

' 商品コード・テキストボックスのValidatingイベント・ハンドラ
Private Sub lengthCheck(ByVal sender As System.Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles txtCode.Validating
  Dim msg As String
  lblAlert.Text = ""
  If ActiveControl Is txtCode Then
    Exit Sub
  End If
  Try
    If txtCode.TextLength <> 8 Then
      Throw New System.ApplicationException("商品コードは8桁です")
    Else
      Integer.Parse(txtCode.Text)
    End If
  Catch el As System.ApplicationException
    msg = el.Message
    ShowErr(e, msg)
  Catch ef As System.FormatException
    msg = "数字以外の文字が含まれています"
    ShowErr(e, msg)
  End Try
End Sub

' エラーを表示するための共通の処理
Private Sub ShowErr(ByVal e As System.ComponentModel.CancelEventArgs, ByVal msg As String)
  e.Cancel = True
  lblAlert.Text = msg
  txtCode.Select(0, txtCode.TextLength)
End Sub
共通の処理をShowErrというプロシージャにまとめた
ShowErrメソッドの引数であるeとmsgは、呼び出し側で使っていたものをそのまま受け取っただけ。

結び − ちゃぶ台をひっくり返すようなお話

 プログラミングの解説本や解説記事では、ステートメントの働きや処理の流れをできるだけ分かりやすく説明するため、流れを妨げる例外処理はあえて省かれることが多い。だが、現実の開発場面では例外処理にかける手間の方がはるかに大きいのではないだろうか。誌面に掲載されているサンプル・プログラムを流用するのはいいが、そこに書かれていない例外処理もきちんと書くようにしなければならない。

 サンプル・プログラム程度であればOn Errorステートメントだけでも何とかなるが、実際には至る所で例外処理が必要になるので、その都度Try〜End Tryというブロックを作っておけば、コードがとても分かりやすくなる。もっとも、どのような例外が想定できるかはプログラミングの場面で初めて考えることではなく、仕様書を作る段階で漏らさずチェックしておくべきことなのだが。

 今回は入力チェックや例外処理について見てきたが、これがすべてではない。例えば、フォームをすべて埋めてから[OK]をクリックした時点でチェックをすればいい場合もある。どういう例外処理が最適であるかは、フォームの使い方によっても変わってくるし、対象となるデータによっても変わってくる。

 で、ここからちゃぶ台をひっくり返す話になるのだが、今回のプログラムも、実際にデータベースを検索するのであれば、検索が成功するかどうかを調べるだけでよく、文字列の長さやデータの種類を調べる必要などまったくないのである。本稿を読み進めている途中でそのことに気付いた人がいたとすれば、なかなかデキる人だと胸を張ってもいいと思う。もちろん、入力したデータが、データベースに記録されたり、後の計算に使われたりする場合には今回のようなチェックが必須なのはいうまでもない。例えば、払出数テキストボックスでの上限チェックなどは必須だろう。

 また、今回は例外を多用したが、例外の発生にかかるオーバーヘッドは小さくないので、単純なIfステートメントでできるチェックよりも時間がかかる。今回のような対話型アプリケーションの場合はさほど問題になることはないだろうが、できる限り高速化したい場合には注意が必要だ*3

*3 Integer型(=System名前空間のInt32構造体)にはParseメソッドのほかに、例外を発生させずに文字列を数値に変換するTryParseメソッドも用意されている。これについては「.NET TIPS:文字列字列を数値に変換するには?(TryParse編)」を参照。

 今回のプログラムは、機能を理解するための「サンプルのためのサンプル」に近いものだが、そういう気付きも期待してあえてこの形にした。初めから正解だけを示しても、どういう間違いや落とし穴があるのか気付かないからだ。

 さて、せっかくデータベースの検索フォームを作ったのに、入力チェックだけで終わってしまうのはあまりにも寂しい。が、さすがに読者の皆さんもお疲れだと思うので(書いている私もお疲れなので)、今回はここまでとして、次回、データベースの検索や更新を取り扱ってみたいと思う。End of Article


 INDEX
  連載:VB 6ユーザーのためのこれならマスターできるVB 2005超入門
  第4回 トラブルは水際で防げ〜入力時のチェックとエラー処理
    1.サンプル・プログラム4 − データベース検索のためのダイアログ
    2.Validatingイベントを使って入力チェック
    3.構造化例外処理を使おう
  4.統一感のある構造化例外処理とFinallyの落とし穴
 
インデックス・ページヘ  「これならマスターできるVB 2005超入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH