【入門編】前提条件の循環参照を検知してログ出力するデバッグツール – Project VBA解析バイブル

スポンサーリンク

こんにちは!マクロの記録を卒業し、「そろそろ本格的な業務システムを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ライフが、より知的でエキサイティングなものになりますように。次のステップへ、自信を持って進んでください!

タイトルとURLをコピーしました