こんにちは!マクロの記録を卒業し、「そろそろ本格的な業務システムをVBAで組んでみたい」と一歩を踏み出したあなたへ。
実務でMicrosoft Project(Project VBA)を使ったWBS(作業分解構成図)の自動化に挑むと、必ずと言っていいほどぶつかる壁があります。それが「タスクの依存関係(前提条件)」の管理です。
「タスクAが終わらないとタスクBが始まらない」というプレデター(先行タスク)とサクセッサ(後続タスク)の関係。これをプログラムで動的に組んでいくと、うっかり「タスクA → タスクB → タスクC → タスクA」のような無限ループ(循環参照)を作り出してしまい、プロジェクトファイルが盛大にクラッシュしたり、スケジュール計算が無限にフリーズしたりする悪夢を見ることになります。
今回は、この循環参照をグラフ理論の思考を使ってスマートに検知し、イミディエイトウィンドウに赤裸々なログとして出力する「デバッグツール」の作り方を解説します。
ここをクリアすれば、Project VBAの構造化とエラーハンドリングの基本はバッチリですよ。一緒に本質に迫っていきましょう!
—
なぜProject VBAで循環参照が起きるのか?
人間がGUIでガントチャートを操作しているときは、Projectの親切なエンジンが「おっと、それは循環参照になりますよ」と警告してくれます。
しかし、VBAから`.LinkPredecessors`メソッドや`Task.Predecessors.Add`を使って高速に大量の依存関係を流し込むと、Projectのエンジンが悲鳴を上げる前に、メモリ上で矛盾した網の目が完成してしまいます。
これを防ぐためには、VBA側でタスクのつながりを「有向グラフ(Directed Graph)」と見なし、コードを実行する前に「ループ(輪っか)ができていないか」を巡回探索(DFS:深さ優先探索など)で事前にチェックしてあげる必要があります。
—
グラフ理論のキホン:三色フラグ法(White-Gray-Black Algorithm)
循環参照を検知する最もエレガントなアルゴリズムに「三色フラグ法」があります。
タスクを探索する際、状態を3つに色分けします。
1. 未訪問(White): まだ一度も足を踏み入れていないタスク。
2. 探索中(Gray): 今まさに枝葉を探索している最中のタスク。もしこの「探索中」のタスクに再びぶつかったら、それは「循環参照(ループ)」の証明です。
3. 探索完了(Whiteを抜けて黒へ / Black): そのタスク以下の依存関係にループが無いことが確認された安全なタスク。
この概念を、Project VBAのオブジェクトモデルに落とし込んでいきましょう。
—
実装コード:循環参照検知デバッグツール
以下のコードを、Microsoft ProjectのVBAエディタ(Alt + F11)の標準モジュールに貼り付けてください。実務の現場でそのままコピペして検証できるように、詳細なコメントを添えています。
Option Explicit
‘ =====================================================================
‘ ブログ解説用:Project VBA 循環参照検知デバッグツール
‘ アーキテクト推奨パターン:グラフ理論に基づく安全網の実装
‘ =====================================================================
Public Sub RunCircularReferenceCheck()
Dim t As Task
Dim visited() As Integer ‘ 0:未訪問(White), 1:探索中(Gray), 2:探索完了(Black)
Dim maxID As Long
Dim hasCycle As Boolean
‘ プロジェクト内にタスクが存在するかチェック
If ActiveProject.Tasks.Count = 0 Then
MsgBox “プロジェクトにタスクが存在しません。”, vbExclamation
Exit Sub
End If
‘ プロジェクト内の最大タスクIDを取得し、配列のサイズを動的確保
‘ ※ProjectのIDは途中で欠番になることもあるため、Safeにループを回す準備
maxID = 0
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
If t.ID > maxID Then maxID = t.ID
End If
Next t
ReDim visited(1 To maxID)
hasCycle = False
Debug.Print “=== 循環参照チェック開始 ===”
‘ すべてのタスクを起点に深さ優先探索(DFS)を実施
For Each t In ActiveProject.Tasks
If Not t Is Nothing Then
‘ まだ訪問していないタスクであれば探索開始
If visited(t.ID) = 0 Then
If DFS_CheckCycle(t, visited) = True Then
hasCycle = True
‘ 1つ見つかったら全停止せず、ログを出し続ける設計にしています
End If
End If
End If
Next t
If hasCycle Then
Debug.Print “【警告】プロジェクト内に循環参照が検出されました!上記ログを確認してください。”
MsgBox “循環参照が検出されました。詳細はイミディエイトウィンドウを確認してください。”, vbCritical, “デバッグエラー”
Else
Debug.Print “【成功】循環参照は検出されませんでした。スケジュール構造は健全です。”
MsgBox “循環参照チェック完了:異常なし”, vbInformation, “デバッグ成功”
End If
Debug.Print “=== 循環参照チェック終了 ===”
End Sub
‘ ———————————————————————
‘ 内部関数:深さ優先探索(DFS)による再帰的チェック
‘ 戻り値:True = 循環あり, False = 正常
‘ ———————————————————————
Private Function DFS_CheckCycle(ByRef currentTask As Task, ByRef visited() As Integer) As Boolean
Dim depTask As Task
Dim link As Dependency
‘ 1. 現在のタスクを「探索中 (Gray: 1)」にマーク
visited(currentTask.ID) = 1
‘ 2. このタスクの「前提条件(Predecessors)」をすべてたどる
For Each link In currentTask.Predecessors
Set depTask = link.FromTask
‘ 念のためオブジェクトの生存確認
If Not depTask Is Nothing Then
‘ まだ訪問していないなら、さらに深く潜る
If visited(depTask.ID) = 0 Then
If DFS_CheckCycle(depTask, visited) = True Then
DFS_CheckCycle = True
Exit Function
End If
‘ 【重要】もし隣接するタスクが「探索中(Gray: 1)」だったらループ成立!
ElseIf visited(depTask.ID) = 1 Then
Debug.Print “【循環参照検知】 タスクID: ” & currentTask.ID & ” (” & currentTask.Name & _
“) が、先行タスクID: ” & depTask.ID & ” (” & depTask.Name & “) とループを形成しています。”
DFS_CheckCycle = True
Exit Function
End If
End If
Next link
‘ 3. すべての枝葉の探索が終わったら「探索完了 (Black: 2)」にマーク
visited(currentTask.ID) = 2
DFS_CheckCycle = False
End Function
—
コードのここがスゴい!プロの技術解説
このコードには、単なる「動くコード」を超えた、実務システムを支えるためのアーキテクチャ上のこだわりが詰まっています。
1. 配列による高速な状態管理(パフォーマンスの最適化)
VBAでオブジェクトのプロパティ(例: `t.Text1`など)にフラグを書き込もうとすると、ディスクやメモリへのアクセスが発生し、タスク数が数千件になった瞬間に処理が重くなります。
ここでは、メモリ上に作成した高速な整数型配列 `visited()` をタスクの `ID` をインデックスとして直引きしています。これにより、巨大なWBSであっても一瞬でスキャンが完了します。
2. 再帰呼び出し(Recursive Call)によるグラフ探索
`DFS_CheckCycle` 関数の中で、自分自身を呼び出しています。「タスクの先にあるタスクの、そのまた先…」という階層構造(ツリーやグラフ)を綺麗にコード化するには、再帰処理が最も強力な武器になります。
3. `ActiveProject.Tasks` のNULLチェック
Project VBAを使い始めると誰もがハマるのが、「削除されたタスクのIDの残骸や空きスロット」を踏んでしまうエラー(実行時エラー 91: オブジェクト変数または With ブロック変数が見つかりません)です。
`If Not t Is Nothing Then` というガード節を挟むことで、実務の現場で汚れてしまったプロジェクトデータでもクラッシュしない頑健性(ロバストネス)を持たせています。
—
初学者が陥りやすいエラーと対策
- 「オーバーフローしました」エラーが出る場合
- 原因: タスクの依存関係が深すぎて、再帰呼び出しのスタックが限界(スタックオーバーフロー)を超えた可能性があります。
- 対策: あまりにも巨大すぎるプロジェクトの場合は、再帰ではなく、キュー(Queue)構造を使った幅優先探索(BFS)への書き換えを検討してください(今回は可読性を重視して再帰を採用しています)。
- 何もログに出ないのに処理が止まる
- 原因: `Option Explicit` を書き忘れて変数のスペルミスがあり、意図しない無限ループに入っている可能性があります。
- 対策: すべてのモジュールの最上部に必ず `Option Explicit` を置き、変数の宣言を強制する癖をつけましょう。
—
おわりに:ここをクリアすれば、あなたはもう中級者!
今回は、グラフ理論を用いた循環参照検知という、少し高度で知的なテーマを扱いました。
マクロの記録をボタンに割り当てて喜んでいた段階から、「データを理論で解釈し、プログラムで安全性を担保する」というエンジニアの思考へシフトできた瞬間です。
このデバッグツールをあなたのWBS自動生成ツールの「実行前バリデーション」として組み込んでみてください。現場のエンジニアたちから「おっ、このマクロ、エラーチェックまで付いていて安全だな!」と一目置かれること間違いなしです。
あなたのVBAライフが、より知的でエキサイティングなものになりますように。次のステップへ、自信を持って進んでください!
