MS Project VBAの深淵:なぜ日付は勝手に動くのか?「制約なし」タスクの連動ロジックを解明する
こんにちは。プロジェクトの自動化に魂を捧げるエンジニアの皆さん。
マクロの記録から一歩踏み出し、「自分でコードを書いて制御したい」と思い始めたあなた。素晴らしい一歩です。
しかし、MS ProjectのVBAを触り始めると、誰もが一度は、ある「不可解な現象」に直面します。
「コードでタスクの開始日を指定したはずなのに、実行すると日付が勝手にズレている……」
「プロジェクトの開始日を変えたら、関係ないはずのタスクまで一斉に動いてしまった!」
これはバグではありません。MS Projectという「高度な計算エンジン」が、設計図通りに、極めて忠実に、そして「制約がないもの」を動かしている結果なのです。
今日は、初学者が必ず躓く「制約なし(As Soon As Possible)」タスクのデフォルト挙動と、プロジェクト開始日との連動ロジックについて、VBAを使ったシミュレーションを通じて解き明かしていきましょう。ここをクリアすれば、あなたの書くコードは「ただの日付代入」から「計算エンジンを操る制御」へと進化します。
1. MS Projectの正体は「日付の計算機」である
多くの人が誤解していますが、MS Projectは「Excelのような表計算ソフト」ではありません。
正体は、「制約条件(Constraint)に基づき、依存関係(Predecessors)を解いて、日付を導き出す計算エンジン」です。
タスクに何も制約を設定していない状態、これをエンジニア用語で言うと「ASAP(As Soon As Possible:可能な限り早く)」の状態と呼びます。
「制約なし」のルールとは?
制約が設定されていないタスクは、以下の2つの力によって日付が決まります。
1. プロジェクト開始日(Project Start Date):プロジェクトが始まらなければ、何も始まれない。
2. 先行タスク(Predecessors):前の作業が終わらなければ、次の作業は始められない。
この2つの力が衝突、あるいは連携して、タスクの開始日が「自動計算」されるのです。あなたがVBAで `Task.Start = “2023/10/01″` と書き込んでも、もしそのタスクに「先行タスク」があり、かつその先行タスクが「2023/10/05」に終わる設定であれば、エンジンはあなたの命令を上書きして「2023/10/06」へと日付を書き換えてしまいます。
2. VBAによる挙動シミュレーション
理屈だけでは分かりにくいので、実際にVBAで「プロジェクト開始日を変えると、制約なしタスクがどう連動するか」を実験してみましょう。
以下のコードは、新しいプロジェクトを作成し、2つのタスク(連動しているもの)を作成して、プロジェクト開始日の変更がどう影響するかをログに出力するものです。
Sub SimulateSchedulingLogic()
Dim pj As Project
Dim t1 As Task, t2 As Task
' 1. 新しいプロジェクトを作成(既存のプロジェクトを汚さないため)
Set pj = CreateProject
' プロジェクト全体の開始日を初期設定
pj.Start = DateValue("2023/10/01")
' 2. タスクの作成
' タスク1: 制約なし
Set t1 = pj.Tasks.Add("タスクA (先行)")
t1.Duration = "2d" ' 2日間
' タスク2: タスク1に依存(Finish-to-Start)
Set t2 = pj.Tasks.Add("タスクB (後続)")
t2.Duration = "3d"
t2.PredecessorLink = t1.ID ' タスク1のIDを先行タスクとして紐付け
' --- シミュレーション開始 ---
Debug.Print "--- シミュレーション開始 ---"
Debug.Print "【初期状態】"
Debug.Print "Project Start: " & pj.Start
Debug.Print "Task A Start: " & t1.Start ' プロジェクト開始日と連動
Debug.Print "Task B Start: " & t2.Start ' Task Aの終了後に連動
' 3. プロジェクト開始日を1週間(7日)後ろにずらす
Debug.Print ""
Debug.Print "【プロジェクト開始日を7日後に変更】"
pj.Start = pj.Start + 7
' 4. 結果の確認
' ここで、制約がないタスクがどう動いたかを見るのがポイントです
Debug.Print "New Project Start: " & pj.Start
Debug.Print "Task A Start: " & t1.Start ' プロジェクト開始日に引きずられて動く
Debug.Print "Task B Start: " & t2.Start ' Task Aの動きに連動して動く
' 5. 「制約」を与えた場合の挙動を比較
Debug.Print ""
Debug.Print "【Task Bに『開始日固定』の制約を追加】"
' ConstraintType 1 は "Start No Earlier Than" (開始前不可) などを指しますが、
' ここでは分かりやすく「Must Start On」的な挙動をシミュレートするために
' 直接制約タイプを操作します。
t2.ConstraintType = pjConstraintMustStartOn
t2.ConstraintDate = DateValue("2023/10/10")
Debug.Print "Task A Start: " & t1.Start
Debug.Print "Task B Start: " & t2.Start ' 制約があるため、Project Startの影響を受けない
Debug.Print "--- シミュレーション終了 ---"
End Sub
' プロジェクトをクリーンに作成するための補助関数
Function CreateProject() As Project
Dim pj As Project
Set pj = Application.Projects.Add
Set CreateProject = pj
End Function
コードの解説とポイント
1. `t2.PredecessorLink = t1.ID`:
ここが重要です。タスク間の「依存関係」を作っています。これにより、タスクAが動けばタスクBも自動的に動く「連動の鎖」が完成します。
2. `pj.Start = pj.Start + 7`:
プロジェクト全体の開始日を変更しています。制約がないタスク(`ConstraintType = pjConstraintAsSoonAsPossible`)は、この変更を検知して、まるで波紋のようにスケジュールを再計算します。
3. `t2.ConstraintType = pjConstraintMustStartOn`:
これが「制約」です。これを設定した瞬間、タスクBは「計算エンジンによる自動計算」の支配から脱し、指定された日付を守ろうとする「固定的な存在」に変わります。
3. 実務で陥りやすい「日付の罠」と回避策
このロジックを知らないままVBAを書くと、現場で以下のようなトラブルが頻発します。
罠①:VBAで日付をセットしたのに、直後に書き換わる
現象: `t1.Start = “2023/11/01″` と書いたのに、実行結果を見ると別の日になっている。
原因: そのタスクに先行タスクがあるか、プロジェクト開始日との整合性が取れないため、計算エンジンが「正しい日付」へ強制修正した。
対策:
- 日付を固定したい場合は、必ず `Task.ConstraintType` を設定してから日付を代入してください。
- もしくは、依存関係(Predecessor)の設計を見直しましょう。
罠②:一括更新によるスケジュール崩壊
現象: 大量のタスクの開始日をVBAで一斉に変更したら、プロジェクト全体の終了日が想定より数ヶ月先に飛んでしまった。
原因: 制約なしのタスクに対して、無理な日付を割り当てたことで、依存関係の連鎖(クリティカルパス)が極端に伸びてしまった。
対策:
- タスクの開始日を直接いじるのではなく、「先行タスクの終了日」や「ラグ(遅延)」を制御するアプローチを検討してください。
まとめ:エンジニアとしての視点
Project VBAをマスターするコツは、「プロパティ(値)を書き換えているのではなく、エンジンのパラメーター(条件)を操作している」という意識を持つことです。
- `Start` や `Finish` は「結果」である。
- `Predecessors` や `ConstraintType` は「原因」である。
「結果」を力技で書き換えようとするのではなく、「原因」を正しく制御する。これが、計算エンジンを支配するプロの書き方です。
ここをクリアすれば、あなたの作る自動化ツールは、単なる「入力補助」から、プロジェクトの動的な変化に耐えうる「強力なシミュレーター」へと進化しますよ。
さあ、次は依存関係をより複雑に制御する方法に挑戦してみましょう。応援しています!
