📊連載第19回！初心者のためのExcel VBA入門：ユーティリティ関数って何？🤔 エラーに強いコードを書くための便利関数活用法💡

# Excel VBAにおけるユーティリティ関数の作成と活用テクニック

私はVBAの活用経験を通じて得た知識を整理し、共有する目的で記事を作成しているプログラミング歴1年半になるエンジニアです。前回は、Excel VBAにおけるEnum型（列挙型）を活用した関数設計について詳しく説明しました。今回は、VBAでよく使用する処理をFunction化して、コードの重複を減らし保守性を高める実践的なユーティリティ関数の作成テクニックについて解説します。

- [第1回: Excel VBAの基礎知識とセキュリティ設定](https://qiita.com/CodeLeaf/items/2d3e3185a725480fecc3)
- [第2回: Excel VBAの基本操作とオブジェクトの理解](https://qiita.com/CodeLeaf/items/c65d2531908aaf53649d)
- [第3回: Excel VBAにおける変数と定数の基本](https://qiita.com/CodeLeaf/items/3b36eddf7b5a8b5c5d99)
- [第4回: Excel VBAにおけるシート操作の基本とエラー処理](https://qiita.com/CodeLeaf/items/573fb7c61a756aa123ea)
- [第5回: Excel VBAにおける条件分岐](https://qiita.com/CodeLeaf/items/8be52d682baa097a65c1)
- [第6回: Excel VBAにおける繰り返し処理の基本](https://qiita.com/CodeLeaf/items/5bc6c961a090e550bca9)
- [第7回: Excel VBAにおける配列とFor Eachの活用](https://qiita.com/CodeLeaf/items/bdd8d488e49086219663)
- [第8回: Excel VBAにおけるFormulaとValueの使い分けとユーザー入力の取得](https://qiita.com/CodeLeaf/items/ecf312a53f7ede91bce9)
- [第9回: Excel VBAにおけるファイル操作とフォルダ管理の基本](https://qiita.com/CodeLeaf/items/e5520321e760364f6aae)
- [第10回: Excel VBAにおけるFileSystemObjectを活用した高度なファイル操作](https://qiita.com/CodeLeaf/items/a78e9c7dd2b7e68f1d9e)
- [第11回: Excel VBAにおけるFileSystemObjectを活用した高度なファイル操作 応用編](https://qiita.com/CodeLeaf/items/4607db0151dddfb7f458)
- [第12回: Excel VBAにおけるStrConv関数の活用と応用テクニック](https://qiita.com/CodeLeaf/items/4f6358548bced6c047e0)
- [第13回: Excel VBAにおけるワークブックの安全な操作と管理テクニック](https://qiita.com/CodeLeaf/items/cf136898dd41c7dd5d9f)
- [第14回: Excel VBAにおけるFunction（関数）の作成と活用テクニック](https://qiita.com/CodeLeaf/items/a299f465bdcb1957e2be)
- [第15回: Excel VBAにおける配列を返す関数の作成と活用テクニック](https://qiita.com/CodeLeaf/items/711a93ac86b5c4150e13)
- [第16回: Excel VBAにおけるコレクションの活用と応用テクニック](https://qiita.com/CodeLeaf/items/6a28c65d5f8d19964664)
- [第17回: Excel VBAにおける辞書型（Dictionary）の活用と応用テクニック](https://qiita.com/CodeLeaf/items/5f85b2029e8d51eaa788)
- [第18回: Excel VBAにおけるEnum型を活用した関数設計と実装テクニック](https://qiita.com/CodeLeaf/items/3e91ef9556799d96aeea)
- 第19回: Excel VBAにおけるユーティリティ関数の作成と活用テクニック（本記事）
- [第20回: Excel VBAにおける正規表現を活用したファイル名解析テクニック](https://qiita.com/CodeLeaf/items/86284498dee42aab731e)


## 目次

- [はじめに](#はじめに)
- [ユーティリティ関数とは](#ユーティリティ関数とは)
- [エラー処理を内包したユーティリティ関数の設計](#エラー処理を内包したユーティリティ関数の設計)
- [実践的なユーティリティ関数の作成例](#実践的なユーティリティ関数の作成例)
- [ユーティリティ関数活用時の注意点](#ユーティリティ関数活用時の注意点)
- [まとめ](#まとめ)

## はじめに

プログラムを書いていると、同じような処理を何度も繰り返し書くことが多くなります。例えば、データを検索する処理、セルの個数を数える処理、シートが存在するかチェックする処理など、基本的でありながら頻繁に使用される処理です。これらの処理を毎回一から記述するのは非効率的であり、コードの重複や保守性の悪化を招きます。

今回紹介する「ユーティリティ関数」は、このような問題を解決する手法です。よく使用する処理を関数として一つにまとめ、エラーが起きても安全に動作するように作ることで、プログラム全体の品質を向上させることができます。特に、Excelのワークシート関数（MatchやCOUNTIFなど）をVBAから使用する際にエラーが発生しやすいため、これらのエラーを適切に処理することが重要になります。

## ユーティリティ関数とは

ユーティリティ関数（Utility Function）とは、プログラムの中でよく使用される便利な処理をまとめた関数のことです。「ユーティリティ」は「便利な道具」という意味で、プログラムを書く時の便利な道具として使える関数という意味です。

### ユーティリティ関数の特徴とメリット

**再利用性とコードの整理:** 一度作成すれば、プロジェクト内のどこからでも呼び出すことができます。同じ処理を複数箇所で書く必要がなくなり、全体のコード量が削減されます。また、処理の変更が必要な場合も、一箇所の修正で済むため保守性が向上します。

**エラー処理の統一:** エラーが発生しやすい処理を関数内で安全に処理することで、メインのプログラムではエラーを気にすることなく処理を実行できます。予期しないエラーによるプログラムの停止を防ぎ、アプリケーションの安定性が向上します。

**設計の明確化:** 一つの関数は一つの明確な役割を持つように設計します。これにより、コードの意図が理解しやすくなり、新しいメンバーがプロジェクトに参加した場合も、コードの理解が容易になります。

**テストとデバッグの効率化:** 個別の関数として切り出すことで、単体テストが行いやすくなり、問題が発生した場合も原因の特定が容易になります。同じ入力に対しては常に同じ出力を返すため、関数の動作が予測しやすくなります。

**統一された結果の判定:** 成功・失敗を判断しやすい戻り値の設計を行います。呼び出し側で結果を適切に判断できるよう、統一されたルールに従った戻り値を提供します。

## エラー処理を内包したユーティリティ関数の設計

ユーティリティ関数を設計する際の最も重要なポイントは、適切なエラー処理の実装です。VBAでは、ワークシート関数を使用する際や、データ型の変換処理において頻繁にエラーが発生するため、これらのエラーを適切に処理する必要があります。

### エラー処理設計の基本方針

**エラーの検出と分類**: 発生する可能性のあるエラーを事前に分類し、それぞれに適した処理を準備します。VBAでよく発生するエラーには、範囲外アクセス（存在しないセルやシートへのアクセス）、型の不一致（数値として扱えない文字列の処理）、ファイルアクセスエラーなどがあります。

**適切な戻り値の設計**: エラーが発生した場合とそうでない場合を明確に区別できる戻り値を設計します。数値を返す関数では負値や0を、文字列を返す関数では空文字列を、オブジェクトを返す関数ではNothingを返すなど、一貫したルールを適用します。

**エラー情報の提供**: エラーが発生した場合、呼び出し側でエラーの内容を把握できるよう、参照渡しのパラメーターやグローバル変数を使用してエラー情報を提供します。これにより、デバッグ時の問題特定が容易になります。

**リソースの適切な解放**: エラーが発生した場合でも、開いたファイルやオブジェクトなどのリソースが適切に解放されるよう、適切な場所での初期化処理を行います。

### エラー情報の詳細管理とグローバル変数の活用

ユーティリティ関数をより実用的にするために、エラー情報を詳細に管理する仕組みを整備することが重要です。どの関数でどのようなエラーが発生したかを記録しておくことで、問題が起きた時の原因特定が容易になります。

#### グローバル変数によるエラー情報管理

エラー情報を全体で管理するために、モジュール上部でグローバル変数を定義します。この変数により、どの関数でエラーが発生したかを後から確認することができます。

```vb
' モジュール上部で定義するグローバル変数
Public g_ErrorList As String  ' 発生したエラーの一覧を蓄積する変数
```

#### 参照渡しによるエラー情報の受け渡し

関数の引数として、エラー情報を格納する文字列変数を参照渡し（ByRef）で定義することで、関数内で発生したエラーの詳細を呼び出し側で受け取ることができます。

```vb
' 参照渡しでエラー情報を受け取る関数の例
Function GetSafeIndexWithError(searchValue As Variant, searchRange As Variant, _
                                                   ByRef errorList As String) As Long
    On Error GoTo ErrorHandler
    
    ' Match関数で検索実行
    GetSafeIndexWithError = Application.WorksheetFunction.Match(searchValue, searchRange, 0)
    Exit Function
    
ErrorHandler:
    ' エラーメッセージの蓄積処理
    If errorList <> "" Then
        errorList = errorList & vbCrLf  ' 既存のエラーがある場合は改行を追加
    End If
    
    ' 詳細なエラー情報を追加
    errorList = errorList & "GetSafeIndexWithError: 値 「" & searchValue & _
        "」が見つかりません（エラー番号: " & Err.Number & "）"
    
    ' グローバル変数を使用する場合の例
    g_ErrorList = g_ErrorList  & "GetSafeIndexWithError: 値 「" & searchValue & _
            "」が見つかりません（エラー番号: " & Err.Number & "）"
    
    ' エラー時の戻り値を設定
    GetSafeIndexWithError = 0
    On Error GoTo 0
End Function
```

#### エラー情報管理のポイント

エラー情報を効果的に管理するために、以下の点に注意しましょう。

- **エラー情報の標準化**: エラーメッセージの形式を統一することで、ログの読みやすさが向上します。関数名、エラー番号、エラー詳細を含む一貫した形式を採用します。

- **改行文字の適切な使用**: 複数のエラーを蓄積する場合は、vbCrLf（改行文字）を使用してエラーメッセージを区切ります。これにより、後でエラーログを確認する際に読みやすくなります。

- **関数名の記録**: どの関数でエラーが発生したかを明確にするため、エラーメッセージには関数名を含めます。特に複数の関数が連携する処理では、問題の発生箇所を特定するために重要です。

- **グローバル変数の初期化**: プログラムの開始時には、グローバルエラー変数を初期化することで、前回の処理のエラー情報が混在することを防ぎます。

```vb
g_ErrorList = ""     ' 最終エラーをクリア
```

## 実践的なユーティリティ関数の作成例

以下の例では、エラー処理を簡潔に記述していますが、実際の開発では前述の「エラー情報の詳細管理とグローバル変数の活用」で説明した詳細なエラー処理（エラーメッセージの蓄積、グローバル変数への記録など）を`ErrorHandler:`部分に実装することを推奨します。

### 例1: 安全な検索機能を提供するGetSafeIndex関数

最初に紹介するのは、Match関数を使用した検索処理を安全に行うユーティリティ関数です。通常、Match関数は検索対象が見つからない場合にエラーを発生させますが、この関数ではエラーを内部で処理し、呼び出し側に安全な戻り値を提供します。

```vb
' 検索値に該当する位置を安全に取得する関数
' searchValue: 検索対象の値
' searchRange: 検索範囲（配列またはRange）
' errorList: エラー情報を格納する文字列（参照渡し）
' 戻り値: 見つかった場合は位置（1から開始）、見つからない場合は0
Function GetSafeIndex(searchValue As Variant, searchRange As Variant) As Long
    On Error GoTo ErrorHandler
    
    ' Match関数で検索実行
    GetSafeIndex = Application.WorksheetFunction.Match(searchValue, searchRange, 0)
    Exit Function
    
ErrorHandler:
    ' エラーが発生した場合は0を返す
    GetSafeIndex = 0    
    On Error GoTo 0
End Function
```

この関数の重要なポイントは、検索対象が見つからない場合でもプログラムが停止せず、0という明確な値を返すことです。呼び出し側では、戻り値が0より大きいかどうかで検索成功を判断できます。

:::note info
#### Match関数について
Match関数は、指定した値を範囲内で検索し、その位置を返すワークシート関数です。第3引数に0を指定すると完全一致検索を行います。値が見つからない場合、通常はエラーが発生しますが、この関数ではそのエラーを適切に処理しています。
:::

### 例2: 条件一致数を安全に取得するSafeCountIf関数

COUNTIF関数は条件に一致するセルの個数を数える便利な関数ですが、範囲が不正な場合や条件が複雑な場合にエラーが発生することがあります。以下のユーティリティ関数は、このような問題を解決します。

```vb
' 条件に一致するセルの個数を安全に取得する関数
' targetRange: 検索対象の範囲
' criteria: 検索条件
' errorInfo: エラー情報を格納する文字列（参照渡し）
' 戻り値: 一致した個数（エラー時は-1）
Function SafeCountIf(targetRange As Variant, criteria As Variant) As Long
    On Error GoTo ErrorHandler
    
    ' 引数の検証
    If IsEmpty(targetRange) Then
        SafeCountIf = -1
        Exit Function
    End If
    
    ' COUNTIF関数の実行
    SafeCountIf = Application.WorksheetFunction.CountIf(targetRange, criteria)
    Exit Function
    
ErrorHandler:
    ' エラー発生時は-1を返す
    SafeCountIf = -1
    On Error GoTo 0
End Function
```

:::note info
#### IsEmpty関数について
IsEmpty関数は、変数やセルが空の状態（未初期化）かどうかを判定するVBAの組み込み関数です。Variant型の変数に対して使用し、何も値が設定されていない場合にTrueを返します。

IsEmpty関数が特に重要な理由は、単純に値がない状態と、空文字列("")や0といった「値はあるが空に見える」状態を区別できる点です。例えば、セルに何も入力されていない場合はIsEmptyでTrueになりますが、セルに空文字列が入力されている場合はFalseになります。

ユーティリティ関数では、引数として渡された範囲やデータが有効かどうかを事前にチェックする際によく使用されます。無効なデータに対して処理を実行すると予期しないエラーが発生する可能性があるため、IsEmptyによる事前チェックは安全なプログラム作成において重要な要素です。
:::


この関数では、エラーが発生した場合に-1を返すことで、正常な結果（0以上）と区別できるようにしています。呼び出し側では、戻り値が0以上の場合のみ有効な結果として扱います。

### 例3: シート存在確認を行うSheetExists関数

指定したワークブックに特定の名前のシートが存在するかどうかを確認する処理は、Excel VBAでよく使用される機能です。以下の関数は、シートが存在しない場合でもエラーを発生させずに、安全に存在確認を行います。

```vb
' 指定したワークブックに特定のシートが存在するかを確認する関数
' wbTarget: 確認対象のワークブック
' sheetName: 検索するシート名
' 戻り値: シートが存在する場合はTrue、存在しない場合はFalse
Function SheetExists(ByVal wbTarget As Workbook, ByVal sheetName As String) As Boolean
    On Error GoTo ErrorHandler
    
    ' 引数の検証（ワークブックがNothingでないかチェック）
    If wbTarget Is Nothing Then
        SheetExists = False
        Exit Function
    End If
    
    ' シート名でアクセスを試行（存在しない場合はエラーが発生）
    Dim testSheet As Worksheet
    Set testSheet = wbTarget.Worksheets(sheetName)
    
    ' エラーが発生しなかった場合はシートが存在
    SheetExists = True
    Exit Function
    
ErrorHandler:
    ' エラーが発生した場合はシートが存在しない
    SheetExists = False
    On Error GoTo 0
End Function
```

この関数では、シートが存在しない場合に発生するエラーを利用して存在確認を行っています。エラーが発生しなければシートが存在し、エラーが発生すればシートが存在しないと判断できます。

:::note info
#### ワークシートオブジェクトのアクセス方法
VBAでは、`Worksheets(シート名)`でシートにアクセスできますが、指定したシートが存在しない場合は実行時エラーが発生します。この特性を利用して、エラーハンドリングによる存在確認を行っています。
:::

### 例4: 罫線設定を行うSetBorders関数

罫線を設定する処理は、よく使用される基本的な操作です。通常は複数の罫線位置に対して個別に設定を記述する必要がありますが、以下のサブルーチン（値を返さない処理のまとめ）を使用することで、配列を使って複数の罫線を一度に設定できるため、コードの記述量を削減できます。

```vb
' 指定された範囲に罫線を設定するサブルーチン
' borderRange: 罫線を設定するRange範囲
' borderIndices: 罫線を設定する位置の配列（xlEdgeTop, xlEdgeBottomなど）
' lineStyle: 線のスタイル（xlContinuous, xlDotなど）
' weight: 線の太さ（xlMedium, xlThinなど）
Sub SetBorders(ByVal borderRange As Range, borderIndices As Variant, _
                       lineStyle As XlLineStyle, weight As XlBorderWeight)
               
    On Error GoTo ErrorHandler
        
    ' 罫線位置配列が有効かチェック
    If IsEmpty(borderIndices) Then
        Exit Sub
    End If
    
    ' 各罫線位置に対して処理を実行
    Dim borderIndex As Variant
    For Each borderIndex In borderIndices
    
        ' 罫線の設定を実行
        With borderRange.Borders(borderIndex)
            .LineStyle = lineStyle  ' 線のスタイルを設定
            .weight = weight        ' 線の太さを設定
        End With
        
    Next borderIndex
    
    Exit Sub
    
ErrorHandler:
    ' エラー処理（設定に失敗した場合は処理を終了）
    On Error GoTo 0
End Sub
```

このサブルーチンでは、複数の罫線位置を配列で指定できるため、一度の呼び出しで複数の罫線を設定できます。例えば、`Array(xlEdgeTop, xlEdgeBottom)` のように配列を渡すことで、上下の罫線を同時に設定できます。

:::note info
罫線の位置指定について
ExcelVBAでは、xlEdgeTop（上側）、xlEdgeBottom（下側）、xlEdgeLeft（左側）、xlEdgeRight（右側）などの定数を使用して罫線の位置を指定します。これらを配列として渡すことで、複数の位置に同時に罫線を設定できます。
:::

## ユーティリティ関数活用時の注意点

### 設計時の注意点

**過度な汎用化を避ける**: ユーティリティ関数を作成する際は、実際の使用場面を想定した適切な抽象化レベルを保つことが重要です。あまりにも汎用的にしすぎると、かえって使いにくくなる場合があります。

**関数の数を適切に管理**: 関数の数が増えすぎると管理が困難になるため、本当に必要な処理のみを関数化することを心がけましょう。同じような処理が3回以上出現する場合に関数化を検討するのが一つの目安です。

**オプション引数の適切な使用**: オプション引数には適切なデフォルト値を設定し、最も一般的な使用パターンをサポートします。これにより、関数の使いやすさが向上し、呼び出し側のコードもシンプルになります。

### パフォーマンスへの配慮

**早期リターンの実装**: 条件チェックで処理が不要と判明した場合は、早めに関数を終了させることで無駄な処理を避けます。特に、引数の妥当性チェックは関数の最初で行い、問題がある場合はすぐにリターンします。

**リソース管理**: オブジェクトの作成や解放を適切に行い、メモリリークを防止します。特に、Excelオブジェクトを使用する場合は、使用後の適切な解放が重要です。

### エラー処理の統一

**エラー情報の形式統一**: エラーメッセージや戻り値の形式を統一し、呼び出し側で一貫した処理ができるようにします。例えば、「エラー時は負値を返す」「エラー時は空文字列を返す」など、明確なルールを決めて統一します。

**ログ出力の統一**: 重要なエラーや処理結果については、統一された形式でログを出力し、運用時の問題分析を容易にします。Debug.Printを使用したデバッグ情報の出力も、形式を統一することで効率的な開発が可能になります。



## まとめ

Excel VBAにおけるユーティリティ関数は、よく使用する処理を関数として一元化し、エラー処理を内包することで、コードの重複を減らし保守性を高める非常に重要な設計手法です。この記事では、ユーティリティ関数の基本概念から始まり、エラー処理を内包した関数設計の原則、グローバル変数や参照渡しを活用したエラー情報管理、そして実践的な関数作成例（安全な検索機能、条件一致数の取得、シート存在確認、罫線設定）を解説しました。これらの知識を身につけることで、エラーが発生しても安全に動作する高品質なツール開発が可能になります。

もし記事の内容で不明な点や、より詳しく知りたい部分がありましたら、コメントでお知らせください。また、実務でのユーティリティ関数活用例や、より効率的な実装方法など、皆様のノウハウもぜひ共有していただければ幸いです。

次回の記事では、正規表現を活用してファイル名から特定のパターンを抽出するテクニックについて解説します。正規表現の基本的な記法から始まり、VBAでの正規表現オブジェクトの使用方法、実際のファイル名解析の実装例を紹介し、より柔軟で強力な文字列処理を実現する方法を習得できます。ぜひご期待ください！