メタデータでコードを支配せよ:VB.NETにおけるカスタム属性とリフレクションの極致
「ハードコーディングされたバリデーションロジック」で、コードベースを汚染するのはもうやめよう。
業務自動化ツールの開発において、最も避けるべきは「仕様変更のたびにif文を書き換える」という地獄の保守作業だ。真の上級者は、ロジックをクラスの外側――すなわちメタデータ――に逃がす。
今回は、VB.NETの「カスタム属性(Custom Attributes)」を駆使し、リフレクションを通じて動的に振る舞いを制御する、プロフェッショナルな設計手法を伝授する。
—
1. なぜ「属性」で設計するのか
多くの開発者は、プロパティの値をチェックするために `If property.Value > 100 Then …` といったコードをあちこちに散らばらせる。だが、これが100箇所あったらどうなるか? 修正漏れによるバグは必至だ。
属性(Attribute)を使えば、対象のプロパティに「これは100以下であるべき」という宣言を付与するだけで済む。ロジックの本体は、リフレクションを用いて実行時に一度だけ記述すればよい。これが「DRY原則(Don’t Repeat Yourself)」の極致だ。
—
2. カスタム属性の実装:設計の肝
まずは、バリデーションのためのメタデータを定義する。ポイントは `AttributeUsage` 属性で、この属性がどこに適用できるかを厳密に制限することだ。
Imports System
‘ 属性の適用範囲をプロパティのみに制限し、継承を許可する
Public Class RangeValidationAttribute
Inherits Attribute
Public Property MinValue As Integer
Public Property MaxValue As Integer
Public Sub New(min As Integer, max As Integer)
Me.MinValue = min
Me.MaxValue = max
End Sub
End Class
—
3. リフレクションによるメタデータ駆動型エンジン
次に、この属性を読み取り、検証を行うエンジンを作成する。ここで注意すべきはパフォーマンスだ。リフレクションは高コストな処理であるため、プロダクションコードでは必ず「キャッシュ」を考慮しなければならない。
Imports System.Reflection
Public Class Validator
‘ リフレクションのパフォーマンス低下を防ぐための簡易キャッシュ
Private Shared _cache As New Collections.Generic.Dictionary(Of Type, PropertyInfo())
Public Shared Function Validate(obj As Object) As Boolean
Dim t As Type = obj.GetType()
Dim props As PropertyInfo()
‘ キャッシュがあれば再利用、なければ取得
If Not _cache.ContainsKey(t) Then
_cache(t) = t.GetProperties()
End If
props = _cache(t)
For Each prop In props
‘ 属性を取得
Dim attr = prop.GetCustomAttribute(Of RangeValidationAttribute)()
If attr IsNot Nothing Then
Dim val = CInt(prop.GetValue(obj))
‘ 検証ロジック
If val < attr.MinValue OrElse val > attr.MaxValue Then
Throw New Exception($”{prop.Name} が範囲外です: {val}”)
End If
End If
Next
Return True
End Function
End Class
—
4. 実装:スマートなデータモデル
これを使う側のコードは、驚くほどクリーンになる。
Public Class UserData
Public Property Age As Integer
Public Property Salary As Integer
End Class
‘ 実行例
Dim user As New UserData With {.Age = 25, .Salary = 500000}
Try
Validator.Validate(user)
Console.WriteLine(“検証完了”)
Catch ex As Exception
Console.WriteLine($”エラー: {ex.Message}”)
End Try
—
5. 現場で生き残るための「鉄則」
この手法を実務で採用する際、以下の3点だけは肝に銘じておいてほしい。
1. リフレクションのキャッシュは必須: 上記例のように `Dictionary` を使用し、型情報を保持せよ。ループ内で毎回 `GetProperties()` を呼ぶようなコードは、データ件数が増えた瞬間にツールを崩壊させる。
2. 型安全性の確保: `GetValue` で取得した値が、期待する型と一致するかどうかは常に気にする必要がある。必要であれば `TryCast` を活用し、クラッシュしない設計を目指せ。
3. データベース連携時の注意: このバリデーションはあくまで「アプリケーション層」のガードだ。DBの制約(Not Nullや文字数制限)と、属性によるバリデーションの整合性をドキュメント化しておくことが、チーム開発における「無言の信頼」を築く。
最後に
メタデータ駆動型プログラミングは、コードの「記述量」を減らすことではなく、コードの「依存関係」を整理することにある。
VB.NETは古臭い言語だという揶揄があるが、リフレクションと属性を理解したエンジニアの手にかかれば、極めて強力な自動化エンジンへと化ける。さあ、今すぐ退屈なif文の羅列を捨て、このアーキテクチャで「変更に強いシステム」を構築してほしい。
健闘を祈る。
