アナログCPU:5108843109

ゲームと音楽とプログラミング(酒と女とロックンロールのノリで)

ExcelVBA入門 #9:パブリック?プライベート?プロシージャの使い方!

はじめに

今回は、最初から何の断りもなく使っていた
「Public Sub test()」~「End Sub」について。

この二つで囲まれた部分を「プロシージャ」といいます。

今までもしれっと例に出したりしてはいましたが、プロシージャはモジュール内に複数置くことができます。

複数に分けることで、見やすくバグの少ないコードを書きやすくなります。
どういうことかというと…ちょっと長くなりますが、以下の2つを眺めてみてください。

Public Sub Aさんの平日()

    '月曜日
    起きる
    朝ごはんを食べる
    会社に行く
    仕事
    昼ごはんを食べる
    仕事
    会社を出る
    英会話教室に行く
    晩ごはんを食べる
    帰宅
    寝る
    
    '火曜日
    起きる
    朝ごはんを食べる
    会社に行く
    仕事
    昼ごはんを食べる
    仕事
    会社を出る
    帰宅
    晩ごはんを食べる
    趣味の時間
    寝る

    '水曜日
    起きる

    …(以下金曜まで続く。長いので省略!)

End Sub
Public Sub Aさんの平日()

    For 月曜 To 金曜
        起きる
        朝ごはんを食べる
        【会社】
        【アフター5】
        寝る
    Next

End Sub

Private Sub 会社()

    会社に行く
    仕事
    昼ごはんを食べる
    仕事
    
    If 作業が終わっていない Then
        残業
    End If
    
    会社を出る

End Sub

Private Sub アフター5()

    If 月曜 Then
        英会話教室に行く
        晩ごはんを食べる
        帰宅
    If 金曜 Then
        定例飲み会
        帰宅
    Else
        帰宅
        晩ごはんを食べる
        趣味の時間
    End If

End Sub

さて、どっちが内容を理解しやすいでしょうか。
そして、どっちがコードの修正が楽そうでしょうか。

…という話なのです。

処理のかたまりで分けたり、繰り返し同じような処理が出てくるのをまとめたりすることで、可読性もメンテ性も高いコードになるのです。

どう分けるのが正解というのはありませんし、分ければ分けるほど良いわけでもありません。
あくまで読みやすいかどうかを考えて分けるようにすると良いです。

…ということで。
プロシージャを分ける上で避けては通れないのが、これから紹介する内容。

プロシージャには戻り値の有無によって「Sub」と「Function」に分けられ、
どちらの場合も「Public」「Private」というスコープを設定できるのです。

…とだけ書くと「は?」「何言ってんだコイツ」となると思いますが、ご安心ください。
最終的には覚えることはそう多くありませんので、その使い分け方をゆっくり見ていきましょう。

PublicとPrivateという「スコープ」

今までは「Public Sub ~」という形しか使っていませんでしたが、
実はPublicのほかにPrivateというものもあるのです。

「Publicはモジュールの外から呼び出すことができ、
Privateはモジュール内でのみ呼び出すことができる」
という説明で理解できるプログラミング経験者さんは
以下を適当に読み飛ばしてください。

さて、「???」な人は、まず標準モジュールをひとつ作り、
そこに次のプログラムを書いてみてください。

Public Sub test1()
    MsgBox "ぱぶりっくさぶ"
End Sub

Private Sub test2()
    MsgBox "ぷらいべーとさぶ"
End Sub

test1プロシージャは、第1回で初めて作ったサンプルプログラムとほぼ同じです。
プロシージャ名や、表示する文字列が違うだけですね。
test2プロシージャもtest1とほぼ同じですが、
最初だけ「Private」になっています。

さて、これを実行しようとすると…
test2が出てきませんね。

f:id:honey8823:20150909174450p:plain

「実行できないのにどうやって使うの!?」と思われた方、
これにはちゃんと使い方があるのです。
では、先程のモジュールに、もう一つプロシージャを追加してください。

Public Sub SubCall()
    Call test1
    Call test2
End Sub

何をするプロシージャか、なんとなくわかるでしょうか?
「Call」は、Subプロシージャを呼び出すために使います。
つまりこれを実行すると、test1とtest2が両方とも実行されるはずです。
試してみてください。


さて、さらにもう一つ新しく標準モジュールを追加してください。
そこに、次のプロシージャを追加します。
(「Module1」の部分は、test1とtest2を書いたモジュールの名前に変更してください)

Public Sub SubCall()
    Call Module1.test1
    Call Module1.test2
End Sub

これを実行するとどうなるでしょうか?
test1とtest2が両方とも実行される…わけではなく、
エラーが出てしまうはずです。

最初に書いたとおり、「Publicはモジュールの外から呼び出すことができ、
Privateはモジュール内でのみ呼び出すことができる」ものです。
別のモジュールからtest1を呼ぶことはできますが、
test2は呼び出せないのでこういうことになってしまうのです。

PublicとPrivateの違いを表にまとめるとこうなります。

Public Private
Alt+F8や実行ボタンでの実行 ×
同じモジュール内からの呼び出し
他のモジュール内からの呼び出し ×

こういう「どこから呼び出せるか」という範囲をスコープといいます。
別に「スコープ」という単語は覚えなくてもよいのですが、概念は理解いただけたでしょうか。

これなら全部Publicでいいじゃん!とお思いの方、まあ、その通りです。
ヘタに動かないものを作ってしまうくらいなら、「とりあえずPublic」でもよいでしょう。
しかし、どのプログラミング言語でも、基本的には必要最低限にしておくものです。
理由はいろいろとあって話すと長くなってしまうのですが、
かなり大雑把にまとめると、「範囲が狭いほど管理しやすいから」です。
(狭ければ狭いほど「良い」という意味ではありません。あくまで「必要最低限」というのは重要です)
興味と余裕があれば調べてみるのもいいかもしれません。
参考URLも貼っておきます。

参考:スコープを意識したプログラミング―その1 スコープって何? - gihyo.jp
http://gihyo.jp/dev/serial/01/code/000301

参考:変数の適用範囲 - Office TANAKA
http://officetanaka.net/excel/vba/variable/05.htm
※プロシージャではなく変数のスコープの話ですが、何故むやみにスコープを広げてはいけないのかが分かりやすいです

SubとFunction

さて、次はSubとFunctionについて。
こちらにはわかりやすい違いがあります。

Subは処理を行うだけですが、
Functionは処理の結果を返してくれます。
(ちなみに、返す値を「戻り値」「返値」などといいます)

もっと具体的に言うと、
「2+3を計算する」のはSubにもできますが、
「2+3を計算した結果を返す」のはFunctionにしかできません。
ちなみにそれを実際にコードに起こしてみると以下のような感じです。

Public Sub main()
    Call test3      'test3プロシージャを呼び出す
    MsgBox test4    'test4プロシージャを呼び出して戻り値を表示する
End Sub

Private Sub test3()
    Dim Answer As Long
    Answer = 2 + 3   '変数Answerに計算結果を格納
End Sub

Private Function test4() As Long
    Dim Answer As Long
    Answer = 2 + 3   '変数Answerに計算結果を格納
    test4 = Answer   '変数Answerの中身を戻り値としてセット
End Function

mainプロシージャでは、test3とtest4を呼び出しています。
test3では、整数型変数Answerに2+3の結果を入れていますが、それを呼び出し元のmainに教える方法がありません。
test4では、同様の処理を行ったあとに「test4 = Answer」とすることでAnswerに入った値をmainに返しています。
(コードを似た形にするためにこう書いていますが、「test4 = 2 + 3」でもOKです)

…と、Functionの書き方はなんとなく分かりましたか?
改めて書いておくと、

スコープ Function プロシージャ名() As 戻り値の型
    '処理
    プロシージャ名 = 戻り値
End Function

となります。

test3の最後に「test3 = Answer」を入れてみたり、
呼び出し元の「Call」を「MsgBox」に変えてみたりすると
エラーになるのを確認してみてください。

Public Sub test() ←このカッコについて

さて、最後にもう一つ。
プロシージャ名の後に付けている「()」ですが、実はこれも大事な使い方があります。

このカッコには「引数」を入れます。

…という説明で意味が分かったプログラミング経験者さんは、やっぱり適当に読み飛ばしてください。

引数とは一体何かというと、「プロシージャに渡す値」です。
例えばさっき「2+3を計算するプロシージャ」を作りましたが、では4+5を計算したい場合は?

Public Sub main()
    MsgBox test4
    MsgBox test5
End Sub

Private Function test4() As Long
    Dim Answer As Long
    Answer = 2 + 3
    test4 = Answer
End Function

Private Function test5() As Long
    Dim Answer As Long
    Answer = 4 + 5
    test5 = Answer
End Function

こうでしょうか。
…test4とtest5はほぼ同じプロシージャになってしまいます。
無駄が多いように見えますね。

こういうときに「引数」の出番なのです。

Public Sub main()
    MsgBox test6(2, 3)
    MsgBox test6(4, 5)
End Sub

Private Function test6(ByVal a As Long, ByVal b As Long) As Long
    Dim Answer As Long
    Answer = a + b
    test6 = Answer
End Function

さっきのコードと見比べてみてください。

test6では、プロシージャ名の後のカッコ内で、変数宣言に似た書き方で「a」「b」を定義しています。
これが引数です。
test6を呼び出している側では「2と3」「4と5」をそれぞれ指定していて、これが呼び出された先の「a」や「b」に入り、使われています。

処理に使う値が異なるが処理の方法は同じ…というときなどに、このように別のプロシージャを作り、処理の度に引数を与えて呼び出すことができます。
呼び出された「a」「b」はその「Private Function test6 ~ End Function」の中だけで有効であり、2回目以降の呼び出しや、呼び出し元の処理に影響を与えることはありません。

引数の指定方法をきちんと書くと、↓このような感じです。
今まで書いていたように引数なしでもOKですし、1個でも2個でもそれ以上でもOKです。
複数の場合は、さっきの例のようにカンマ区切りで。

ByVal 引数名 As

「ByVal」は省略してもOKですし、「As 型」を省略すると例によってVariant型になります。(もちろんByValには意味があって、別のものを書くこともありますが、その話はまたいずれ…)

いまいちイメージが湧かない方のために、たとえ話で書いてみます。

ジュースの買える自動販売機を想像してください。
自販機は「商品指定とお金」を与えると「指定した商品とお釣り」が返ってきますね。
商品はいろんな種類がありますし、ものによって値段も変わりますが、一つの機械で処理していますね。

VBAのコードで表してみるとこんな感じです。
(通常、戻り値は1つしか返せないので、ここではお釣りを返すプロシージャになっていますが…)
一部の処理は複雑になるため日本語で書いていますが、なんとなくイメージはつかめたでしょうか。

'* 自販機で何かを買うプロセス
Public Sub buyDrink()

    Dim item As String '商品
    Dim money As Long  '投入金額
    Dim change As Long 'おつり
    
    ' 商品を決めてお金を入れる(と、おつりが返ってくる)
    item = "コーラ"
    money = 200
    change = autoDispenser(item, money)
    
    If money = change Then
        ' 「投入金額=おつり」:買えなかった
        MsgBox "買えませんでした(売り切れor投入金額不足)"
    Else
        ' 買えた
        MsgBox item & "が買えました。おつりは" & change & "円です。"
    End If

End Sub

'* 自販機本体
Private Function autoDispenser(ByVal item As String, ByVal money As Long) As String

    If 【itemの在庫がある】 Then
        ' おつり計算
        autoDispenser = money - 【itemの値段】
        If autoDispenser < 0 Then
            ' 在庫はあるが投入金額不足だった
            autoDispenser = money
        End If
    Else
        ' 在庫なし
        autoDispenser = money
    End If

End Function

おしまい

「PublicとPrivateの使い分け」「SubとFunctionの違い / 戻り値」「プロシージャに渡す引数」と、やたら盛りだくさんな内容となってしまいましたが、いかがでしたでしょうか。
ただ、概念を説明するのに長ったらしくなっていますが、理解さえできれば複雑な内容ではありません。最初に書いた通り、覚えること自体はそう多くないのです。

…すみません、「どこがだよ!わかんねえよ!」な方はこっそり教えていただけると助かります…。

次回予告

ExcelVBA入門 #10:定数を定義することとそのメリット - アナログCPU:5108843109

次回は「定数」の説明とその使い方について。