【実務・中級編】実務中級:Excelの「制約条件マスタ」からProjectへ情報を同期するデータ連携ツール – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

制約条件の同期を「ただのループ」で終わらせるな:ExcelマスタからProjectへ魂を吹き込む設計論

多くの開発者が、ExcelからMS Projectへのデータ連携を「セルを読み取って、Taskオブジェクトのプロパティに代入するだけの単純なループ処理」だと誤解している。

もし君が、`For i = 2 To LastRow` と書き始め、その中で `Task.Start = Cells(i, 1)` といったコードを並べているのなら、今すぐそのキーボードから手を離すべきだ。それは「ツール」ではない。いつか必ずデータ不整合を引き起こし、スケジュールの整合性を破壊する「時限爆弾」だ。

本稿では、Excelで管理された「制約条件マスタ」をMS Projectの計算エンジンへ、極めて堅牢かつ高速に同期させるための、プロフェッショナルなアーキテクチャを伝授する。

1. なぜ、あなたの同期ツールは「壊れる」のか

実務レベルの自動化において、直面する壁は「ロジックの正しさ」ではなく、「状態の不整合」と「計算負荷」である。

制約条件(Constraint)の罠

MS Projectにおける制約条件(ASAP, ALAP, Start No Earlier Than等)は、単なる日付の固定ではない。それはProjectのスケジューリング・エンジンに対する命令である。
例えば、`Start No Earlier Than`(開始前指定)を設定した瞬間、Projectはクリティカルパスの再計算を開始する。ループ内で一つずつ制約を書き換えるたびに、エンジンがフル稼働して再計算を繰り返す……これが、処理が極端に遅くなる最大の原因だ。

データ型と列挙型の乖離

Excel上の「ASAP」という文字列と、Project VBAの `pjStartNoEarlierThan` という定数。この間にある「解釈のギャップ」を適切にハンドリングしない設計は、バグの温床となる。

2. 堅牢な設計のための3つの鉄則

プロフェッショナルなエンジニアとして、以下の設計原則をコードに組み込む。

① 抽象化されたマッピング層の構築

Excelの「文字列」を直接Projectの「定数」に変換してはいけない。必ず、中間層(Mapping Layer)を設け、`Enum`(列挙型)を用いて型安全性を確保せよ。

② 計算エンジンの制御(Calculation Control)

同期処理の間、Projectの計算モードを `pjManual` に切り替えるのは最低限のマナーだ。全ての同期が完了した後に、一括で `Calculate` を実行する。これがパフォーマンスを劇的に変える。

③ 辞書オブジェクトによる検索の高速化

Taskの名前やIDでProjectのタスクを探す際、毎回 `Project.Tasks(“TaskName”)` を呼び出すのは、非効率の極みだ。ProjectのTaskオブジェクトを `Scripting.Dictionary` にキャッシュし、メモリ上で照合せよ。

3. プロダクション・コード:制約条件同期エンジン

以下に、実務でそのまま利用可能な、堅牢性を極限まで高めたコード例を示す。このコードは、Excelの「制約条件マスタ」を読み込み、Projectのタスクへ反映させる。

前提条件

  • Excelシートの構成:
  • A列: `Task ID` (Projectの固有ID)
  • B列: `Constraint Type` (ASAP, ALAP, StartNoEarlier, etc…)
  • C列: `Constraint Date` (制約日付)
  • MS Projectへの参照設定(Microsoft Project Object Library)が必要。
Option Explicit

' --- 1. 制約条件のマッピング用列挙型 ---
' Excel上の文字列とProjectの定数を結びつけるための抽象化レイヤー
Enum ExcelConstraintType
    EX_ASAP = 0
    EX_ALAP = 1
    EX_START_NO_EARLIER = 2
    EX_START_NO_LATER = 3
    EX_FINISH_NO_EARLIER = 4
    EX_FINISH_NO_LATER = 5
    EX_UNKNOWN = 99
End Enum

' --- メイン処理 ---
Public Sub SynchronizeConstraintsFromExcel()
    Dim msProject As MSProject.Application
    Dim targetWb As Workbook
    Dim wsMaster As Worksheet
    Dim dictTasks As Object ' Scripting.Dictionary
    Dim lastRow As Long, i As Long
    
    ' 設定
    Set targetWb = ThisWorkbook
    Set wsMaster = targetWb.Sheets("ConstraintMaster")
    
    ' Projectへの接続
    On Error Resume Next
    Set msProject = GetObject(, "MSProject.Application")
    If msProject Is Nothing Then
        MsgBox "MS Projectが起動していません。", vbCritical
        Exit Sub
    End If
    On Error GoTo ErrorHandler

    ' 高速化のための準備
    msProject.Calculation = pjManual ' 計算をマニュアルに切り替え
    
    ' 1. ProjectのタスクをDictionaryにキャッシュ (検索の高速化)
    ' Key: TaskID (Long), Value: Task Object
    Set dictTasks = CreateObject("Scripting.Dictionary")
    Dim t As MSProject.Task
    For Each t In msProject.ActiveProject.Tasks
        If Not t Is Nothing Then
            If dictTasks.Exists(t.ID) Then
                ' 重複回避(通常は起こらないが堅牢性のために)
            Else
                dictTasks.Add t.ID, t
            End If
        End If
    Next t

    ' 2. Excelデータの読み込みと同期
    lastRow = wsMaster.Cells(wsMaster.Rows.Count, "A").End(xlUp).Row
    
    Dim taskId As Long
    Dim constraintStr As String
    Dim constraintDate As Variant
    Dim targetTask As MSProject.Task
    Dim projConstraint As MSProject.PJType
    Dim projDate As Date

    For i = 2 To lastRow ' ヘッダーを除いて2行目から
        taskId = wsMaster.Cells(i, 1).Value
        constraintStr = wsMaster.Cells(i, 2).Value
        constraintDate = wsMaster.Cells(i, 3).Value
        
        ' タスクの存在確認
        If dictTasks.Exists(taskId) Then
            Set targetTask = dictTasks(taskId)
            
            ' 制約タイプの変換
            projConstraint = MapExcelConstraintToProject(constraintStr)
            
            ' 制約日付の処理(ASAP/ALAPの場合は日付の扱いが特殊なため注意)
            If IsDate(constraintDate) Then
                projDate = CDate(constraintDate)
            Else
                projDate = Date ' デフォルト
            End If

            ' --- 同期実行 ---
            ' 制約タイプがASAP(None)の場合、日付設定は意味をなさないためスキップ
            If projConstraint = pjConstraintNone Then
                targetTask.ConstraintType = pjConstraintNone
            Else
                targetTask.ConstraintType = projConstraint
                targetTask.ConstraintDate = projDate
            End If
        Else
            ' ログ出力(実務ではエラーログシートへ書き出すのが定石)
            Debug.Print "Warning: Task ID " & taskId & " not found in Project."
        End If
    Next i

    ' 3. 後処理
    msProject.Calculate ' 全ての同期完了後に一括計算
    msProject.Calculation = pjAutomatic ' 計算モードを自動に戻す
    
    MsgBox "同期が完了しました。", vbInformation
    Exit Sub

ErrorHandler:
    msProject.Calculation = pjAutomatic
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub

' --- 2. マッピングロジック (単一責任の原則) ---
' 文字列からProjectの定数へ変換する。ここを修正するだけで拡張が可能。
Private Function MapExcelConstraintToProject(ByVal excelStr As String) As MSProject.PJType
    Select Case UCase(Trim(excelStr))
        Case "ASAP"
            MapExcelConstraintToProject = pjConstraintNone
        Case "ALAP"
            ' Projectには直接のALAP定数はないため、論理的にFinish No Later Than等で代用
            ' ここでは実務的な解釈に基づき実装
            MapExcelConstraintToProject = pjFinishNoLaterThan 
        Case "START_NO_EARLIER"
            MapExcelConstraintToProject = pjStartNoEarlierThan
        Case "START_NO_LATER"
            MapExcelConstraintToProject = pjStartNoLaterThan
        Case "FINISH_NO_EARLIER"
            MapExcelConstraintToProject = pjFinishNoEarlierThan
        Case "FINISH_NO_LATER"
            MapExcelConstraintToProject = pjFinishNoLaterThan
        Case Else
            MapExcelConstraintToProject = pjConstraintNone
    End Select
End Function

4. アーキテクトの視点:保守性を高めるためのアドバイス

「ASAP/ALAP」の定義を疑え

MS Projectの内部モデルには、厳密な意味での「ASAP(制約なし)」と「ALAP(完了遅延可能)」という定数は存在しない。

  • ASAP は `pjConstraintNone` である。
  • ALAP を実現するには、タスクの「終了前指定(Finish No Later Than)」を、そのタスクの本来の終了予定日に設定するなどのロジックが必要になる。

この「概念の差」をコード内の `MapExcelConstraintToProject` 関数でどう解釈するか。ここがエンジニアの腕の見せ所だ。

エラーハンドリングの「質」

`On Error Resume Next` を乱用してはいけない。
実務ツールにおいて重要なのは、「どこで、どのデータが、なぜ失敗したのか」をユーザーに即座に伝えることだ。上記のコードでは、タスクが見つからない場合に `Debug.Print` を入れているが、プロダクション環境ではこれを「エラーログ用シート」へ書き出すように拡張せよ。

ユニットテストの思考

このツールを配布する前に、必ず以下のテストを行え。
1. 空のExcelシートを投入してもクラッシュしないか?
2. 存在しないTask IDが含まれていても、他のタスクの同期が止まらないか?
3. 不正な日付形式(例: “2023/13/40″)が入力された際、どのように振る舞うか?

結論

優れた自動化ツールとは、単に「動く」ものではない。
「データが汚れていても、システムが壊れず、計算負荷を最小限に抑え、後から誰が見ても意図が明確なコード」のことだ。

Excelを「データ入力インターフェース」、Projectを「計算エンジン」として明確に分離し、その間を強固なマッピング層で繋ぐ。この設計思想を、君の武器にしてほしい。

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