Project VBAを掌握する極限の知見:スキルタグ駆動型・要員自動抽出検索エンジンの実装
Microsoft Project VBAにおけるリソース管理は、多くの開発者が挫折する領域の一つである。標準の「リソース プール」や「コスト管理」の機能は、単一プロジェクトの枠内であれば機能するものの、複数プロジェクトが錯綜する企業環境や、動的なスキル要件を伴うアジャイル・マトリックス型組織においては、その硬直したデータ構造ゆえに完全に破綻する。
特に「タスクに必要なスキルと稼働状況を突き合わせ、最適な要員をミリ秒単位で抽出する」という要件を純粋なProjectのUI操作や標準マクロで実現しようとすると、COMオブジェクトの過剰なラウンドトリップ(往復通信)によって極端なパフォーマンス劣化を引き起こす。
本稿では、レガシーなProject VBAの制約を極限まで突破し、カスタムフィールドに格納された「スキルタグ」と「稼働率(Availability)」をインメモリ上で高速演算する、要員自動抽出検索エンジンのアーキテクチャを解説する。
—
1. アーキテクチャの設計思想:なぜ標準機能では不可能なのか
Projectの `Resource` オブジェクトおよび `Assignment` オブジェクトは、内部的にCOM境界を跨ぐため、VBAからプロパティやメソッドへ無秩序にアクセスすると、COMのマーシャリングコストによって実行時間が幾何学的に増大する。
さらに、スキル管理をテキストベースのカスタムフィールド(`Text1`〜`Text30`など)で行う場合、以下の課題に直面する。
- 複数のスキルタグ(例: `Python, Azure, VBA`)のあいまい検索・完全一致検索の遅延
- タスク期間中のリソースの「実効稼働余力(Over-allocationの回避)」の動的計算漏れ
- オブジェクトの解放漏れによるExcel/Projectプロセス間でのメモリリーク
これらを解決するため、本エンジンでは以下の設計アプローチを採用する。
1. 一括メモリキャッシング: リソースの基本属性、コスト、およびカスタムフィールドのスキルタグを、VBA実行開始時に二次元配列(Variant配列)へ一括ロードする。
2. ビット演算/部分一致のインメモリ評価: 配列上で高速にフィルタリングを行い、COMへのアクセスを極限まで排除する。
3. 安全なCOMライフサイクル管理: 参照したオブジェクトは厳密なスコープ管理と明示的な `Nothing` 代入により、メモリプレッシャーをゼロにする。
—
2. 実装コード:スキルタグ検索エンジンの心臓部
以下に、指定されたスキルキーワードとタスク期間の空き状況を照合し、条件に合致するリソースをイミディエイトウィンドウに列挙する実用的なVBAモジュールを示す。
Option Explicit
‘ =========================================================================
‘ 模块名: ModResourceSearchEngine
‘ 概要: カスタムフィールドのスキルタグと稼働状況を照合する高速検索エンジン
‘ 著作権: チーフアーキテクト専用知見コード
‘ =========================================================================
Type ResourceProfile
ID As Long
Name As String
MaxUnits As Double
Skills As String
CostPerUse As Currency
StandardRate As Double
End Type
Public Sub ExecuteResourceMatchingEngine()
Dim tStart As Double
tStart = Timer
On Error GoTo ErrorHandler
‘ 1. 検索条件の定義(本来はユーザーフォームやタスクのカスタムフィールドから動的取得)
Const REQUIRED_SKILL As String = “Azure”
Dim taskStart As Date
Dim taskEnd As Date
taskStart = Date
taskEnd = DateAdd(“d”, 30, Date)
‘ 2. Projectのアプリケーション・ドキュメントコンテキストの確立
Dim prj As Project
Set prj = ActiveProject
Dim resCount As Long
resCount = prj.Resources.Count
If resCount = 0 Then
MsgBox “リソースが登録されていません。”, vbExclamation
Exit Sub
End If
‘ 3. COMラウンドトリップを排除するためのメモリキャッシング
‘ 毎回Projectオブジェクトを叩くのを防ぐため、配列にデータを一撃で吸い上げる
Dim resArray() As ResourceProfile
ReDim resArray(1 To resCount)
Dim r As Resource
Dim idx As Long
idx = 0
For Each r In prj.Resources
If Not r Is Nothing Then
‘ 削除済みリソースやコストリソース等の例外処理
If r.Type <> pjResourceTypeCost Then
idx = idx + 1
resArray(idx).ID = r.ID
resArray(idx).Name = r.Name
resArray(idx).MaxUnits = r.MaxUnits
‘ Text1 をスキルタグ格納フィールドとして想定
resArray(idx).Skills = CStr(r.Text1)
resArray(idx).StandardRate = r.StandardRate
End If
End If
Next r
‘ 実際の有効リソース数にリサイズ
If idx > 0 Then
ReDim Preserve resArray(1 To idx)
Else
MsgBox “有効なマンパワーリソースが存在しません。”, vbExclamation
GoTo CleanUp
End If
‘ 4. インメモリ・フィルタリングエンジン実行
Debug.Print “=== スキル検索結果 [ 検索キーワード: ” & REQUIRED_SKILL & ” ] ===”
Dim i As Long
Dim matchCount As Long
matchCount = 0
For i = LBound(resArray) To UBound(resArray)
‘ スキルタグの包含チェック(カンマ区切り、大文字小文字を区別しない簡易パーース)
If InStr(1, resArray(i).Skills, REQUIRED_SKILL, vbTextCompare) > 0 Then
‘ 稼働率(Availability)の検証:期間内でのオーバーアロケーションチェック
If CheckResourceAvailability(prj, resArray(i).ID, taskStart, taskEnd) Then
matchCount = matchCount + 1
Debug.Print “候補ID: ” & resArray(i).ID & _
” | 氏名: ” & resArray(i).Name & _
” | スキル: ” & resArray(i).Skills & _
” | 標準単価: ” & resArray(i).StandardRate & “円/h”
End If
End If
Next i
Debug.Print “— 検索完了 (ヒット数: ” & matchCount & “名 / 処理時間: ” & Format(Timer – tStart, “0.000”) & “秒) —
CleanUp:
‘ 5. 厳密なオブジェクト解放(メモリリーク防止)
Set r = Nothing
Set prj = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp
End Sub
Private Function CheckResourceAvailability(ByRef prj As Project, ByVal resID As Long, ByVal startDt As Date, ByVal endDt As Date) As Boolean
‘ 極限のパフォーマンスを維持するため、アサインメントの負荷を検証
‘ ※本サンプルでは簡略化として常にTrueを返すスタブ構造(実運用ではここにWork/Availabilityの差分計算を実装)
Dim targetRes As Resource
Set targetRes = prj.Resources.UniqueID(resID)
If targetRes Is Nothing Then
CheckResourceAvailability = False
Exit Function
End If
‘ 稼働率が0%でないか、期間中の割り当て超過がないかを判定
If targetRes.MaxUnits > 0 Then
CheckResourceAvailability = True
Else
CheckResourceAvailability = False
End If
Set targetRes = Nothing
End Function
—
3. シニアエンジニアが押さえるべき極限の最適化ポイント
A. COMマーシャリングの最小化
`For Each r In prj.Resources` のループ内で直接プロパティ(`r.Text1` や `r.StandardRate`)を評価し続けるコードは、プロジェクトの規模が大きくなると(数千リソースを超える場合)致命的な遅延を招く。上記のコードのように、一度構造体配列(`ResourceProfile`)へデータを集約し、VBAのネイティブメモリ空間内でループを回すことで、処理速度を 最大で数十倍 に跳ね上げることが可能だ。
B. オブジェクトのライフサイクル管理とメモリプレッシャー
VBAのランタイムはガベージコレクションの挙動がブラックボックス化している。特にMicrosoft ProjectのCOMオブジェクトは、参照カウントが適切にデクリメントされないと、VBA実行終了後もプロセスがメモリ上に残存し(ゾンビプロセス)、ExcelやProject全体の不安定化を招く。
コード内で生成した `Resource` や `Project` などのオブジェクト参照は、例外処理(`On Error GoTo`)の経路を含め、必ず最後に `Set xxx = Nothing` で明示的に解放しなければならない。
C. レガシー環境とWindows API連携の拡張性
大規模組織におけるProject VBAの運用では、Active Directory(LDAP)から最新のスキルセットや組織改編データを自動同期する要件がつきまとう。
このようなシステム間連携を行う場合、VBA単体でのHTTP通信(MSXML2など)はセキュリティポリシーやパフォーマンスの面で限界を迎える。その場合は、Windows API(`kernel32` のメモリ操作系APIや、COMコンポーネントとしての .NET Framework製DLL)を呼び出すラッパーを設計し、VBAはあくまで「UIとProjectドキュメントのインターフェース」に徹するアーキテクチャが最も堅牢となる。
—
結び
Project VBAは、正しくアーキテクチャを設計し、メモリとCOMの挙動を完全に制御下に置くことで、市販の高価なリソース管理SaaSに匹敵するカスタムエンジンへと昇華させることができる。
「動けばいい」という妥協を捨て、極限まで無駄を削ぎ落としたコードベースこそが、真のシステム管理者およびチーフアーキテクトの武器となる。
