2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

📊連載第39回!初心者のためのExcel VBA入門:&演算子の繰り返しとサヨナラ!🔤可変引数で任意個数の文字列を一発連結✨

Last updated at Posted at 2025-12-16

Excel VBAで可変引数を活用した文字列連結関数を実装するテクニック

私はVBAの活用経験を通じて得た知識を整理し、共有する目的で記事を作成しているプログラミング歴2年になるエンジニアです。前回は、フィルタ後の可視セルの列範囲を取得する関数の実装テクニックについて解説しました。今回は、ParamArrayを活用した可変引数による文字列連結関数の実装テクニックについて解説します。

目次

はじめに

Excel業務では、複数の文字列を特定の記号で連結する作業が頻繁に発生します。

たとえば、ファイル名を生成する際に「年月日_連番.xlsx」という形式で作成したり、データベースのキー値として「部署コード_社員番号_プロジェクトID」という形式で管理したり、システム用識別子として「会社コード_事業部コード_部署コード_チームコード_社員番号_プロジェクトID_タスクID」といった多階層の識別子を生成したりするケースです。

こうした処理を通常の方法で実装すると、以下のような冗長なコードになります。

' 通常の連結方法(ファイル名生成)
Dim fileName As String
fileName = yearMonthDay & "_" & serialNo & ".xlsx"

' キー値の生成
Dim key As String
key = deptCode & "_" & empNo & "_" & projectId

' システム用識別子の生成
Dim systemId As String
systemId = companyCode & "_" & divisionCode & "_" & deptCode & "_" & _
           teamCode & "_" & empNo & "_" & projectId & "_" & taskId

このコードには、いくつかの問題があります。

  • 連結する要素が増えるたびにコードが長くなる
  • アンダースコア(_)の記述が繰り返され、可読性が低下する
  • 連結する要素の順序を変更する際に、全体を書き直す必要がある
  • 同様の連結処理を複数箇所で行う場合、コードの重複が発生する

今回は、ParamArrayを活用した可変引数による文字列連結関数を実装することで、これらの課題を解決する方法を解説します。この関数を使うことで、任意の個数の文字列をシンプルかつ安全に連結できるようになります。

基本的な文字列連結の課題

パターン1: 直接連結

最もシンプルな方法は、&演算子で直接連結する方法です。

' ファイル名の生成例
Dim fileName As String
fileName = "Report" & "_" & "2025" & "_" & "Q1" & "_" & "Sales" & ".xlsx"
' 結果: "Report_2025_Q1_Sales.xlsx"

この方法の問題点は以下の通りです。

問題点 説明
可読性の低下 要素が増えるほど、どこまでが1つの値か分かりにくくなる
保守性の悪化 区切り文字を変更する際、すべての箇所を修正する必要がある
タイプミスのリスク &_の記述ミスが発生しやすい

パターン2: 変数に段階的に追加

変数に段階的に文字列を追加していく方法もあります。

Dim result As String
result = "Report"
result = result & "_" & "2024"
result = result & "_" & "Q1"
result = result & "_" & "Sales"
result = result & ".xlsx"
' 結果: "Report_2024_Q1_Sales.xlsx"

この方法も以下の問題があります。

  • コードが縦に長くなり、見通しが悪い
  • 同じパターンの繰り返しで冗長
  • 要素の追加・削除時にコードの修正範囲が広い

パターン3: 配列とJoin関数

VBAには、配列の要素を指定した区切り文字で連結するJoin関数があります。

Dim elements(3) As String
elements(0) = "Report"
elements(1) = "2024"
elements(2) = "Q1"
elements(3) = "Sales"

Dim result As String
result = Join(elements, "_") & ".xlsx"
' 結果: "Report_2024_Q1_Sales.xlsx"

Join関数は便利ですが、以下の制約があります。

制約 説明
配列の事前宣言 配列のサイズを事前に決める必要がある
要素の代入が冗長 各要素を個別に代入する必要がある
動的な要素数に弱い 要素数が変動する場合、配列のリサイズが必要

特に、要素数が実行時に決まる場合や、条件によって要素が変わる場合には、配列の管理が煩雑になります。

理想的な実装の要件

これらの課題を踏まえると、理想的な文字列連結関数は以下の要件を満たす必要があります。

  1. 任意の個数の引数を受け取れる: 2個でも10個でも同じ書き方で対応
  2. シンプルな呼び出し: 配列の宣言や要素の代入が不要
  3. 可読性が高い: 何を連結しているか一目で分かる
  4. 保守性が高い: 区切り文字の変更が容易

次のセクションでは、これらの要件を満たすJoinUnderscore関数の実装を詳しく解説します。

JoinUnderscore関数の実装

それでは、実際のコードを見ていきます。

'可変数の文字列をアンダースコアで連結
Function JoinUnderscore(ParamArray args() As Variant) As String
    Dim i As Long
    Dim result As String
    
    For i = LBound(args) To UBound(args)
        If i = LBound(args) Then
            result = args(i)
        Else
            result = result & "_" & args(i)
        End If
    Next i
    
    JoinUnderscore = result
End Function

コードの全体構造

この関数は、以下の4つの要素で構成されています。

  1. 関数のシグネチャ(関数名と引数の定義): 可変引数を受け取る宣言
  2. 変数の宣言: ループカウンタと結果格納用の変数
  3. ループ処理: 配列の各要素を順に連結
  4. 戻り値の設定: 連結結果を返す

各要素について、詳しく見ていきます。

関数のシグネチャ

Function JoinUnderscore(ParamArray args() As Variant) As String

この1行で、関数の重要な特性が定義されています。

ParamArrayの宣言

ParamArray args() As Variantという宣言により、この関数は任意の個数の引数を受け取ることができます。

' 2個の引数
result = JoinUnderscore("A", "B")
' 結果: "A_B"

' 5個の引数
result = JoinUnderscore("Report", "2024", "Q1", "Sales", "Final")
' 結果: "Report_2024_Q1_Sales_Final"

' 1個の引数でも動作
result = JoinUnderscore("OnlyOne")
' 結果: "OnlyOne"

通常の関数では、引数の個数は固定されていますが、ParamArrayを使うことで、呼び出し側で自由に引数の個数を決められます。

戻り値の型指定

As Stringにより、この関数は文字列型の値を返すことを宣言しています。文字列を連結して返すという関数の目的が明確になります。

変数の宣言

Dim i As Long
Dim result As String

必要最小限の変数だけを宣言しています。

  • i: ループカウンタとして使用する変数
  • result: 連結結果を格納する変数

ループ処理の詳細解説

ここがこの関数の核心部分です。

For i = LBound(args) To UBound(args)
    If i = LBound(args) Then
        result = args(i)
    Else
        result = result & "_" & args(i)
    End If
Next i

この処理は、以下の3つのステップで動作します。

ステップ1: ループ範囲の設定

For i = LBound(args) To UBound(args)

LBoundUBoundを使用して、配列の開始インデックスから終了インデックスまでループします。この手法の詳細は、前回の記事「Excel VBAで複数列対応のフィルタ可視セル取得関数を実装するテクニック」で解説していますので、そちらを参照してください。

ステップ2: 最初の要素の処理

If i = LBound(args) Then
    result = args(i)

最初の要素(インデックスがLBound(args)の要素)は、アンダースコアを付けずにそのままresultに代入します。

' JoinUnderscore("Report", "2024", "Q1") の場合
' i = 0 のとき
result = "Report"  ' アンダースコアなし

この処理により、連結結果の先頭にアンダースコアが付かないようにしています。

ステップ3: 2番目以降の要素の処理

Else
    result = result & "_" & args(i)
End If

2番目以降の要素は、既存のresultにアンダースコアを挟んで連結します。

' JoinUnderscore("Report", "2024", "Q1") の場合
' i = 0: result = "Report"
' i = 1: result = "Report" & "_" & "2024" = "Report_2024"
' i = 2: result = "Report_2024" & "_" & "Q1" = "Report_2024_Q1"

このように、ループを回すたびに、resultに新しい要素がアンダースコア付きで追加されていきます。

処理フローのイメージ図

引数: JoinUnderscore("A", "B", "C", "D")

配列化: args(0)="A", args(1)="B", args(2)="C", args(3)="D"

ループ1回目(i=0): result = "A"
ループ2回目(i=1): result = "A_B"
ループ3回目(i=2): result = "A_B_C"
ループ4回目(i=3): result = "A_B_C_D"

戻り値: "A_B_C_D"

戻り値の設定

JoinUnderscore = result

ループ処理で完成したresultを、関数名に代入することで、関数の戻り値として返します。

使用例

例1: 基本的な使用法

Sub Example1()
    Dim fileName As String
    
    ' 3つの要素を連結
    fileName = JoinUnderscore("Report", "2024", "Q1")
    Debug.Print fileName
    ' 結果: "Report_2024_Q1"
End Sub

例2: 変数を引数として使用

Sub Example2()
    Dim year As String
    Dim month As String
    Dim day As String
    
    year = "2024"
    month = "11"
    day = "18"
    
    Dim dateStr As String
    dateStr = JoinUnderscore(year, month, day)
    Debug.Print dateStr
    ' 結果: "2024_11_18"
End Sub

例3: 多数の要素を連結

Sub Example3()
    Dim fullPath As String
    
    ' 8つの要素を連結
    fullPath = JoinUnderscore("C:", "Users", "Admin", "Documents", _
                              "Projects", "2024", "Reports", "Final.xlsx")
    Debug.Print fullPath
    ' 結果: "C:_Users_Admin_Documents_Projects_2024_Reports_Final.xlsx"
End Sub

例4: 数値も文字列として連結

Sub Example4()
    ' 数値も自動的に文字列として扱われる
    Dim id As String
    id = JoinUnderscore("EMP", 2024, 1001)
    Debug.Print id
    ' 結果: "EMP_2024_1001"
End Sub

ParamArrayVariant型なので、文字列だけでなく数値も引数として渡せます。VBAが自動的に型変換を行い、文字列として連結されます。

ParamArrayの詳細解説

ParamArrayは、VBAで可変引数を実現するための強力な機能です。基本的な特性と制約については、前回の記事「Excel VBAで複数列対応のフィルタ可視セル取得関数を実装するテクニック」で詳しく解説していますので、ここでは本記事で必要なポイントに絞って説明します。

ParamArrayの基本特性

ParamArrayには、以下のような特徴があります。

特性 説明
配置位置 必ず引数リストの最後に配置する
型指定 必ずVariant型で宣言する
個数制限 1つの関数に1つだけ使用できる
配列として扱われる 内部的には0ベースの配列になる

ParamArrayと通常引数の組み合わせ

実務では、固定引数と可変引数を組み合わせることで、より柔軟な関数設計ができます。

パターン1: 区切り文字を指定できるバージョン

Function JoinWithDelimiter(delimiter As String, _
                            ParamArray args() As Variant) As String
    Dim i As Long
    Dim result As String
    
    For i = LBound(args) To UBound(args)
        If i = LBound(args) Then
            result = args(i)
        Else
            result = result & delimiter & args(i)
        End If
    Next i
    
    JoinWithDelimiter = result
End Function

この関数では、第1引数で区切り文字を指定し、第2引数以降で連結する文字列を指定します。

' アンダースコアで連結
result = JoinWithDelimiter("_", "A", "B", "C")
' 結果: "A_B_C"

' ハイフンで連結
result = JoinWithDelimiter("-", "2024", "11", "18")
' 結果: "2024-11-18"

' スラッシュで連結
result = JoinWithDelimiter("/", "C:", "Users", "Admin", "Documents")
' 結果: "C:/Users/Admin/Documents"

このように、区切り文字を変えることで、様々な用途に対応できます。

パターン2: プレフィックスとサフィックスを追加

Function JoinWithWrapper(prefix As String, suffix As String, _
                        ParamArray args() As Variant) As String
    Dim i As Long
    Dim result As String
    
    For i = LBound(args) To UBound(args)
        If i = LBound(args) Then
            result = args(i)
        Else
            result = result & "_" & args(i)
        End If
    Next i
    
    JoinWithWrapper = prefix & result & suffix
End Function

この関数では、連結結果の前後に文字列を追加できます。

' ファイル名を生成(拡張子付き)
fileName = JoinWithWrapper("", ".xlsx", "Report", "2024", "Q1")
' 結果: "Report_2024_Q1.xlsx"

' 括弧で囲む
result = JoinWithWrapper("[", "]", "Status", "Active")
' 結果: "[Status_Active]"

' プレフィックスとサフィックスの両方を指定
result = JoinWithWrapper("ID:", "_END", "2024", "001")
' 結果: "ID:2024_001_END"

ParamArrayの内部的な動作

ParamArrayで受け取った引数は、内部的に0ベースのVariant型配列として処理されます。

' JoinUnderscore("A", "B", "C") を呼び出した場合

' 内部的には以下のような配列が作られる
' args(0) = "A"
' args(1) = "B"
' args(2) = "C"

' 配列の境界
' LBound(args) = 0
' UBound(args) = 2

このため、配列を扱う一般的な手法がすべて使用できます。

Function CountArgs(ParamArray args() As Variant) As Long
    ' 引数の個数を返す
    CountArgs = UBound(args) - LBound(args) + 1
End Function

' 使用例
Debug.Print CountArgs("A", "B", "C")  ' 結果: 3
Debug.Print CountArgs("X")            ' 結果: 1

ParamArrayの型変換

ParamArrayVariant型なので、様々な型の引数を受け取れます。

Sub TestTypeConversion()
    Dim result As String
    
    ' 文字列、数値、日付を混在させて渡す
    result = JoinUnderscore("ID", 2024, #11/18/2024#,"Active")
    Debug.Print result
    ' 結果: "ID_2024_2024/11/18_Active"
End Sub

VBAは自動的に型変換を行いますが、予期しない結果になることもあるため、引数を渡す前に明示的に文字列型に変換(CStr関数)することで、この問題を回避できます。

' 推奨: 明示的に文字列変換
result = JoinUnderscore("ID", CStr(2024), _
                       Format(#11/18/2024#, "yyyy-mm-dd"), "Active")
' 結果: "ID_2024_2024-11-18_Active"

効率的なループ処理の設計

JoinUnderscore関数のループ処理は、シンプルでありながら効率的な設計になっています。ここでは、その設計思想と代替実装との比較を行います。

採用している実装方法

For i = LBound(args) To UBound(args)
    If i = LBound(args) Then
        result = args(i)
    Else
        result = result & "_" & args(i)
    End If
Next i

この実装のポイントは、最初の要素だけ特別扱いすることで、先頭にアンダースコアが付かないようにしている点です。

代替実装パターンとの比較

パターンA: 後処理で先頭のアンダースコアを削除

' すべての要素にアンダースコアを付けてから、先頭を削除
For i = LBound(args) To UBound(args)
    result = result & "_" & args(i)
Next i

' 先頭の1文字(アンダースコア)を削除
If Len(result) > 0 Then
    result = Mid(result, 2)
End If

【この方法の問題点】

  • 不要なアンダースコアを一度追加してから削除する無駄がある
  • Mid関数の呼び出しによるオーバーヘッド(無駄な処理負荷)
  • 空の引数の場合のエラーチェックが必要

Mid関数について

Mid関数は、文字列の指定した位置から、指定した文字数を取り出す関数です。

Mid(文字列, 開始位置, [文字数])
  • 開始位置: 1から始まる位置(1文字目が1)
  • 文字数: 省略すると、開始位置から末尾までを取得

この例では、Mid(result, 2)とすることで、2文字目から末尾までを取得し、先頭の1文字(アンダースコア)を削除しています。

' 例
Mid("_A_B_C", 2)  ' 結果: "A_B_C"

パターンB: 最初の要素をループ外で処理

' 最初の要素を別に処理
result = args(LBound(args))

' 2番目以降をループで処理
For i = LBound(args) + 1 To UBound(args)
    result = result & "_" & args(i)
Next i

【この方法の問題点】

  • 引数が1つも渡されない場合にエラーが発生する
  • コードが2つのブロックに分かれて可読性が低下
  • args(LBound(args))へのアクセスが無防備

採用実装の優位性

元の実装(If i = LBound(args) Then)は、以下の点で優れています。

観点 評価
可読性 ループ内で完結しており、処理の流れが追いやすい
保守性 1つのループで処理が完結しているため、修正箇所が明確
堅牢性 空の引数や1個の引数でも安全に動作
効率性 不要な処理がなく、最小限の比較で済む

ループ最適化の考察

この実装では、毎回i = LBound(args)を比較していますが、実際には最初の1回しかTrueにならないため、理論的には無駄に見えます。しかし、以下の理由から、これは許容できる設計です。

  1. 比較処理のコストは非常に低い: 整数の比較は現代のCPUで極めて高速
  2. コードの単純性とのトレードオフ: 複雑な最適化よりもシンプルなコードの方が価値が高い
  3. 実務での引数個数: 通常は10個以下なので、パフォーマンスへの影響はほぼゼロ

文字列連結のパフォーマンス考察

VBAでは、文字列連結(&演算子)を繰り返すと、内部的にメモリの再確保が発生します。大量の文字列を連結する場合は、パフォーマンスに影響することがあります。

' 大量連結の場合の代替案: Joinを使用
Dim tempArray() As String
ReDim tempArray(LBound(args) To UBound(args))
For i = LBound(args) To UBound(args)
    tempArray(i) = args(i)
Next i
result = Join(tempArray, "_")

ただし、この最適化が意味を持つのは、数百個以上の文字列を連結する場合です。実務での使用を考えると、以下の理由から、元の実装で十分です。

  • 通常は10個以下の引数を連結する
  • 可読性とシンプルさを優先すべき
  • 最適化による効果が体感できない

パフォーマンス最適化の原則

プログラミングにおける最適化では、「早すぎる最適化は諸悪の根源」という格言があります。

最適化の優先順位

  1. まずは動くコードを書く: 正確性と可読性を最優先
  2. ボトルネックを計測する: 実際に遅い部分を特定
  3. 必要な箇所だけ最適化: 効果の高い部分に集中

今回のJoinUnderscore関数のような小規模な処理では、可読性と保守性を優先し、シンプルな実装を選択するのが正しい判断です。パフォーマンスが問題になった場合のみ、最適化を検討すればよいでしょう。

発展的な実装パターン

基本のJoinUnderscore関数をベースに、さらに機能を拡張したバリエーションを紹介します。

パターン1: 空文字列をスキップするバージョン

連結する要素の中に空文字列が含まれる場合、それをスキップして連結します。

' 空文字列を除外して連結
Function JoinUnderscoreSkipEmpty(ParamArray args() As Variant) As String
    Dim result As String
    
    Dim isFirst As Boolean
    isFirst = True
    
    Dim i As Long
    For i = LBound(args) To UBound(args)
        ' 空文字列はスキップ
        If Len(Trim(CStr(args(i)))) = 0 Then GoTo NextIteration
        
        If isFirst Then
            result = args(i)
            isFirst = False
        Else
            result = result & "_" & args(i)
        End If
        
NextIteration:
    Next i
    
    JoinUnderscoreSkipEmpty = result
End Function

使用例

' 複数の空文字列が混在
result = JoinUnderscoreSkipEmpty("A", "", "B", "", "", "C")
Debug.Print result  ' 結果: "A_B_C"

このバージョンは、以下のような場面で有用です。

  • オプション項目が空の場合がある
  • データの欠損を考慮する必要がある
  • 動的に生成された値が空になる可能性がある

パターン2: 大文字・小文字変換機能付き

連結時に、すべての要素を大文字または小文字に統一します。

' 大文字に統一して連結
Function JoinUnderscoreUpper(ParamArray args() As Variant) As String
    Dim i As Long
    Dim result As String
    
    For i = LBound(args) To UBound(args)
        If i = LBound(args) Then
            result = UCase(args(i))
        Else
            result = result & "_" & UCase(args(i))
        End If
    Next i
    
    JoinUnderscoreUpper = result
End Function

' 小文字に統一して連結
Function JoinUnderscoreLower(ParamArray args() As Variant) As String
    Dim i As Long
    Dim result As String
    
    For i = LBound(args) To UBound(args)
        If i = LBound(args) Then
            result = LCase(args(i))
        Else
            result = result & "_" & LCase(args(i))
        End If
    Next i
    
    JoinUnderscoreLower = result
End Function

使用例

' 大文字に統一
result = JoinUnderscoreUpper("Report", "sales", "Tokyo")
Debug.Print result  ' 結果: "REPORT_SALES_TOKYO"

' 小文字に統一
result = JoinUnderscoreLower("Report", "SALES", "Tokyo")
Debug.Print result  ' 結果: "report_sales_tokyo"

この機能は、以下のような場面で有用です。

  • ファイル名やキー値を統一した形式で管理したい
  • 大文字・小文字の混在による不整合を防ぎたい

パターン3: 配列を受け取るバージョン

ParamArrayではなく、配列を直接受け取るバージョンです。

' 配列を受け取って連結
Function JoinArrayUnderscore(arr As Variant) As String
    Dim i As Long
    Dim result As String
    
    ' 配列でない場合はそのまま返す
    If Not IsArray(arr) Then
        JoinArrayUnderscore = CStr(arr)
        Exit Function
    End If
    
    For i = LBound(arr) To UBound(arr)
        If i = LBound(arr) Then
            result = arr(i)
        Else
            result = result & "_" & arr(i)
        End If
    Next i
    
    JoinArrayUnderscore = result
End Function

使用例

Dim elements(2) As String

elements(0) = "A"
elements(1) = "B"
elements(2) = "C"

result = JoinArrayUnderscore(elements)
Debug.Print result  ' 結果: "A_B_C"

' Arrayリテラルも使用可能
result = JoinArrayUnderscore(Array("X", "Y", "Z"))
Debug.Print result  ' 結果: "X_Y_Z"

このバージョンは、すでに配列として用意されているデータを連結する場合に便利です。

ParamArray版と配列版の使い分け

2つのバージョンには、それぞれ適した使用場面があります。

適した場面
ParamArray版 ・ 要素を直接列挙して渡したい
・ 呼び出しコードをシンプルにしたい
・ 要素数が少ない(10個以下)
配列版 ・ すでに配列形式でデータがある
・ 動的に配列を生成して渡す
・ 要素数が多い、または可変

実務では、両方を用意しておき、状況に応じて使い分けるのが理想的です。

Arrayリテラルについて

Array関数は、引数として渡された値から、即座にVariant型の配列を作成する関数です。

構文

Array(1, 2, 3, ...)

配列変数を宣言して要素を個別に代入する手間を省き、1行で配列を作成できます。

' 通常の配列宣言
Dim arr(2) As String
arr(0) = "A"
arr(1) = "B"
arr(2) = "C"

' Arrayリテラルを使用(上記と同じ結果)
Dim arr As Variant
arr = Array("A", "B", "C")

Array関数は0ベースのVariant型配列を返すため、文字列や数値など異なる型を混在させることも可能です。関数の引数として配列を渡す際に、コードを簡潔に記述できます。

エラーハンドリングとベストプラクティス

引数チェックの実装

' エラーハンドリング付きのバージョン
Function JoinUnderscoreSafe(ParamArray args() As Variant) As String
    
    On Error GoTo ErrorHandler

    ' 引数が渡されているかチェック
    If UBound(args) < LBound(args) Then
        Err.Raise vbObjectError + 1001, "JoinUnderscoreSafe", _
                  "少なくとも1つの引数が必要です。"
    End If
    
    Dim result As String
    
    Dim i As Long
    For i = LBound(args) To UBound(args)
        ' Null値のチェック
        If IsNull(args(i)) Then
            Err.Raise vbObjectError + 1002, "JoinUnderscoreSafe", _
                      "Null値は連結できません。(引数" & (i + 1) & ")"
        End If
        
        ' オブジェクト型のチェック
        If IsObject(args(i)) Then
            Err.Raise vbObjectError + 1003, "JoinUnderscoreSafe", _
                      "オブジェクト型は連結できません。(引数" & (i + 1) & ")"
        End If
        
        If i = LBound(args) Then
            result = CStr(args(i))
        Else
            result = result & "_" & CStr(args(i))
        End If
    Next i
    
    JoinUnderscoreSafe = result
    Exit Function
    
ErrorHandler:
    Err.Raise Err.Number, Err.Source, Err.Description
End Function

このバージョンでは、以下のチェックを行っています。

1. 引数の存在チェック

If UBound(args) < LBound(args) Then
    Err.Raise vbObjectError + 1001, "JoinUnderscoreSafe", _
              "少なくとも1つの引数が必要です。"
End If

ParamArrayで引数が1つも渡されない場合、UBound(args) < LBound(args)となります。

2. Null値のチェック

If IsNull(args(i)) Then
    Err.Raise vbObjectError + 1002, "JoinUnderscoreSafe", _
              "Null値は連結できません。(引数" & (i + 1) & ")"
End If

IsNull関数でNull値を検出し、適切なエラーメッセージとともにエラーを発生させます。引数番号(i + 1)を表示することで、どの引数が問題なのかを明確にします。

3. オブジェクト型のチェック

If IsObject(args(i)) Then
    Err.Raise vbObjectError + 1003, "JoinUnderscoreSafe", _
              "オブジェクト型は連結できません。(引数" & (i + 1) & ")"
End If

IsObject関数でオブジェクト型を検出します。RangeオブジェクトやWorksheetオブジェクトなどは文字列に変換できないため、エラーとして扱います。

vbObjectErrorとユーザー定義エラー番号

VBAでカスタムエラーを発生させる際は、vbObjectError定数に任意の数値を加算してエラー番号を作成します。

vbObjectErrorとは

  • VBAが予約している定数(値: -2147221504)
  • ユーザー定義エラー用の基準値として使用
  • システムエラーと区別するための仕組み

ユーザー定義エラー番号の作成

vbObjectError + 1001  ' -2147220503
vbObjectError + 1002  ' -2147220502
vbObjectError + 1003  ' -2147220501

加算する数値(1001、1002など)は、開発者が自由に決められます。一般的には、以下のような規則で管理します。

範囲 用途
1000番台 引数エラー
2000番台 データエラー
3000番台 ファイル操作エラー
4000番台 その他のエラー

Err.Raiseメソッドについて

Err.Raiseメソッドは、意図的にエラーを発生させて呼び出し元に通知するためのメソッドです。入力値の検証などを明示的にエラーとして扱う際に使用します。

構文

Err.Raise Number, Source, Description
  • Number: エラー番号(vbObjectError + ユーザー定義番号)
  • Source: エラーの発生元(関数名やモジュール名)
  • Description: エラーの説明メッセージ

このメソッドにより、呼び出し元で適切にエラーをキャッチできるようになります。

エラーハンドリングの構造

NullチェックやオブジェクトチェックでErr.Raiseが実行されると、ErrorHandlerに飛び、そこで再度Err.Raiseが実行されます。

一見すると、ErrorHandler内で再度Err.Raiseを実行するのは無駄に見えますが、この構造は、VBAにおける標準的なエラーハンドリングパターンです。動作の流れは以下の通りです。

エラー発生(Err.Raise)
  ↓
ErrorHandlerラベルに移動
  ↓
ErrorHandlerで再度Err.Raiseを実行
  ↓
呼び出し元にエラー情報を伝達
  ↓
呼び出し元でエラー処理が可能
実装方法 動作 使い分け
再Raiseする エラー情報を呼び出し元に伝達 呼び出し元でエラー処理をさせたい場合
再Raiseしない エラーを関数内で処理して終了 エラーを関数内で完結させたい場合

この構造により、エラーの発生場所(Err.Source)、エラーの種類(Err.Number)、詳細な説明(Err.Description)がすべて呼び出し元に伝わり、適切なエラー処理が可能になります。

ErrorHandler内でエラー処理を完結させる場合

もしエラーを関数内で処理して、呼び出し元にはエラーを伝えたくない場合は、以下のように実装します。

ErrorHandler:
    ' エラーメッセージを表示して終了
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    JoinUnderscoreSafe = ""  ' デフォルト値を返す
    Err.Clear  ' エラー情報をクリア
    ' Exit Functionで終了(Raiseしない)

この場合、呼び出し元ではエラーが発生したことを検知できないため、戻り値で判断する必要があります。どちらの実装を選ぶかは、関数の用途と設計方針によって決まります。

エラーメッセージの工夫

エラーが発生した際、どの引数が問題なのかを明示することで、デバッグが容易になります。

Sub TestErrorHandling()
    Dim result As String
    
    On Error Resume Next
    
    ' Null値を含む呼び出し
    result = JoinUnderscoreSafe("A", Null, "C")
    
    If Err.Number <> 0 Then
        Debug.Print "エラー番号: " & Err.Number
        Debug.Print "エラー元: " & Err.Source
        Debug.Print "エラー内容: " & Err.Description
        ' 結果:
        ' エラー番号: -2147220502
        ' エラー元: JoinUnderscoreSafe
        ' エラー内容: Null値は連結できません。(引数2)
        Err.Clear
    End If
    
    On Error GoTo 0
End Sub

ベストプラクティス

項目 推奨内容
引数の型統一 文字列型で統一するか、明示的にCStrで変換
空文字列の扱い 業務要件に応じて、スキップ版と通常版を使い分け
エラーチェック 本番環境では、Safe版の使用を検討
関数名の命名 用途が分かりやすい名前を付ける
コメント記述 引数の意味と戻り値の形式を明記

地域設定への対応

日付や数値を文字列に変換する際は、地域設定に依存しない形式を使用します。

Sub RegionalSettings()
    Dim reportDate As Date
    Dim amount As Double
    Dim result As String
    
    reportDate = #11/18/2024#
    amount = 1234.56
    
    ' 非推奨: 地域設定に依存
    result = JoinUnderscore("Report", CStr(reportDate), CStr(amount))
    ' 結果: 地域設定によって異なる形式になる
    
    ' 推奨: Format関数で形式を明示
    result = JoinUnderscore( _
        "Report", _
        Format(reportDate, "yyyy-mm-dd"), _
        Format(amount, "0.00") _
    )
    Debug.Print result
    ' 結果: "Report_2024-11-18_1234.56" (常に同じ形式)
End Sub

Format関数を使用することで、どの環境でも一貫した結果を得られます。

関数のドキュメント化

関数の使い方を明確にするため、コメントでドキュメントを記述します。

'****************************************************************************
' 関数名: JoinUnderscore
' 
' 概要:
'   可変個の文字列をアンダースコア(_)で連結して返します。
'
' 引数:
'   args - 連結する文字列(可変長引数)
'          文字列型以外の値は自動的に文字列に変換されます
'
' 戻り値:
'   アンダースコアで連結された文字列
'
' 使用例:
'   result = JoinUnderscore("A", "B", "C")  ' 結果: "A_B_C"
'   result = JoinUnderscore("Report", 2025, "Q1")  ' 結果: "Report_2025_Q1"
'
' 注意事項:
'   - 引数が1つも渡されない場合、空文字列を返します
'   - Null値やオブジェクトを渡すと予期しない動作をする可能性があります
'   - 数値や日付は地域設定に依存した形式に変換されるため、
'     Format関数での事前変換を推奨します
'
' 作成日: ####/##/##
' 作成者: YourName
'****************************************************************************
Function JoinUnderscore(ParamArray args() As Variant) As String
    Dim i As Long
    Dim result As String
    
    For i = LBound(args) To UBound(args)
        If i = LBound(args) Then
            result = args(i)
        Else
            result = result & "_" & args(i)
        End If
    Next i
    
    JoinUnderscore = result
End Function

このようなドキュメントコメントを記述することで、以下のメリットがあります。

  • 他の開発者が関数の使い方を理解しやすい
  • 将来の自分が見返したときに思い出しやすい
  • 保守性が向上する
  • チーム開発での混乱を防げる

ユニットテストの作成

関数の動作を検証するためのテストコードを用意します。

Sub TestJoinUnderscore()
    Dim testResult As Boolean
    testResult = True
    
    ' テスト1: 基本的な連結
    If JoinUnderscore("A", "B", "C") <> "A_B_C" Then
        Debug.Print "テスト1失敗: 基本的な連結"
        testResult = False
    End If
    
    ' テスト2: 単一要素
    If JoinUnderscore("Single") <> "Single" Then
        Debug.Print "テスト2失敗: 単一要素"
        testResult = False
    End If
    
    ' テスト3: 数値の混在
    If JoinUnderscore("ID", 2024, 1001) <> "ID_2024_1001" Then
        Debug.Print "テスト3失敗: 数値の混在"
        testResult = False
    End If
    
    ' テスト4: 空文字列の混在
    If JoinUnderscore("A", "", "C") <> "A__C" Then
        Debug.Print "テスト4失敗: 空文字列の混在"
        testResult = False
    End If
    
    ' テスト5: 多数の要素
    If JoinUnderscore("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") <> _
       "1_2_3_4_5_6_7_8_9_10" Then
        Debug.Print "テスト5失敗: 多数の要素"
        testResult = False
    End If
    
    ' 結果表示
    If testResult Then
        Debug.Print "すべてのテストに合格しました。"
    Else
        Debug.Print "一部のテストが失敗しました。"
    End If
End Sub

テストコードを作成することで、関数の変更時に既存の動作が壊れていないか確認できます。

テストケースの適用範囲

このテストコードは、基本的なJoinUnderscore関数に対するテストです。

空文字列をスキップするバージョン(JoinUnderscoreSkipEmpty)や、大文字・小文字変換を行うバージョン(JoinUnderscoreUpperJoinUnderscoreLower)など、発展的な実装を行った場合は、それぞれの仕様に合わせてテストケースを修正する必要があります。

たとえば、JoinUnderscoreSkipEmptyの場合、テスト4は以下のように変更します。

' テスト4: 空文字列の混在(SkipEmptyバージョン)
If JoinUnderscoreSkipEmpty("A", "", "C") <> "A_C" Then
    Debug.Print "テスト4失敗: 空文字列の混在"
    testResult = False
End If

関数の仕様に応じて、期待される結果を正しく設定し、適切なテストを実施してください。

バージョン管理とメンテナンス

関数を修正する際は、バージョン情報をコメントに記録します。

'****************************************************************************
' 関数名: JoinUnderscore
' バージョン: 1.1
' 
' 変更履歴:
'   2024/11/18 v1.0 - 初版作成
'   2024/11/20 v1.1 - 空文字列チェックを追加
'
'****************************************************************************
Function JoinUnderscore(ParamArray args() As Variant) As String
    ' (関数の実装)
End Function

このような記録により、変更の経緯を追跡でき、問題発生時の原因特定が容易になります。

関数ライブラリとしての整理

複数のバリエーションを1つのモジュールにまとめて、関数ライブラリとして管理します。

'****************************************************************************
' モジュール名: StringJoinUtility
' 
' 概要:
'   文字列連結に関するユーティリティ関数群
'
' 含まれる関数:
'   - JoinUnderscore: アンダースコア連結(基本版)
'   - JoinWithDelimiter: 任意の区切り文字で連結
'   - JoinUnderscoreSkipEmpty: 空文字列をスキップして連結
'   - JoinWithWrapper: 前後に装飾を追加して連結
'   - JoinUnderscoreUpper: 大文字に統一して連結
'   - JoinUnderscoreLower: 小文字に統一して連結
'
' 作成日: 2025/11/18
'****************************************************************************

' (各関数の実装)

関数ライブラリとして整理することで、以下のメリットがあります。

  • 関連する関数をまとめて管理できる
  • 他のプロジェクトへの移植が容易
  • チーム内での共有がしやすい
  • メンテナンスが効率化される

まとめ

今回解説したParamArrayを活用した可変引数による文字列連結関数の実装テクニックは、「複数の文字列を特定の記号で連結する作業が頻繁に発生する」「連結する要素の個数が変動する場合に柔軟に対応したい」といった実務における課題を体系的かつ確実に解決する実用的な手法です。

この手法の核心となるのは、ParamArrayによる任意個数の引数受け取りと、最初の要素だけ特別扱いする効率的なループ処理設計、そして区切り文字を指定できる拡張実装という3つの実装パターンの使い分けです。実装時に特に重要なのは、LBound/UBoundを活用した配列境界の安全な処理と、空文字列スキップや大文字・小文字変換といった発展的機能の段階的な追加です。

通常の&演算子による直接連結では要素が増えるほど可読性が低下し、配列とJoin関数を使う方法では配列の事前宣言と要素の個別代入が煩雑になる問題に対し、ParamArrayとVariant型配列の組み合わせにより任意個数の文字列を直接列挙して渡せる柔軟性と、最初の要素を特別扱いすることで先頭にアンダースコアが付かない確実な連結処理により、ファイル名生成やシステム用識別子作成といった多様な業務シーンに対応したシンプルかつ強力な文字列連結システムを構築できます。

また、Null値やオブジェクト型のチェックによる堅牢なエラーハンドリング、Format関数を活用した地域設定に依存しない日付・数値変換、引数番号を明示したエラーメッセージといった実装パターンにより、予期しない動作を防ぎながら安定動作する発展的なツールとして成長させることが可能で、空文字列スキップ版や大文字・小文字統一版といったバリエーション実装との組み合わせにより、実務で安心して使える完成度の高い業務支援関数として活用できます。

次回は、Rangeオブジェクトの基礎から実践的な活用テクニックについて、初心に帰って掘り下げていく予定です!ぜひご期待ください!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?