プロシージャ

説明

プロシージャとは

プロシージャとは、プログラムの冒頭にある「Sub」等が該当します。
下記サンプルの赤字部分です。

Sub Sample1()
    MsgBox "w((+_+))w ウェイ"
End Sub

今まで何気なく使用してきた「Sub」について詳しく見ていこうという回です。

Microsoftによるプロシージャの解説

プロシージャとは、Microsoft公式(用語集)によると次のようにあります。

1 つの単位として実行されるステートメントの名前付きシーケンス。 たとえば、Function、Property、Sub はプロシージャの種類です。 プロシージャ名は常にモジュール レベルで定義されます。 すべての実行可能コードはプロシージャに含まれる必要があります。 プロシージャを他のプロシージャの入れ子にすることはできません。

MicroSoftは一般人がわかりにくいように解説する天才なので(笑)、かみ砕いて説明します。
プロシージャは、Function、Property、Subの3種類があり、変数の宣言等を除くプログラムは全てこのプロシージャ内に記載する必要があります。
プロシージャ内にプロシージャを定義することはできません。
これは次のサンプルを見てください。

' ダメなサンプル例です。
Sub Sample2A()
    ' プロシージャ内にプロシージャを定義しているため、エラー
    Sub Sample2B()
        MsgBox "w((+_+))w ウェイ"
    End Sub
End Sub

上記のサンプルでは、Subプロシージャ内にSubプロシージャを定義(作成)しようとしてエラーが発生しています。
Sub ~ とプロシージャを定義したら、End SubまではSub ~ と書けないということです。

Sub, Function, Property の役割

各プロシージャの使用用途は次の表を参考にして下さい。
Propertyについてはクラスを理解する必要があるため、簡易的な説明にとどめます。

プロシージャの種類使用目的
Subプログラムの開始、プログラムの機能を分割するときに使用する
※プロシージャが長すぎると可読性が低下する
Function戻り値(詳細は後述)を得るために使用する
Propertyクラス使用時に、データをカプセル化する際に使用する

プロシージャの呼び出し

実行しているプロシージャと、別のプロシージャを呼び出すことができます。

"プロシージャ名"で呼び出す

実行しているプロシージャ以外のプロシージャ名を入力すると、
そのプロシージャを呼び出すことができます。

次のサンプルプログラムを確認してください。
なお、Sample3Aを実行してください。

' Sample3Aを実行してください。
Sub Sample3A()
    MsgBox "Sample3Aを実行しています。"
    Smple3B
End Sub
' Sample3Aから呼び出されます。
Sub Sample3B()
    MsgBox "Sample3Bが呼び出されました。"
End Sub

上記、サンプルのSample3Aを実行すると次のようになります。

実行結果:Sample3Aを実行しています。
実行結果:Sample3Bが呼び出されました。

デバッグ→ステップイン(ショートカットキー:F8)を繰り返してみるとわかりますが、
指定したプロシージャへ実行ステップを移動させることができます。

"Call プロシージャ名"で呼び出す

先の方法とは別に、プロシージャを呼び出す方法が別にあります。

Call プロシージャ名()

今回は、プロシージャの前に『Call』がつき、プロシージャの後ろに『()』がつきます。
サンプルプログラム「Sample4B」を実行してください。

' Sample4Aを実行してください。
Sub Sample4A()
    Call Smple4B()
End Sub
' ここにエラー発生時の専用のプログラムを入力する
Sub Sample4B()
    MsgBox "Sample4Bが呼び出されました"
End Sub

上記、サンプルのSample4Bを実行すると次のようになります。

実行結果:Sample4Bが呼び出されました

先述の「Sample4A」と同様に実行ステップをプロシージャへ移動させることができました。

"Call"を使うべき?

呼び出すプロシージャ名の前にCallがつく場合とつかない場合の差です。
Microsoft公式(Call ステートメント)のリファレンスによると次のようにあります。

プロシージャを呼び出すとき、Call キーワードを使用する必要はありません。しかし、Call キーワードを使用して、引数を必要とするプロシージャを呼び出す場合、__ argumentlist をカッコで囲む必要があります。 Call キーワードを省略する場合、__ argumentlist のカッコを外す必要があります。 Call 構文を使用して、ネイティブまたはユーザー定義の関数を呼び出す場合、関数の戻り値は破棄されます。 プロシージャに配列全体を渡すには、空のカッコの後に配列名を使用します。

上記の通り、Callを使用しなくても正常にプログラムは動作します。
(黒字の説明は、Callをつけると後述する戻り値が無い時でも()をつけられる、という意味です)
ただし、個人的にですが変数と見間違えてしまうことを避けるため、Callをつけるべきだと考えています。

呼び出し先のプロシージャ内に同じ名前の変数名がある場合、それぞれ別の変数として扱われます。

' プロシージャ「Sample5A」の変数strは"イチゴ"
Sub Sample5A()
    Dim str As String
    Str = "イチゴ"
    MsgBox "Sample5Aのstr:" & str
    Call Sample5B()
End Sub
' プロシージャ「Sample5B」の変数strは"ミカン"
Sub Sample5B()
    Dim str As String
    Str = "ミカン"
    MsgBox "Sample3Bのstr:" & str 
End Sub

呼び出し元の変数を別プロシージャで使いたい場合は後述の「引数」を参照下さい。

Subプロシージャで機能ごとにプロシージャを分割してみる

プログラムの開始、プログラムの機能を分割するときに使用します。
1つのプロシージャが長くなりすぎると可読性が下がるため、機能ごとにプロシージャを分割する必要があります。

例えば、表を書き出すプログラムを作成したとします。
どちらが読みやすいでしょうか?

'パターンA:表を書く
Sub SampleMakeGlaph_A()
    Range("B2:D9").NumberFormatLocal = "@"       '表全体のセルの書式を文字列にする
    Range("B2") = "No"
    Range("B2:D9").Borders.LineStyle = True      'B2~D9に表の罫線を引く
    Range("C2") = "会員番号"
    Range("B2:D2").Interior.Color = vbYellow     'B2~D9に表題部分の背景色を黄色にする
    Range("D2") = "氏名"
    Range("B2:D2").HorizontalAlignment = xlCenter   '見出し部分を文字の中央寄せにする
End Sub

次にパターンBです。

'パターンB:表を書く
Sub SampleMakeGlaph_B()
    Call MakeGlaphFormat()      '表の形式を描画する
    Call MakeGlaphTitle()       '表の見出しを書く
End Sub

'表の形式を描画する
Sub MakeGlaphFormat()
    Range("B2:D9").Borders.LineStyle = True    'B2~D9に表の罫線を引く
    Range("B2:D9").NumberFormatLocal = "@"     '表全体のセルの書式を文字列にする
    Range("B2:D2").Interior.Color = vbYellow   'B2~D9に表題部分の背景色を黄色にする
    Range("B2:D2").HorizontalAlignment = xlCenter  '見出し部分を文字の中央寄せにする
End Sub

'表の見出しを書く
Sub MakeGlaphTitle()
    Range("B2") = "No"
    Range("C2") = "会員番号"
    Range("D2") = "氏名"
End Sub

上記パターンA、Bともに同じ処理結果となりますが、読みやすいのは、パターンBでしょう。
このように機能を分割することで読みやすくなります。
今回の例は、少しわざとらしい部分がありますが、気を付けるべきです。

Functionプロシージャで戻り値を取得する

実行しているプロシージャから別のプロシージャを呼び出すことができますが、
そのプロシージャから呼び出したプロシージャへ値を渡すことができます。
そのときの別のプロシージャから渡した値を戻り値と呼びます。

Function プロシージャ名()
    プロシージャ名 = 戻す値
End Function

Functionプロシージャを定義するには、Function ~で定義を開始して、End Functionで定義を終了させます。
Functionのプロシージャは変数のように使用でき、その値が戻り値となります。
下記サンプルを見てください。

' Sample6を実行してください。
Sub Sample6()
    Dim num As Long
    Range("A1") = 4   '・・・①
    Range("B1") = 2   '・・・②
    num = AddFunc()   '・・・③
    MsgBox num
End Sub
' セルA1とB1の和算を返す
Function AddFunc()
    AddFunc = Range("A1") + Range("B1")   '・・・④
End Function

上記、サンプルのSample4を実行すると次のようになります。

実行結果:6

最初に、一定の結果を得るため、セルA1に4、セルB1に2を最初に入れています。(サンプル①、②)
サンプル③の部分で「AddFunc」へステップが移動します。
AddFunc関数で、セルA1とB1の足し算を行い、AddFuncに格納しています。(サンプル④)
なお、プロシージャ名(AddFunc)に値を格納する必要があります
プロシージャ「AddFunc」が終わり、サンプル③の位置に戻ってきます。
"AddFunc"に格納された数値(6)を、Sample4のnumへ格納します(サンプル③)。
MsgBoxで「3」が表示されます。

引数について

引数とは、他のプロシージャに変数もしくは変数の値を送ることです。
先述のFunctionプロシージャと値渡し・参照渡しを組み合わせることで、緻密な処理が可能になります。

引数付きのプロシージャ

プロシージャを呼び出すときに、呼び出すプロシージャへ値を渡すことができます。
その値を「引数」といい、次のように使用します。

プロシージャの種類 プロシージャ名 (キーワード※ 変数名 As 型名)

上記補足として、「プロシージャの種類」は、Sub、Function、Propertyのいずれかです。
「キーワード」は、ByRef、ByVal のどちらかで、入力省略時はByRefが記載されたことになります。
ByRefについては後述の「参照渡し」、ByValについては後述の「値渡し」の項目をお読みください
「As 型名」は変数の宣言と同様に型の指定を省略できますが、可読性を上げるため、明記しましょう。
なお、型の宣言を省略したときは、Variant型になります。

' Sample7Aを実行してください。
Sub Sample7A()
    Call Sample7B("今日もいい天気です。")
End Sub
' 呼び出されるプロシージャ
Sub Sample7B(ByRef str As String)
    MsgBox str
End Sub

実行結果:今日もいい天気です。

引数は2つ以上指定でき、その場合は変数と変数の間を","(カンマ)で区切ります。

' Sample8Aを実行してください。
Sub Sample8A()
    Call Sample8B(1, 2)
End Sub
' 呼び出されるプロシージャ
Sub Sample8B(ByRef i as Long, ByRef j as Long)
    MsgBox i + j
End Sub

実行結果:3

参照渡し

「参照渡し」とは、受け取った側のプロシージャで引数で値を変更したとき、その変更を呼び出し元のプロシージャへ反映させることを言います。
簡単に説明すると、呼び出し元の変数を呼び出し先でも使いたいときに使います。

'パターン1(ByRefを省略しない方法)
プロシージャの種類 プロシージャ名(ByRef 変数名 As 型名)
'パターン2(ByRefを省略する方法)
プロシージャの種類 プロシージャ名(変数名 As 型名)

受け取り側のプロシージャで変数名の前に「ByRef」を指定するか、省略することで参照渡しになります。
省略することができますが、思わぬミスを誘発するので、必ずByRefをつけるようにしましょう。

次にサンプルです。

' Sample9Aを実行してください。
Sub Sample9A()
    Dim num11 As Long
    num1 = 1
    Call Sample9B(num1)         ' num1 を 参照渡しする
    MsgBox "num1 = " & num1
End Sub
' する
Sub Sample9B(ByRef num2 As Long)
    num2 = num2 + 2          ' Sample6Aの num1 と Sample6B の num2 が 3 になる
End Sub

上記サンプルを実行した結果は、次の通りです。

実行結果:num1 = 3

サンプル内のコメントにもありますが、Sample6Aの num1 と Sample6B の num2 が 3 になります。
これに対し、別の変数として扱うのが次項の「値渡し」になります。

値渡し

「値渡し」とは、受け取った側のプロシージャで値だけ使い、呼び出し元のプロシージャへ反映させないことを言います。
簡単に説明すると、呼び出し元の値を呼び出し先にコピーします。
注意点があり、値の格納時にSetを使用する型は値渡しができません。

プロシージャの種類 プロシージャ名(ByVal 変数名 As 型名)

変数の前にByValと記載することで値渡しができます。
次にサンプルです。

' Sample10A を実行してください。
Sub Sample10A()
    Dim num As Long
    num = 1
    Call Sample10B(num)
    MsgBox "Sample10A の num = " & num
End Sub
' 加算する
Sub Sample10B(ByVal num As Long)
    num = num + 2
    MsgBox "Sample10B の num = " & num
End Sub/span>

上記サンプルを実行した結果は、次の通りです。

実行結果:Sample10A の num = 1
     Sample10B の num = 3

参照渡しとは異なり、呼び出し元(Sample10A)のnumは呼び出し先(Sample10B)で変更しても値が変わりません。

ここで1つアドバイスです。
String型とVariant型は値渡しでなく、前述の参照渡しを使用すべきです。
値渡しは変数の値をコピーしているため、バイト数の上限が極端に大きいString型とVariant型は、処理に時間がかかる場合があります。
これに対し、参照渡しの場合はどの型を引数にしても4バイトが渡されるだけです。
(参照元:Microsoft社のリファレンス:引数を効率的に渡す)。

可変長引数:ParamArray

ParamArrayは引数の個数が定まっていないときに使用します。
呼び出し先では1つの配列の変数になるため、引数の個数が定まっていない値は全て同じ型で、
呼び出し先の型は必ずVariant型で宣言(Variant型以外はエラーになる)。

プロシージャの種類 プロシージャ名(ParamArray 変数名() As Variant)

ParamArray は For + UBound関数が非常に相性が良いです。
UBoundで引数の配列の上限を取得しつつ、Forで最大限まで繰り返すという方法がとれます。

' Sample11A を実行してください。
Sub Sample11A()
    Call Sample11B("リンゴ", "梨", "サクランボ")
End Sub

Sub Sample11B(ParamArray str() As Variant)
    Dim i As Long
    Dim buf As String
    For i = 0 To UBound(str)    'UBoundで配列の最大要素数を取得する
        buf = buf & (i + 1) & "番目の要素:" & str(i) & vbNewLine
    Next i
    MsgBox buf
End Sub

上記サンプルを実行した結果は、次の通りです。

実行結果 1番目の要素:リンゴ
     2番目の要素:梨
     3番目の要素:サクランボ

省略可能な引数:Optional

呼び出し元で他のプロシージャを呼び出すとき、引数を省略することができます。
ほぼ毎回同じ値で、特別な時にのみ値を変えるときに有用です。

'パターン1(初期値を定めたいとき)
プロシージャの種類 プロシージャ名(Optional 変数名 As 型名 = 初期値)
'パターン2(初期値は変数の型によって任せる場合 ※)
プロシージャの種類 プロシージャ名(Optional 変数名 As 型名)

 ※・・・引数の初期値は数値型なら0、文字列型ならVbNullStaring、Object型ならNothingです。

下記サンプルを見てください。
呼び出し元に引数を

' スタート
Sub Sample12A()
    ' 引数を省略可能している
    Call Sample12B
End Sub
' 引数にOptionalを付けることで省略可能とする
Sub Sample12B(Optional ByVal num2 As Long = 1)
    num2 = num2 + 2
    MsgBox "num2 = " & num2
End Sub

上記サンプルを実行した結果は、次の通りです。

実行結果 num2 = 3

当然のことながら、呼び出す側で引数の値を指定することもできます。

' スタート
Sub Sample13A()
    Dim num1 As Long
    num1 = 10
    Call Sample13B(num1)
    MsgBox "num1 = " & num1
End Sub
' する
Sub Sample13B(Optional ByVal num2 As Long)
    num2 = num2 + 2
    MsgBox "num2 = " & num2
End Sub

上記サンプルを実行した結果は、次の通りです。

実行結果 num2 = 12
     num1 = 10

注意点として、「Optional」のつく省略可能な引数は省略できない引数よりも後ろに定義してください。
※下はエラーが発生するサンプルです。

' スタート
Sub Sample14A()
    Dim num1 As Long
    num1 = 10
    Call Sample14B(num1)
    MsgBox "num1 = " & num1
End Sub
' 省略可能なOptionは省略できない引数の前に指定することはできない
Sub Sample14B(Optional ByVal num2 As Long, Byval num3 As Long)
    num2 = num2 + num3
    MsgBox "num2 = " & num2
End Sub

Sub Sample14Bの引数の定義を『Byval num3 As Long, Optional ByVal num2 As Long』と、Optionalの引数定義の位置を変えればエラーはなくなります。

戻り値がオブジェクト変数の場合

シートオブジェクトを返すプロシージャの例です。
オブジェクトを戻すときと受け取るときの両方で「Set」を使用します。

Sub Sample15A()
    Dim ws As Worksheet
    '変数の前に「Set」をつける
    Set ws = Sample15B("Sheet2")   
    MsgBox ws.Name
    Set ws = Nothing
End Sub
' シートオブジェクトを返す(引数:シート名)
Function Sample15B(ByVal nm As String) As Worksheet
    'プロシージャ名の前に「Set」をつける
    Set Sample15B = ThisWorkbook.Worksheets(nm)
End Function

戻り値が配列の場合

戻り値が配列の場合です。
Sample16Aの変数を配列にしますが、要素を記載しません(下記サンプル①)。
戻り値として指定する際、Sample16Bのfruitsにカッコをつけません(下記サンプル②)。

Sub Sample16A()
    Dim str() As String  '←変数の宣言時、カッコだけで要素数を記載しない ・・・①
    str = Sample16B()    '←戻り値取得時、strにカッコをつけない ・・・②
    MsgBox "str(0) = " & str(0) & vbNewLine & _
            "str(1) = " & str(1) & vbNewLine & _
            "str(2) = " & str(2)
End Sub
' 文字列(配列)を返す
Function Sample16B() As String()   '←Stringに()をつけること
    Dim fruits(2) As String
    fruits(0) = "リンゴ"
    fruits(1) = "梨"
    fruits(2) = "サクランボ"
    Sample16B = fruits   '←fruitsは配列だがカッコをつけない
End Function

引数を配列にしたい場合

引数を配列に指定したい場合です。
引数として変数を送るときは()をつけずに配列名を指定します(下記サンプルの①)。
引数として変数を受け取るときはByRefを指定し、変数名に()をつけます(下記サンプルの②)。

Sub Sample17A()
    Dim fruits(2) As String
    fruits(0) = "リンゴ"
    fruits(1) = "梨"
    fruits(2) = "サクランボ"
    Call Sample17B(fruits)   '←fruitsに()をつけない ・・・①
End Sub
' 文字列(配列)が引数
Sub Sample17B(ByRef str() As String)   '←引数の"str"に()をつけること ・・・②
    MsgBox "str(0) = " & str(0) & vbNewLine & _
            "str(1) = " & str(1) & vbNewLine & _
            "str(2) = " & str(2)
End Function

関連リンク

ページの先頭へ