Sub と Function の違い

にメンテナンス済み

VBA でコードを書いていくと、「処理をまとめて名前を付けて再利用したい」という場面が必ず出てきます。VBA では処理のまとまりをプロシージャと呼び、大きく分けて Sub プロシージャFunction プロシージャの2種類があります。

この2つは似ているようで明確な違いがあり、使い分けを理解することはVBAプログラミングの基礎として非常に重要です。この記事では、Sub と Function の違いから、引数の渡し方(ByRef / ByVal)、スコープの制御まで詳しく解説します。

Sub と Function を理解するメリット

プロシージャの基礎を正しく理解することで、以下のようなメリットがあります:

  • コードの再利用: 同じ処理を何度も書かずに済む
  • 可読性の向上: 処理に名前を付けることで、何をしているかが一目でわかる
  • 保守性の向上: 修正が必要なとき、1箇所を直すだけで済む
  • テストのしやすさ: 処理を小さな単位に分割することで、個別にテストできる
  • ワークシート関数としての利用: Function はセルの数式から呼び出せる

Sub プロシージャ

基本構文

Sub プロシージャは、一連の処理を実行するための最も基本的なプロシージャです。戻り値を返しません

sub_syntax.bas
' 構文
Sub プロシージャ名()
    ' 処理内容
End Sub

基本的な使用例

sub_basic.bas
Sub SayHello()
    MsgBox "こんにちは!"
End Sub

Sub FormatHeader()
    With Range("A1")
        .Font.Bold = True
        .Font.Size = 14
        .Interior.Color = RGB(68, 114, 196)
        .Font.Color = RGB(255, 255, 255)
    End With
End Sub

Sub の特徴

  • マクロとして直接実行できる(Alt + F8、ボタンへの割り当てなど)
  • 戻り値を返さない(処理を実行するだけ)
  • ワークシートの数式から呼び出すことはできない
チェック

VBA で「マクロ」と呼ばれるものは、基本的に引数なしの Public Sub プロシージャのことです。Alt + F8 のマクロ一覧に表示されるのは、この条件を満たすプロシージャだけです。

Function プロシージャ

基本構文

Function プロシージャは、処理を実行して結果(戻り値)を返すプロシージャです。

function_syntax.bas
' 構文
Function 関数名() As 戻り値の型
    ' 処理内容
    関数名 = 戻り値
End Function

基本的な使用例

function_basic.bas
' 消費税込みの金額を計算する関数
Function CalcTax(price As Long) As Long
    CalcTax = price * 1.1
End Function

' 使用例
Sub TestCalcTax()
    Dim result As Long
    result = CalcTax(1000)
    Debug.Print result  ' => 1100
End Sub

戻り値の返し方

VBA の Function では、関数名に値を代入することで戻り値を返します。他のプログラミング言語の return に相当します。

function_return.bas
Function GetFullName(firstName As String, lastName As String) As String
    ' 関数名に値を代入 = 戻り値を返す
    GetFullName = lastName & " " & firstName
End Function

Sub TestGetFullName()
    Debug.Print GetFullName("太郎", "田中")  ' => 田中 太郎
End Sub
関数名への代入を忘れない

Function 内で関数名に値を代入しないと、戻り値は既定値(数値型なら 0、文字列型なら ""(空文字)、Boolean なら False)になります。予期しない結果の原因となるため、必ず関数名に値を代入しましょう。

' × 戻り値を返し忘れている
Function BadAdd(a As Long, b As Long) As Long
    Dim result As Long
    result = a + b
    ' BadAdd = result を忘れている!→ 常に 0 が返る
End Function

ワークシート関数としての利用

Function プロシージャは、Excel のセルの数式から**ユーザー定義関数(UDF)**として呼び出すことができます。

function_udf.bas
' 標準モジュールに記述
Function FullWidthToHalf(text As String) As String
    Dim i As Long
    Dim result As String
    result = ""

    For i = 1 To Len(text)
        Dim char As String
        char = Mid(text, i, 1)

        ' 全角数字を半角に変換
        If char >= "0" And char <= "9" Then
            result = result & Chr(Asc(char) - &HFF10 + Asc("0"))
        Else
            result = result & char
        End If
    Next i

    FullWidthToHalf = result
End Function

上記の関数を定義すると、セルに =FullWidthToHalf(A1) と入力して使えます。

ユーザー定義関数(UDF)の制限

ワークシートから呼び出す Function では、セルの書式変更やメッセージボックスの表示など、ワークシートに副作用を与える操作はできません。値を計算して返すことに専念しましょう。

Sub と Function の違い

比較表

特徴SubFunction
戻り値× なし○ あり
マクロとして直接実行○ 可能△ 可能だが通常はしない
ワークシートの数式から呼出× 不可○ 可能(UDF)
主な用途処理の実行値の計算・変換
Call文での呼び出し○(戻り値は無視される)

使い分けの基準

Sub が適しているケース:

  • セルの書式を変更する
  • メッセージボックスを表示する
  • ファイルの読み書きを行う
  • シートの追加・削除を行う
  • 他のマクロを呼び出して一連の処理を実行する

Function が適しているケース:

  • 値を計算して結果を返す(税込金額、文字列変換など)
  • 条件に基づいて True/False を返す(バリデーション)
  • ワークシート関数として使用する
  • 他のプロシージャに計算結果を渡す
sub_vs_function.bas
' ○ Sub が適切:セルの書式変更(副作用がある処理)
Sub HighlightCell(targetRange As Range)
    targetRange.Interior.Color = RGB(255, 255, 0)
    targetRange.Font.Bold = True
End Sub

' ○ Function が適切:値の計算(結果を返す処理)
Function IsAdult(age As Long) As Boolean
    IsAdult = (age >= 18)
End Function

' 使用例
Sub Main()
    If IsAdult(20) Then
        Call HighlightCell(Range("A1"))
    End If
End Sub

引数の基礎

引数とは

**引数(ひきすう)**とは、プロシージャに渡すデータのことです。引数を使うことで、同じプロシージャを異なるデータで再利用できます。

arguments_basic.bas
' 引数なし
Sub GreetDefault()
    MsgBox "こんにちは!"
End Sub

' 引数あり(名前を受け取る)
Sub GreetByName(name As String)
    MsgBox "こんにちは、" & name & " さん!"
End Sub

' 複数の引数
Sub CreateReport(sheetName As String, title As String, startRow As Long)
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets(sheetName)
    ws.Cells(startRow, 1).Value = title
    ws.Cells(startRow, 1).Font.Bold = True
    Set ws = Nothing
End Sub

Optional 引数(省略可能な引数)

Optional キーワードを使うと、呼び出し時に省略できる引数を定義できます。

optional_arguments.bas
Function FormatPrice(price As Long, Optional taxRate As Double = 0.1) As String
    Dim total As Long
    total = price * (1 + taxRate)
    FormatPrice = Format(total, "#,##0") & " 円"
End Function

Sub TestFormatPrice()
    Debug.Print FormatPrice(1000)       ' => 1,100 円(既定の税率 10%)
    Debug.Print FormatPrice(1000, 0.08) ' => 1,080 円(税率 8%)
End Sub
チェック

Optional 引数は、引数リストの末尾にまとめて配置する必要があります。必須引数の後に Optional 引数を置きます。

IsMissing 関数

Variant 型の Optional 引数が省略されたかどうかを判定するには IsMissing 関数を使用します。

is_missing.bas
Sub LogMessage(message As String, Optional logLevel As Variant)
    Dim prefix As String

    If IsMissing(logLevel) Then
        prefix = "[INFO]"
    Else
        prefix = "[" & logLevel & "]"
    End If

    Debug.Print prefix & " " & message
End Sub

Sub TestLogMessage()
    Call LogMessage("処理を開始します")              ' => [INFO] 処理を開始します
    Call LogMessage("エラーが発生しました", "ERROR")  ' => [ERROR] エラーが発生しました
End Sub

ByRef と ByVal - 引数の渡し方

VBA の引数の渡し方には ByRef(参照渡し)ByVal(値渡し) の2種類があります。この違いはVBA初心者が最もつまずきやすいポイントの一つです。

ByRef(参照渡し)- 既定

ByRef は引数を参照で渡す方式で、VBA の既定の動作です。プロシージャ内で引数の値を変更すると、呼び出し元の変数も変更されます

byref_example.bas
Sub DoubleValue(ByRef num As Long)
    num = num * 2
End Sub

Sub TestByRef()
    Dim value As Long
    value = 10

    Debug.Print "呼び出し前: " & value  ' => 10
    Call DoubleValue(value)
    Debug.Print "呼び出し後: " & value  ' => 20(変更された!)
End Sub

ByVal(値渡し)

ByVal は引数を値のコピーで渡す方式です。プロシージャ内で引数を変更しても、呼び出し元の変数は変更されません

byval_example.bas
Sub DoubleValueByVal(ByVal num As Long)
    num = num * 2
    Debug.Print "プロシージャ内: " & num  ' => 20
End Sub

Sub TestByVal()
    Dim value As Long
    value = 10

    Debug.Print "呼び出し前: " & value  ' => 10
    Call DoubleValueByVal(value)
    Debug.Print "呼び出し後: " & value  ' => 10(変更されない)
End Sub

ByRef と ByVal の比較

特徴ByRef(参照渡し)ByVal(値渡し)
既定の動作○ 省略時はこちら× 明示的に指定が必要
呼び出し元への影響あり(値が変更される)なし(コピーが渡される)
メモリ効率○ コピーを作らない△ コピーを作成する
安全性△ 意図しない変更のリスク○ 安全
byref_byval_comparison.bas
' ByRef: 呼び出し元の変数が変更される
Sub IncrementByRef(ByRef counter As Long)
    counter = counter + 1
End Sub

' ByVal: 呼び出し元の変数は変更されない
Sub IncrementByVal(ByVal counter As Long)
    counter = counter + 1  ' このプロシージャ内でのみ有効
End Sub

Sub TestComparison()
    Dim count As Long

    ' ByRef のテスト
    count = 0
    Call IncrementByRef(count)
    Debug.Print "ByRef 後: " & count  ' => 1

    ' ByVal のテスト
    count = 0
    Call IncrementByVal(count)
    Debug.Print "ByVal 後: " & count  ' => 0
End Sub
引数は ByVal を基本にする

意図しない副作用を防ぐため、特別な理由がなければ ByVal を使用するのがお勧めです。ByRef は「呼び出し元の値を意図的に変更したい」場合にのみ使用しましょう。VBA では ByRef が既定のため、うっかりミスで呼び出し元の変数が書き換わってしまうことがよくあります。

ByRef は既定であるため省略に注意

引数に ByRefByVal も付けない場合、VBA では自動的に ByRef になります。これは他のプログラミング言語と異なる挙動であり、VBA特有のバグの原因になりやすいポイントです。

' ByRef が暗黙的に適用される
Sub ModifyValue(num As Long)  ' ← 実質的に ByRef num As Long
    num = 999
End Sub

プロシージャの呼び出し方

Sub の呼び出し

Sub プロシージャを呼び出すには、Call 文を使う方法と、Call を省略する方法があります。

call_sub.bas
Sub Greet(name As String)
    MsgBox "こんにちは、" & name & " さん!"
End Sub

Sub TestCallSub()
    ' 方法1: Call 文を使う(引数は括弧で囲む)
    Call Greet("太郎")

    ' 方法2: Call を省略する(引数は括弧で囲まない)
    Greet "太郎"
End Sub
Call の有無で括弧のルールが変わる

VBA では Call を使う場合は引数を括弧で囲みCall を省略する場合は括弧を付けないというルールがあります。この違いは非常にわかりにくく、初心者がよく混乱するポイントです。

' ○ 正しい
Call Greet("太郎")     ' Call あり → 括弧あり
Greet "太郎"           ' Call なし → 括弧なし

' × 間違い(単一のByRef引数で問題が起きうる)
Greet("太郎")          ' Call なし → 括弧あり(意図しない動作の可能性)

統一性のために、Call 文を常に使う方針をお勧めします。

Function の呼び出し

Function プロシージャの戻り値を受け取るには、関数呼び出しの結果を変数に代入します。

call_function.bas
Function AddNumbers(a As Long, b As Long) As Long
    AddNumbers = a + b
End Function

Sub TestCallFunction()
    ' 戻り値を変数に代入
    Dim result As Long
    result = AddNumbers(10, 20)
    Debug.Print result  ' => 30

    ' 直接使用することもできる
    Debug.Print "合計: " & AddNumbers(5, 3)  ' => 合計: 8

    ' 条件式に使用
    If AddNumbers(1, 2) > 0 Then
        Debug.Print "正の値です"
    End If
End Sub

スコープ(公開範囲)

Public と Private

プロシージャのスコープ(他のモジュールから呼び出せるかどうか)は、PublicPrivate で制御します。

scope.bas
' Public: 他のモジュールからも呼び出せる(既定)
Public Sub PublicProcedure()
    Debug.Print "どこからでも呼び出せます"
End Sub

' Private: 同じモジュール内からのみ呼び出せる
Private Sub PrivateProcedure()
    Debug.Print "同じモジュール内からのみ呼び出せます"
End Sub

' Public は省略可能(Sub は既定で Public)
Sub DefaultProcedure()
    Debug.Print "これも Public として扱われます"
End Sub
修飾子呼び出せる範囲マクロ一覧に表示
Publicすべてのモジュールから呼び出せる○(引数なしの場合)
Private同じモジュール内からのみ×
省略時Public と同じ○(引数なしの場合)
ヘルパー関数は Private にする

メインの処理から呼び出されるだけの補助的なプロシージャは Private にしましょう。マクロ一覧に不要なプロシージャが表示されなくなり、ユーザーが間違って実行するリスクも減ります。

実践的な活用例

例1:処理を Sub に分割して構造化する

practical_structured.bas
Option Explicit

' メインの処理
Public Sub GenerateReport()
    Call InitializeSettings
    Call ClearPreviousData
    Call ImportData
    Call FormatReport
    Call RestoreSettings

    MsgBox "レポートの生成が完了しました", vbInformation
End Sub

Private Sub InitializeSettings()
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
End Sub

Private Sub ClearPreviousData()
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("レポート")
    ws.Cells.Clear
    Set ws = Nothing
End Sub

Private Sub ImportData()
    ' データのインポート処理
    Dim wsData As Worksheet
    Dim wsReport As Worksheet
    Set wsData = ThisWorkbook.Sheets("データ")
    Set wsReport = ThisWorkbook.Sheets("レポート")

    Dim lastRow As Long
    lastRow = wsData.Cells(wsData.Rows.Count, 1).End(xlUp).Row

    wsData.Range("A1:D" & lastRow).Copy Destination:=wsReport.Range("A1")

    Set wsData = Nothing
    Set wsReport = Nothing
End Sub

Private Sub FormatReport()
    ' レポートの書式設定処理
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Sheets("レポート")

    With ws.Rows(1)
        .Font.Bold = True
        .Interior.Color = RGB(68, 114, 196)
        .Font.Color = RGB(255, 255, 255)
    End With

    Set ws = Nothing
End Sub

Private Sub RestoreSettings()
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
End Sub

例2:汎用的な Function を作成する

practical_functions.bas
Option Explicit

' 指定した範囲内の数値セルの平均を計算
Function AverageNonEmpty(targetRange As Range) As Double
    Dim cell As Range
    Dim total As Double
    Dim count As Long
    total = 0
    count = 0

    For Each cell In targetRange
        If IsNumeric(cell.Value) And cell.Value <> "" Then
            total = total + CDbl(cell.Value)
            count = count + 1
        End If
    Next cell

    If count > 0 Then
        AverageNonEmpty = total / count
    Else
        AverageNonEmpty = 0
    End If
End Function

' 文字列が指定した文字で始まるかを判定
Function StartsWith(ByVal text As String, ByVal prefix As String) As Boolean
    StartsWith = (Left(text, Len(prefix)) = prefix)
End Function

' 文字列が指定した文字で終わるかを判定
Function EndsWith(ByVal text As String, ByVal suffix As String) As Boolean
    If Len(text) < Len(suffix) Then
        EndsWith = False
    Else
        EndsWith = (Right(text, Len(suffix)) = suffix)
    End If
End Function

' 使用例
Sub TestFunctions()
    Debug.Print StartsWith("Excel VBA", "Excel")  ' => True
    Debug.Print EndsWith("report.xlsx", ".xlsx")   ' => True
    Debug.Print EndsWith("data.csv", ".xlsx")      ' => False
End Sub

例3:ByRef を活用した複数の戻り値

Function の戻り値は1つだけですが、ByRef 引数を使うと実質的に複数の値を返すことができます。

practical_multiple_return.bas
Option Explicit

' 範囲内のデータの統計情報を取得
Sub GetStatistics(targetRange As Range, _
                  ByRef outMin As Double, _
                  ByRef outMax As Double, _
                  ByRef outAvg As Double, _
                  ByRef outCount As Long)
    Dim cell As Range
    Dim total As Double
    outMin = 1E+308  ' 十分に大きな値で初期化
    outMax = -1E+308
    total = 0
    outCount = 0

    For Each cell In targetRange
        If IsNumeric(cell.Value) And cell.Value <> "" Then
            Dim val As Double
            val = CDbl(cell.Value)
            If val < outMin Then outMin = val
            If val > outMax Then outMax = val
            total = total + val
            outCount = outCount + 1
        End If
    Next cell

    If outCount > 0 Then
        outAvg = total / outCount
    Else
        outMin = 0
        outMax = 0
        outAvg = 0
    End If
End Sub

Sub TestGetStatistics()
    Dim minVal As Double
    Dim maxVal As Double
    Dim avgVal As Double
    Dim cnt As Long

    Call GetStatistics(Range("A1:A100"), minVal, maxVal, avgVal, cnt)

    Debug.Print "件数: " & cnt
    Debug.Print "最小: " & minVal
    Debug.Print "最大: " & maxVal
    Debug.Print "平均: " & Format(avgVal, "#,##0.0")
End Sub

注意点とよくある間違い

1. Function の戻り値が返されない

mistake_no_return.bas
' × 戻り値を代入し忘れている
Function CalcArea_Bad(width As Double, height As Double) As Double
    Dim area As Double
    area = width * height
    ' CalcArea_Bad = area を忘れている → 0 が返る
End Function

' ○ 正しい
Function CalcArea_Good(width As Double, height As Double) As Double
    CalcArea_Good = width * height
End Function

2. Function 内で関数名を再帰呼び出ししてしまう

mistake_recursive.bas
' × 意図しない再帰呼び出し
Function Factorial(n As Long) As Long
    If n <= 1 Then
        Factorial = 1
    Else
        Factorial = n * Factorial(n - 1)  ' これは再帰呼び出し(意図的ならOK)
    End If
End Function
チェック

VBA では 関数名 = 値 が戻り値の設定、関数名(引数) が再帰呼び出しです。戻り値の設定なのか再帰呼び出しなのかを明確にコメントで示しましょう。

3. Call 文の括弧ルール

mistake_call_parentheses.bas
Sub ShowMessage(msg As String)
    MsgBox msg
End Sub

Sub TestCallRules()
    ' ○ 正しい呼び出し方
    Call ShowMessage("テスト")  ' Call + 括弧
    ShowMessage "テスト"        ' Call なし + 括弧なし

    ' △ 動くが紛らわしい
    ShowMessage ("テスト")      ' 括弧が式の評価として扱われる
End Sub

練習問題

次の説明のうち、Function プロシージャの特徴として正しいものはどれですか?

正解は「ワークシートのセルから数式として呼び出すことができる」です。Function プロシージャはユーザー定義関数(UDF)としてセルの数式から直接呼び出せます。Sub プロシージャにはこの機能はありません。

次のコードを実行した後、変数 x の値はいくつですか?
Sub ChangeValue(ByVal num As Long)
    num = num + 100
End Sub

Sub Test()
    Dim x As Long
    x = 5
    Call ChangeValue(x)
    Debug.Print x
End Sub
正解は「5」です。引数が ByVal(値渡し)で渡されているため、プロシージャ内で num の値を変更しても、呼び出し元の変数 x には影響しません。もし ByRef だった場合は 105 になります。

VBA で引数に ByRef も ByVal も指定しなかった場合、どちらが適用されますか?

正解は「ByRef(参照渡し)が適用される」です。VBA では引数の渡し方を省略した場合、既定で ByRef(参照渡し)が適用されます。これはVBA特有のルールであり、意図しない値の変更を防ぐために、ByVal を明示的に指定する習慣をつけることが推奨されます。

まとめ

  • Sub プロシージャは処理を実行するためのもので、戻り値を返さない。マクロとして直接実行できる
  • Function プロシージャは値を計算して戻り値を返す。ワークシートの数式からも呼び出せる
  • 戻り値は関数名に値を代入することで返す(関数名 = 値
  • 引数の渡し方には ByRef(参照渡し)ByVal(値渡し) の2種類がある
  • VBA の既定は ByRef であり、意図しない副作用を防ぐには ByVal を明示的に指定する
  • Optional キーワードで省略可能な引数を定義できる
  • Public / Private でプロシージャの公開範囲を制御する
  • 処理を小さな Sub / Function に分割すると、コードの可読性と保守性が向上する
変数宣言とデータ型
VBAの変数宣言(Dim)とデータ型(Integer, Long, String, Boolean等)の使い方を初心者向けに徹底解説。Option Explicitによる宣言の強制、変数のスコープ(Public/Private/Dim)、型
Property Let と Set の違い
VBA でクラスを作成し、 プロパティ(メンバー変数) を取り扱う際に作成する Property メソッドには Get, Set, Let があります。一般的に、VBA 以外の言語では、プロパティ(メンバー変数)に設定するのは Getter
With文の使い方
VBAのWith文を使用することで、同じオブジェクトへの繰り返し参照を省略し、コードの可読性と保守性を向上させる方法について解説します。基本構文から応用例、注意点まで詳しく紹介します。
WorksheetFunction
VBAからExcelのワークシート関数(VLOOKUP、COUNTIF、SUMIF等)を呼び出す方法を詳しく解説します。WorksheetFunctionオブジェクトの使い方から実践的な活用例まで、網羅的に説明します。
#VBA #Sub #Function #プロシージャ #引数 #ByRef #ByVal #初心者向け