初心者のためのExcelマクロ超入門(絶対できるVBA開発)

マクロがまったくわからない人のためにエクセルマクロやVBAについてできるだけわかりやすく書いています。Twitter:@shuhhohhey

初心者のためのExcel(エクセル)マクロVBA入門-成績表マクロの作成:エラー処理と変数の改善

さて、今回からさっそく成績表マクロを作成しましょう!。成績表マクロの仕様をもう一度ここでおさらいです。

<成績表マクロの仕様>

  • 成績表シートに成績表がある。
  • 出力する出席番号を入力欄がある
  • 出力ボタンを押すと入力した出席番号の人の成績表ファイルが作成される。
  • 印刷チェックボックスがある
  • 印刷チェックがついてると成績表ファイルが作成され、印刷もされる。

作るマクロの「仕様」を決めよう!

さっそく作ってもいいのですが、まずはできる改善をしていきましょう。前回で補足できてない例外の処理を改善します。実はこれは変数の型を見直すだけで至極簡単にできてしまいます。ここでまずこのマクロの仕様としてのお約束を上げましょう。マクロの仕様とは、ユーザーが使いたい機能を満たす仕様ではなく、マクロを作るために必要な仕様(お約束)のことです。

  • 考えられる例外は例外処理ではなくて事前にチェックしてメッセージを表示して終了すること。
  • それ以外のエラーはシステムエラーとして予期しないエラーとして処理すること

以上をとりあえず約束事として決めましょう。それ以外にもコーディング規約などもありますが、とりあえず割愛します。

変数の型を改善してエラーをなくす。

では早速考えられる例外は・・・

  • 入力した出席番号が数字じゃない場合
  • 出席番号が成績表にない場合
  • 成績表の点数が文字列だった場合

この3つです。実はこのうちの2つは変数の型を変えればそれで改善できてしまいます。いきなり答えを書きますが、それは現在のLongで宣言した変数をString(文字列型)かVariant(汎用型)に変更すればいいのです。現在、数字じゃない時に起きている例外は2つ。出席番号と成績表の点数です。ですので、それを文字列か汎用型にすれば、どんなものでも変数に格納できるので、例外は発生しません。では今回は文字列型に変更して改善しましょう。

前回のマクロの前半部分です。

Option Explicit

Sub sample1()
'出席番号のエラーをキャッチする
On Error GoTo shussekiErr
    
    ' 必要な変数を作成する
    Dim shussekiNumber As String      ' 出席番号を格納する変数
    Dim name As String                ' 氏名
    Dim sansu As String               ' 算数点数
    Dim kokugo As String              ' 国語点数
    Dim rika As String                ' 理科点数
    Dim syakai As String              ' 社会点数
    Dim eigo As String                ' 英語点数
    

これで数字ではない時に起こる例外は回避できました。しかし、実際の文字列が入ってきてもらっても困りますよね?そこで、入力された文字が数字かどうかを後で判定すればいいのです。ついでに、出席番号にあるかどうかも分かるか調べてましょう。ちょっと整理すると・・・

入力された出席番号に対して・・・

  • 数字かどうかをチェックする
  • 出席番号にあるかどうかをチェックする

2つともチェックする。という処理です。こういう時は処理を分けてしまうといいですね。マクロ(プログラム)はできるだけ見やすく、簡潔にです!

入力チェックメソッドを作ろう!


ではこの2つに対しての入力チェックメソッドを作成します。入力チェックでエラーだったら対応するエラーのメッセージを戻り値として返し、エラーでなければ空文字「""」を返すメソッドにします。こんな感じで書いてみましょう。いつも通りはじめと終わりからです。

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' 入力チェックのメソッド
' InputCheck
' 引数:shussekiNumber As String
' 戻り値:String
' エラーの場合はエラーメッセージを返す。
' エラーがない場合は空文字を返す。
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function InputCheck(ByVal shussekiNumber As String) As String


End Function

このようにコメントはしっかりと書いておきましょう。後で見てもこれならこのメソッドの役割がわかりやすいですね。

Privateは他からアクセスできない。


Privateと書いていると、そのオブジェクトやモジュール内の外から呼び出すことができないものになります。入力チェックは処理の間に一時的に呼ばれるものなので、今回はPrivateとしています。

ハードコーディングは極力しない


さて、エラーのメッセージを返すわけですが、ここでも直接文章を書かないようにしましょう。理由はマクロをその後で変更する時に極力、「楽」をしたいからです。特にこういった一定の値(定数)はどこか別の標準モジュールに記述してまとめておきましょう。どのメッセージかどうかわからなくなるのでそこはコメントでカバーします。

標準モジュールの中に[mdlDefine]という名前で標準モジュールを作成してその中に書いておきます。

Option Explicit

Public Const APP_TITLE = "成績表マクロ"

Public Const ERROR_MSG1 = "出席番号が存在しません。処理を終了します。"
Public Const ERROR_MSG2 = "出席番号が見つかりません。処理を終了します。"
Public Const ERROR_MSG3 = "成績表の点数が不正です。処理を終了します。"

Public Const ERROR_WINDOW_TITLE = "成績表マクロ:エラー"

こんな感じです。定数はすべて大文字で記述するといいでしょう。またPublicにしておかないと、どこでも使用できなくなってしまうのでこちらもつけましょう。これで準備完了です。さっそく入力チェックを書いて行きましょう。チェックするのは

・数字かどうか?
・出席番号の中にあるか?

ですね。数字かどうかはIsNumeric関数を使います。出席番号の中にあるかどうかは、数字の大小で見ていけばOKですね。当然、判定を行うので条件分岐構文であるIf~Then文を使います。先ほど書いたInputCheckメソッドに記述します。

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'
' 入力チェックのメソッド
' InputCheck
' 引数:shussekiNumber As String
' 戻り値:String
' エラーの場合はエラーメッセージを返す。
' エラーがない場合は空文字を返す。
'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function InputCheck(ByVal shussekiNumber As String) As String

    '数字かどうかをチェックします。
    If Not IsNumeric(shussekiNumber) Then
        
        ' 数字ではない場合「出席番号が存在しません。処理を終了します。」
        InputCheck = mdlDefine.ERROR_MSG1
        Exit Function
        
    End If
    
    '出席番号の中にあるかチェックします。
    If CLng(shussekiNumber) < 0 And CLng(shussekiNumber) > 10 Then
        
        ' 出席番号が0以下または11以上の場合「出席番号が見つかりません。処理を終了します。」
        InputCheck = mdlDefine.ERROR_MSG2
        Exit Function
        
    End If
    
    ' それ以外はエラーなしとして空文字「""」を返す
    InputCheck = ""

End Function

これがInpputCheckメソッドになります。値を返すメソッドはSubではなくFunctionとなります。また値を返す場合は返す値のデータの型を指定します。「As String」という部分ですね。()カッコの中にはInpuCheckを呼び出す際に入力する引数を定義しています。当然出席番号をチェックするので、「shussekiNumber」で、データの型は呼び出す側の変数をStringに変更したのでそのままStringとしています。

データをキャスト(変換)する


さて、1つ見慣れないのがありますよね?If文にある「CLng」というものです。これはデータの型を変換する関数です。CLngはLong型に変換してくれます。この変換をプログラミング用語で「キャスト」と呼びます。このようにキャストできる関数はVBAには数多く用意されていて、Integer型に変換する「CInt」文字列に変換する「CStr」日付型に変換する「CDate」なんてのもありますのでヘルプで見てください。キャストは一時的に変換してくれるだけで入力された変数の型が変わるわけではないので注意してください。

値を返す場合は「関数名 = 値」


さて、一般的に関数などで値を返す場合プログラミングですと「return文」が有名ですが、VBAではちょっと変わった書き方をします。それが「関数名 = 値」という形です。またこの命令文は返す値を「設定する」だけで関数自体を「終了する」わけではないので、きちんと「Exit Function」としてメソッドを終える必要があります。「関数」や「メソッド」とか出てきて混乱するかもしれませんが、どちらも同じです「関数」=「メソッド」という認識でいいですが、自分でこのように記述するような場合は一般的には「メソッド」と呼ぶことが多いです。VBAですと全部「プロシージャ」でひとくくりにしてしまいますが、それではわかりにくいので、ここではそのように呼びます。
さて、これで入力チェックのメソッドが完成したので、本体のSub Sample1()プロシージャから呼び出してみましょう。ついでに名前もSample1からもっとわかりやすく大元のプロシージャなのでMainという名前に書き換えます。さらに、呼び出した後はエラーメッセージがあるかどうか判定して、メッセージがあればメッセージを表示して終了し、空文字なら入力OKとして先に処理を進めるようにします。また全部のコードを載せておきます。

Option Explicit

Sub Main()
'出席番号のエラーをキャッチする
On Error GoTo shussekiErr
    
    ' 必要な変数を作成する
    Dim shussekiNumber As String    ' 出席番号を格納する変数
    Dim name As String              ' 氏名
    Dim sansu As Long               ' 算数点数
    Dim kokugo As Long              ' 国語点数
    Dim rika As Long                ' 理科点数
    Dim syakai As Long              ' 社会点数
    Dim eigo As Long                ' 英語点数
    
    Dim strErrMsg As String         ' 入力チェックの結果を格納する変数
    
    ' 出席番号を入力してもらう(ここで例外発生する!)
    shussekiNumber = InputBox("出席番号を入力してください", "出席番号入力", 1)
    
    ' InputCheckメソッドを呼び出してエラーメッセージを格納する
    strErrMsg = InputCheck(shussekiNumber)
    
    If strErrMsg <> "" Then
    
        'エラーになった時の処理
        MsgBox strErrMsg, vbOKOnly + vbExclamation, mdlDefine.ERROR_WINDOW_TITLE
        'マクロを終了する
        Exit Sub
    
    End If
    
'VLookupのエラーをキャッチする
On Error GoTo vlookupErr

    ' 番号を使ってVlookUpで名前と点数を取得する(ここでも例外発生する!)
    name = Application.WorksheetFunction.VLookup(shussekiNumber, Sheet1.Range("A3:G13"), 2, False)
    sansu = Application.WorksheetFunction.VLookup(shussekiNumber, Sheet1.Range("A3:G13"), 3, False)
    kokugo = Application.WorksheetFunction.VLookup(shussekiNumber, Sheet1.Range("A3:G13"), 4, False)
    rika = Application.WorksheetFunction.VLookup(shussekiNumber, Sheet1.Range("A3:G13"), 5, False)
    syakai = Application.WorksheetFunction.VLookup(shussekiNumber, Sheet1.Range("A3:G13"), 6, False)
    eigo = Application.WorksheetFunction.VLookup(shussekiNumber, Sheet1.Range("A3:G13"), 7, False)
    
    ' 所定の場所へ出力する
    Sheet1.Range("A19").Value = shussekiNumber
    Sheet1.Range("B19").Value = name
    Sheet1.Range("C19").Value = sansu
    Sheet1.Range("D19").Value = kokugo
    Sheet1.Range("E19").Value = rika
    Sheet1.Range("F19").Value = syakai
    Sheet1.Range("G19").Value = eigo
    
    'マクロ終了
    Exit Sub
    
shussekiErr:
    'エラーになった時の処理
    MsgBox "出席番号が存在しません。処理を終了します。", vbOKOnly + vbExclamation, "エラー"
    'マクロを終了する
    Exit Sub
    
vlookupErr:
    MsgBox "出席番号が見つかりません。処理を終了します。", vbOKOnly + vbExclamation, "エラー"
    'マクロを終了する
    Exit Sub

End Sub

前回と変わったところは

    Dim strErrMsg As String         ' 入力チェックの結果を格納する変数
    
    ' 出席番号を入力してもらう(ここで例外発生する!)
    shussekiNumber = InputBox("出席番号を入力してください", "出席番号入力", 1)
    
    ' InputCheckメソッドを呼び出してエラーメッセージを格納する
    strErrMsg = InputCheck(shussekiNumber)
    
    If strErrMsg <> "" Then
    
        'エラーになった時の処理
        MsgBox strErrMsg, vbOKOnly + vbExclamation, mdlDefine.ERROR_WINDOW_TITLE
        'マクロを終了する
        Exit Sub
    
    End If

このあたりです。エラーメッセージを格納する変数を用意しています。そこにInputCheckメソッドを呼び出してその結果を入力しているわけです。そしてそれが空文字ではない場合に、格納されたエラーメッセージとmdlDefineモジュールで定義しておいた、メッセージボックスのタイトルを設定して、メッセージボックスを表示し、マクロを終了しています。
変数 strErrMsgという名前は先にデータ型を書いてその後に変数の意味を表す単語で作成しています。こうすると変数を見ただけでこれは何型の変数なのか?がわかるので便利です。また、条件でしか変数を使わないのなら・・・

    
    ' 出席番号を入力してもらう(ここで例外発生する!)
    shussekiNumber = InputBox("出席番号を入力してください", "出席番号入力", 1)
    
    ' 入力チェックを呼び出した結果で判定する
    If InputCheck(shussekiNumber) <> "" Then
    
    ・・・
    ・・・

このようにして変数を使わずに書くこともできます。こちらの方が若干スマートですね。変数を作る場合は他で使ったり、何度も関数を呼ぶ必要がある場合は、変数に一度格納してそれを使う方がプログラムの速度も上がるので良いと思います。

別のモジュールにある関数や定数はモジュール名から書く!


実は定数を書くときに、mdlDefineは省略できます。しかし、私はこれは「省略してはいけない」と考えています。なぜなら、プログラミング基礎の時に私はこう言いました。

マクロ(プログラム)は人とコンピューターの両方が見てわかるように書くこと

そうです。ここでmdlDefineを省略してしまうと、ERROR_WINDOW_TITLEが一体何を表しているのか?が分からないのです。やはり「どこそこの」という意味でモジュール名を記載することはとても大切なことなので、省略してもコンピュータはわかりますが、人が理解しにくいということから「省略してはいけない」と考えています。こうしておくと、モジュール名がなければ同一モジュールにあるものとわかります。あれば、そのモジュールを確認できるので人も理解できるわけです。

これで例外処理はいらない?


これで、例外処理で書いていた処理はなくすことができました。ですので、消しましょう。。。と言いたいところですが、先に書いた通り「予期しないエラーは例外処理する」というのがありますので、残しておきます。次回はこのあたりを整備しましょう。

今日はここまで!


かしこ