【5分でVBA学習】使えると便利なFunctionプロシージャ ～Excelフィルタリングツール コード解説 #3～

# 1. はじめに
ＡＲアドバンストテクノロジ株式会社（ＡＲＩ）の鈴木タクヤです。

__[前回](https://qiita.com/TakuyaSuzuki/items/cbb8f2e32cc0294f53fe "【5分でVBA学習】避けては通れないSubプロシージャ ～Excelフィルタリングツール コード解説 #2～")__ から少し時間が空きましたが、育児と仕事と法事と __[資格の勉強](https://qiita.com/TakuyaSuzuki/items/4296a3af2a6b5184062f "【資格試験を受ける方へ】不合格体験記のすゝめ")__ で忙しかったためです。・・・遊んだりもしたけれど。

https://qiita.com/TakuyaSuzuki/items/4296a3af2a6b5184062f

んなこたぁ置いといて。

今回はExcelフィルタリングツールのソースコード解説第3回です。

VBA初心者だった私が __"今の開発組織でトライした"__（←この記事の参加イベント）、業務効率向上ツール作成。せっかく作ったツールを広めるべく、またVBA初心者は実際に動くモノがあった方が勉強しやすいと考え始めた、ツールの導入方法の紹介と、解説形式でのVBA学習用記事シリーズです。（記事執筆も __トライ__ 中。）

（ツールの詳細については以下の記事を、「Excelフィルタリングツール解説シリーズ」タグの記事一覧については以下の記事下部「5. 解説記事のリンク」をご参照ください。）

https://qiita.com/TakuyaSuzuki/items/75789a1cf0b3a7c31c0b

__[解説第2回](https://qiita.com/TakuyaSuzuki/items/cbb8f2e32cc0294f53fe "【5分でVBA学習】避けては通れないSubプロシージャ ～Excelフィルタリングツール コード解説 #2～")__ はSubプロシージャについての記事でしたが、第3回のテーマはそれと似て非なる「Functionプロシージャ」です。

# 2. Functionプロシージャとは
Functionプロシージャとは、「Function」と「End Function」で挟まれた部分のことです。Subプロシージャと同様に、この中にマクロの処理内容を記述します。


Excelフィルタリングツールにおいては、
`isOpened(ByVal bookName As String) As Boolean`
`setFile(ByVal fullPath As String) As Workbook`
`checkFormat(ByVal cellReference As String) As String` 
という3つのFunctionプロシージャが存在します。Subプロシージャと同様に、括弧の前までがFunctionプロシージャ名で、括弧の中はFunctionプロシージャを呼び出す際の __引数（ひきすう）__ です。また括弧の右のAsの後ろは __戻り値__ の __データ型__ です。戻り値の設定は、Functionプロシージャ内で __「Functionプロシージャ名 = 戻り値」__ のように記述します。
```vb:Functionプロシージャの書き方
Function functionName(引数) As 戻り値のデータ型
    ～何かしらの処理～
    （戻り値はプロシージャ内で「functionName = 戻り値」の形式で記述）
End Function
```

上のFunctionプロシージャを別のFunctionプロシージャやSubプロシージャから呼び出すときは、以下のように記述できます。（Subプロシージャの呼び出し方と同様）
```vb:Functionプロシージャの呼び出し方
Call functionName(引数)
```
___Call___ は省略可能ですが、その場合は引数を括弧で囲みません。また、実際にはFunctionプロシージャの処理結果である戻り値を使用することが多く、その場合は ___Call___ を使用せず、以下の例のようになります。

```vb:Excelフィルタリングツールより抜粋
'既に対象ブックを開いているか確認する
    If isOpened(targetBookName) = True Then
        Set targetBook = Workbooks(targetBookName)
    Else
        '対象ブックの存在確認＆設定
        Set targetBook = setFile(fullPath)
    End If
```
・・・どれだよ？！と思われたかと思いますが、`isOpened(targetBookName)` `setFile(fullPath)` の部分です（処理内容は後述）。`Workbooks(targetBookName)`も似ていますが、これはWorkbooksプロパティと呼ばれるもので、ややこしいので一旦スルーでお願いします。括弧内で指定した名前のExcelワークブックを表すものだと思っておけばOKです。

::: note
__引数__：
プログラムの中で処理を呼び出す際に渡す値のこと。

__戻り値__：
プログラムの中で関数等の処理結果として呼び出し元に返す値のこと。

__データ型__：
変数に入れるデータの種類。一覧については以下のページを参照。初心者はとりあえず、 __整数は「Long」、小数は「Double」、文字は「String」、真偽値（○か×か）は「Boolean」__ あたりから触れていけば良いかと。VBAにおいてはVariant型というなんでも入る型があるのであまり意識せずとも動くプログラムが書けます。
:::
https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/data-type-summary

Subプロシージャとの大きな違いは、 __「Functionプロシージャは戻り値を返す」__ ということです。「あれやっておいて～」と処理を呼んで、やりっぱなしなのがSub、結果を返してくれるのがFunctionというイメージです。
__Functionプロシージャを使いこなせれば、好きな関数を作成してすることができて非常に便利です。__

:::note warn
プログラミング言語によっては戻り値のない一連の処理を「プロシージャ」、戻り値のある一連の処理を「関数（ファンクション）」と呼び分けるものもありますが、VBAにおいてはどちらも「プロシージャ」と呼ぶのが一般的なようです。
:::
__[参考：「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典【関数（function）】](https://wa3.i-3-i.info/word1905.html)__
 
# 3. ExcelフィルタリングツールにおけるFunctionプロシージャの役割
## 3-1 isOpened
`isOpened(ByVal bookName As String) As Boolean`
ブック（Excelファイル）の名前を表すString型の文字列を渡すと、そのブックが開かれている（True）か否（False）かをBoolean型で返します。
（汎用的かと思うので、使えそうならコピペしてください。）
```vb:Excelフィルタリングツールより抜粋：流し見してください
'■指定のブックを開いているか否かを判定する関数
Function isOpened(ByVal bookName As String) As Boolean
    isOpened = False
    Dim wb As Workbook
    isOpened = False　　　　　　 '👈戻り値にFalse（偽）を設定
    
    For Each wb In Workbooks
        If wb.Name = bookName Then
            isOpened = True　　　'👈引数で指定したブックが開かれていたら戻り値にTrue（真）を設定
            Exit For
        End If
    Next
    
    Set wb = Nothing
End Function
```

## 3-2 setFile
`setFile(ByVal fullPath As String) As Workbook`
ブックのフルパス（String型）を引数として渡すと、そのファイルが存在するか確認します。有ればワークブックを開いてWorkbook型の戻り値を返し、無ければメッセージボックスで"ファイル「（引数で渡したフルパス）」が存在しません。"と表示し、 __[第2回](https://qiita.com/TakuyaSuzuki/items/cbb8f2e32cc0294f53fe "【5分でVBA学習】避けては通れないSubプロシージャ ～Excelフィルタリングツール コード解説 #2～")__ で紹介したSubプロシージャ「endMacro」を呼び出します。
```vb:Excelフィルタリングツールより抜粋：流し見してください
'■対象ファイル存在確認/設定関数（渡されたfullPathのファイルが存在すればそれを返し、存在しなければエラーメッセージを表示する。）
Function setFile(ByVal fullPath As String) As Workbook
    Dim fileChecker As Object
    Set fileChecker = CreateObject("Scripting.FileSystemObject")
    
    If fileChecker.FileExists(fullPath) Then
        Set setFile = Workbooks.Open(fullPath)　　'👈戻り値のオブジェクトを設定
    Else
        MsgBox "ファイル「" & fullPath & "」が存在しません。"
        Call endMacro
    End If
    
    Set fileChecker = Nothing
End Function
```
## 3-3 checkFormat
`checkFormat(ByVal cellReference As String) As String`
「A1」「AB28」といったセル番地（String型）を引数として渡します。その文字列がセル番地として有効か否かを判定し、OKであればそれを半角大文字に変換した文字列（String型）を戻り値として返します。NGであれば  __[第2回](https://qiita.com/TakuyaSuzuki/items/cbb8f2e32cc0294f53fe "【5分でVBA学習】避けては通れないSubプロシージャ ～Excelフィルタリングツール コード解説 #2～")__ で紹介したSubプロシージャ「showFormatErrorMessage」を呼び出します。
（入力されたセル番地のエラーをハンドリングをするにしても、もっと上手いやり方がありそうですが、半分遊びで作成しました。）
```vb:Excelフィルタリングツールより抜粋：流し見してください（長い） 
'■セル番地のフォーマットチェック関数
Function checkFormat(ByVal cellReference As String) As String
    '全て半角大文字に置換
    cellReference = UCase(StrConv(cellReference, vbNarrow))
    '文字数を変数に代入
    Dim wordCount As Integer
    wordCount = Len(cellReference)
    
    '文字列が「英字部分・数字部分」の構成になっていることを確認
    If Not cellReference Like "[A-Z]*" Then             '先頭が英字であることを確認
        Call showFormatErrorMessage(1)
    End If
    
    If Not cellReference Like "*[0-9]" Then             '末尾が数字であることを確認
        Call showFormatErrorMessage(wordCount)
    End If
    
    Dim startingPositionOfRowNo As Integer              '数字の開始位置が先頭から何文字目かを格納する変数を定義
    Dim i As Integer, j As Integer                      'ループ処理用変数を定義
    '2文字目以降は1文字ずつ英字か数字か確認
    For i = 2 To wordCount
        If Mid(cellReference, i, 1) Like "[0-9]" Then
            startingPositionOfRowNo = i                 '数字部分（行番号）開始が先頭から何文字目かを保持
            Exit For
        ElseIf Not Mid(cellReference, i, 1) Like "[A-Z]" Then   '数字じゃなくて英字でもなければエラー
            Call showFormatErrorMessage(i)
        End If
    Next i
    
    '数字部分（行番号）の2文字目以降が（存在すれば）全て数字であることを確認
    j = startingPositionOfRowNo + 1
    Do While j <= wordCount
        If Not Mid(cellReference, j, 1) Like "[0-9]" Then       '数字以外だったらエラー
            Call showFormatErrorMessage(j)
        End If
        
        j = j + 1
    Loop
    
    '英字部分（列番号）が上限（XFD）以下であることを確認する
    Dim columnNo As String
    columnNo = Left(cellReference, startingPositionOfRowNo - 1)
    
    '英字部分が4桁以上の場合はエラー
    If startingPositionOfRowNo >= 5 Then
        Call showFormatErrorMessage(4)
    End If
    
    '英字部分が3桁の場合の整合性チェック
    If startingPositionOfRowNo = 4 Then
        If (Mid(cellReference, 1, 1) Like "[Y-Z]") _
            Or (Mid(cellReference, 1, 1) = "X" And Mid(cellReference, 2, 1) Like "[G-Z]") _
            Or (Mid(cellReference, 1, 1) = "X" And Mid(cellReference, 2, 1) = "F" And Mid(cellReference, 3, 1) Like "[E-Z]") _
            Then
            
            Call showFormatErrorMessage(3)
        End If
    End If
    
    '数字部分（行番号）が上限（1048576）以下であることを確認する
    Dim rowNo As Long
    rowNo = CLng(Right(cellReference, wordCount - startingPositionOfRowNo + 1))
    
    If rowNo > 1048576 Then
        Call showFormatErrorMessage(wordCount)
    End If
    
    checkFormat = cellReference        '👈確認済みの文字列を戻り値に設定
End Function
```
# 4. 引数の渡し方について
ここまで、引数については「___ByVal___ 引数名 _As_ 引数のデータ型」という書き方をしてきましたが、___ByVal___ ではなく ___ByRef___ を指定することもできます。これらは引数の渡し方を意味し、 ___ByVal___ は「値渡し」、 ___ByRef___ は「参照渡し」です。
___ByVal___ の場合は __元のデータのコピー__ を引数として渡すので、渡された側の処理でデータが変更されたとしても渡した側のデータは変更されません。
一方、___ByRef___ の場合は __元のデータ自体__ を引数として渡すので、渡された側でデータが変更されると、渡した側のデータも変更されます。

__[参考：「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典【「ByVal」と「ByRef」の違い】](https://wa3.i-3-i.info/diff985programming.html)__

:::note
`Function checkFormat(ByVal cellReference As String) As String`
の戻り値は、値渡しの引数を半角大文字に変換したものなので、
`Sub checkFormat(ByRef cellReference As String)`
と参照渡しをしても良いということです。
（但し、呼び出し元の記述は少し変える必要があります。）
:::

# 5. おわりに
以上、Functionプロシージャの解説でした。
「5分で」と謳っておきながら結構ボリューミー（死語？）になってしまいました・・・。FunctionはSubと異なりソースコードに必須ではないですが、使えると便利です。

次回は、今回登場した引数・戻り値とも関連のある __変数__ について解説します。次回の更新も期間があいてしまったらすみません。気長にお待ちください。

👇次回

https://qiita.com/TakuyaSuzuki/items/4402237239e338368257

# 6. 参考文献
（今回は解説中にも参考リンクを挟み込んでいますが、それ以外で。）
こちらはMicrosoftの公式ドキュメントです。

https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/function-statement

https://learn.microsoft.com/ja-jp/office/vba/language/concepts/getting-started/writing-a-function-procedure

こちらは私がものすごくお世話になっている「エクセルの神髄」という解説サイト。より深く学習を進めたい方は是非。

https://excel-ubara.com/excelvba1/EXCELVBA406.html