VBA関数の仮引数で暗黙の型変換を避ける
VBAで共通関数を作るとき、仮引数を Long や Double にしておけば安全に見えます。しかし実際には、呼び出し時点でVBAが暗黙の型変換を行うため、入力の誤りが関数の入口で見えなくなることがあります。この記事では、VBAの関数設計で「どこで型を確定させるか」を整理します。
Copyright © 2026 LWP 山中 一弘
本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。
記事要約
VBAの仮引数に具体的な型を指定すると、関数に入る前に暗黙の型変換が行われることがあります。これは便利な反面、呼び出し元が渡した値の状態を関数内で診断できなくする原因になります。
共通関数やライブラリ関数では、仮引数をいったん Variant で受け取り、関数内で検査してから CLng、CDbl、CStr などの明示的な変換を行う設計が有効です。大事なのは、単に「Variantを使う」ことではなく、入力の検査、変換、エラー化の責任を関数側に集めることです。
本記事の対象とゴール
この記事は、VBAで業務用の共通関数、計算関数、入力検査関数、TakiLibのような再利用ライブラリを作る人を対象にしています。
読み終えた時点で、次の判断ができることをゴールにします。
仮引数に具体型を指定したとき、暗黙の型変換がどこで起きるかを説明できる
共通関数で Variant を使う理由を「何でも受けるため」ではなく「診断するため」として説明できる
値型、オブジェクト型、配列、Rangeなどで仮引数の考え方を分けられる
ByVal Variant と ByRef Variant の違いを、設計上のリスクとして意識できる
先に結論
VBAの共通関数では、仮引数を必ず具体型にすれば安全、とは言えません。
たとえば数値を足す関数で、仮引数を Double にしておくと、一見すると数値だけを受け取る関数に見えます。しかし呼び出し元が文字列 "3.5" を渡した場合でも、VBAが関数呼び出し時に Double へ変換できれば、そのまま関数内へ入ってきます。
つまり、関数内では「もともと数値だったのか」「文字列だったが変換されたのか」を見分けにくくなります。
入力の誤りを診断したい関数では、入口で Variant として受け、値の種類を調べ、許可する条件を満たすときだけ明示的に変換する方が、原因を説明できる関数になります。
1. 仮引数の型指定は入口の防波堤ではない
VBAでは、次のような関数を書くことがあります。
Public Function AddNumber(ByVal leftValue As Double, ByVal rightValue As Double) As Double AddNumber = leftValue + rightValueEnd Functionこの関数は Double を2つ受け取るため、数値以外は入ってこないように見えます。
しかし、VBAの呼び出しでは、渡された値が仮引数の型へ変換可能な場合、関数に入る前に暗黙の型変換が行われます。次のような呼び出しでも、変換可能なら通ってしまいます。
Debug.Print AddNumber("3.5", "2")この動き自体はVBAの機能です。問題は、共通関数や業務ロジックの境界で、呼び出し元の値がどういう状態だったのかを失いやすいことです。
2. 「ゴミが入ったらゴミが出る」では業務関数として弱い
業務で使う関数では、誤った入力が来たときに、単におかしな結果や実行時エラーを出すだけでは足りないことがあります。
大事なのは、次のどちらの設計にするかです。
ゴミが入ったら、どこかでゴミの結果が出る
ゴミが入ったら、入口で診断して止める
共通関数は後者に向いています。なぜなら、その関数は複数の呼び出し元から使われるため、入口で値の種類や許容条件を見ておくほど、後工程の調査が楽になるからです。
3. Variantは「何でも許す型」ではなく「診断を遅らせない型」
Variant は、雑に使うと危険です。なんでも受けて、なんとなく計算する関数になってしまうからです。
一方で、共通関数の入口では Variant が役に立ちます。理由は、値が関数に入る前に勝手に具体型へ丸められることを防ぎ、関数内で値の状態を確認できるからです。
たとえば、次のように設計します。
Public Function AddNumberStrict(ByVal leftValue As Variant, ByVal rightValue As Variant) As Double If Not IsNumeric(leftValue) Then Err.Raise 13, , "leftValue は数値として扱える値ではありません。" End If If Not IsNumeric(rightValue) Then Err.Raise 13, , "rightValue は数値として扱える値ではありません。" End If AddNumberStrict = CDbl(leftValue) + CDbl(rightValue)End Functionこの関数でも、最終的には CDbl で数値に変換しています。ただし、変換のタイミングを関数の中へ移しています。
これにより、関数は「何を受け取ったか」「どの条件で許可したか」「どこで変換したか」を自分で管理できます。
4. IsNumericだけで十分とは限らない
上の例では説明を単純にするため IsNumeric を使いました。しかし実務では、IsNumeric だけで十分とは限りません。
たとえば、次の観点があります。
空文字を許すのか
Null を許すのか
Empty をゼロ扱いするのか
日付として解釈できる値を数値扱いしてよいのか
通貨、小数、整数のどこまでを許すのか
カンマ付き文字列を許すのか
セルのエラー値をどう扱うのか
つまり、検査とは「変換できるか」だけではありません。「この関数の仕様として受け入れてよいか」を決めることです。
共通関数を作るときは、Variant で受けたあとに、関数の目的に合わせて判定を分けます。
5. 明示的な型変換は仕様を表す
VBAには、CInt、CLng、CDbl、CStr、CBool、CDate などの型変換関数があります。
暗黙の型変換に任せると、どこで変換されたのかが読み取りにくくなります。一方で、明示的な型変換を書くと、その行が仕様の境界になります。
Dim amount As Doubleamount = CDbl(inputValue)このコードは、「ここで inputValue を Double として確定する」という意思を表します。
業務コードでは、このような境界が重要です。金額、数量、日付、コード値のように、誤変換がそのまま業務ミスにつながる値では、変換の位置を見えるようにしておくべきです。
6. 返り値は具体型でよいことが多い
仮引数では Variant が有効な場面がありますが、返り値まで常に Variant にする必要はありません。
たとえば、数値計算の結果を返す関数なら、返り値は Double や Long でよいことが多いです。
Public Function TaxIncludedAmount(ByVal baseAmount As Variant, ByVal taxRate As Variant) As Long If Not IsNumeric(baseAmount) Then Err.Raise 13, , "baseAmount は数値ではありません。" End If If Not IsNumeric(taxRate) Then Err.Raise 13, , "taxRate は数値ではありません。" End If TaxIncludedAmount = CLng(CDbl(baseAmount) * (1 + CDbl(taxRate)))End Function入口では診断のために Variant を使い、出口では仕様として具体型を返しています。
これは、「入力は疑う。出力は約束する」という考え方です。
7. オブジェクト型は具体型で受ける
ここまでの話は、主に数値や文字列のような値型の話です。
一方で、Worksheet、Workbook、Range、Dictionary、Collection などのオブジェクトを受け取る場合は、基本的には具体型で受ける方が読みやすく、安全です。
Public Sub FillHeader(ByVal targetRange As Range) targetRange.Value = "見出し"End Subこの場合、関数が必要としているのは「何らかの値」ではなく、Range オブジェクトです。Variant にしてしまうと、オブジェクトであること、どのオブジェクトを期待しているのかが見えにくくなります。
ただし、Excel VBAではセルやRangeに既定プロパティが関係する場面があります。Range オブジェクトそのものを扱っているのか、Range.Value を扱っているのかは、関数の入口で明確に分けてください。
8. ByVal VariantとByRef Variantは同じではない
Variant を使うときは、ByVal と ByRef の違いも重要です。
値を診断して変換するだけの関数では、まず ByVal Variant を基本にします。
Public Function NormalizeText(ByVal value As Variant) As String If IsError(value) Then Err.Raise 13, , "セルエラー値は文字列化できません。" End If NormalizeText = Trim$(CStr(value))End FunctionByVal にしておけば、呼び出し元の変数を書き換える意図がないことが読み手に伝わります。
一方で、ライブラリ内部では ByRef Variant が必要になる場面もあります。たとえば、呼び出し元の変数そのもの、配列、オブジェクト参照、未初期化状態を含めて扱いたい場合です。
ただし ByRef Variant は、呼び出し元への影響が大きくなります。共通関数で使うなら、なぜ ByRef が必要なのかを関数名、コメント、仕様で明確にするべきです。
9. すべての関数で厳密にする必要はない
ここまで読むと、すべてのVBA関数で Variant にして厳密検査を入れるべきだと思うかもしれません。しかし、それもやりすぎです。
画面のイベントプロシージャ、短いローカル処理、一度しか使わない小さな補助関数では、仮引数を具体型にして簡潔に書く方がよい場面もあります。
厳密にするべきなのは、主に次のような関数です。
複数の場所から呼ばれる共通関数
金額、数量、日付など業務上の影響が大きい値を扱う関数
入力値の不正を早めに説明したい関数
ライブラリとして長く使う関数
他人が使う可能性のある関数
逆に、処理範囲が狭く、呼び出し元と呼び出し先を同時に読める関数では、具体型の引数で十分なこともあります。
10. 設計のチェックリスト
VBAで関数の仮引数を決めるときは、次の順に考えると整理しやすくなります。
この関数は共通関数か、局所的な補助関数か
入力値の誤りを関数内で診断したいか
暗黙の型変換で元の値の状態が失われると困るか
値型を受け取るのか、オブジェクトを受け取るのか
変換できることと、仕様上許可することを分けているか
変換の行がコード上で明示されているか
呼び出し元を書き換える必要が本当にあるか
このチェックを通すと、Variant を使うべき場面と、具体型を使うべき場面が見えやすくなります。
まとめ
VBAの仮引数に具体型を書くことは、必ずしも入力の安全性を保証しません。呼び出し時に暗黙の型変換が行われるため、関数内に入った時点では元の値の状態が見えにくくなることがあります。
共通関数では、Variant で受けて、値を調べ、仕様として許可できる場合だけ明示的に変換する設計が有効です。
Variant は、雑に使うための型ではありません。入力を診断し、変換の責任を関数の中に置くための型です。
値型は必要に応じて Variant で受ける。オブジェクト型は具体型で受ける。返り値は仕様として具体型にする。この切り分けを意識すると、VBAの共通関数は読みやすく、原因追跡しやすくなります。
出典メモ
元記事: VBA 関数の仮引数はどうあるべきか。暗黙の型変換をどうやって回避するか - Togetter
元URL: https://togetter.com/li/1830562
移行先URL: https://posfie.com/@hoehoe1234/p/oTXM1kH
取得日: 2026-05-24
参照: Microsoft Learn「Type conversion functions」
参照: Microsoft Learn「VarType function」
参照: Microsoft Learn「IsNumeric function」
