Project VBAを掌握する極限の知見:実績工数と残工数から算出する独自進捗率の実装
プロジェクト管理の現場でMicrosoft Projectを運用されている皆さん、こんにちは。伝説的なチーフアーキテクトにして、Project VBAの深淵を知り尽くした者として、今回皆さんに伝授するのは、Projectの標準機能だけでは決して得られない、真にプロジェクトの実態を反映する「進捗率」を自動計算する術です。
多くのプロジェクトマネージャーが、Projectの「%完了」や「%物理的達成率」に疑問符を投げかけてきました。「なぜこのタスクは完了しているのに、%完了が低いのだ?」「物理的には何も進んでいないのに、なぜ工数が計上されているのだ?」——これらの根本的な不一致は、標準の進捗率が、現実のプロジェクトの「工数」という最も重要なリソースの消費状況を正確に示していないことに起因します。
本記事では、Project VBAを駆使し、実績工数 (Actual Work) と残存工数 (Remaining Work) に基づいた、より実態に即した独自の進捗率を算出するロジックを実装し、カスタムフィールドにその結果を書き戻すための自動化ツールを構築します。単なるコードのコピペに終わらず、なぜその設計が必要なのか、いかに堅牢なコードを構築すべきか、その極意を徹底的に解説します。
—
1. Projectの進捗率を巡る誤解と真実:なぜ独自ロジックが必要なのか
Microsoft Projectには、いくつかの進捗率を示すフィールドがあります。
- %完了 (Percent Complete): タスクの期間に対する進捗率で、既定ではタスクの期間に実績期間と残存期間が占める割合に基づいて計算されます。
- %物理的達成率 (Physical Percent Complete): タスクの実際の達成度を数値で直接入力するフィールドです。アーンドバリュー分析などに使用されますが、手動入力が基本となります。
- 作業の達成率 (Work Complete): タスクの作業時間に対する進捗率。
これらは一見すると便利そうに見えますが、現実のプロジェクト管理ではしばしば以下のような課題に直面します。
1. 期間ベースの限界: 「%完了」は期間を重視するため、タスクの実態(例えば、開発タスクで初期段階に多くの調査工数が集中し、実装は短期間で終わるようなケース)を反映しにくい。
2. 手動入力の非効率性: 「%物理的達成率」は直感的ですが、手動入力が必要なため、大規模プロジェクトではメンテナンスコストが高く、入力漏れや誤りの温床となりがちです。
3. 工数管理との乖離: 最も重要なのは、プロジェクトマネージャーが最も注視すべき「工数」の消費状況と、これらの進捗率が必ずしも同期しない点です。
私が皆さんに提唱するのは、「工数ベースの進捗率こそ、プロジェクトの実態を最も正確に映し出す鏡である」という哲学です。実績工数がどれだけ消化され、残存工数がどれだけ残っているのか。このシンプルな事実こそが、進捗の真の姿を物語ります。
2. 堅牢なProject VBAコード設計のための基礎原則
Project VBAで自動化ツールを開発する際、単に動けば良いという発想は厳禁です。未来の自分、そしてチームが安心して運用できる、バグの起きない堅牢な設計こそが、チーフアーキテクトとしての矜持です。
2.1. オブジェクトのライフサイクルとパフォーマンス最適化
Projectのオブジェクトモデルは非常にリッチですが、同時に非常に重い側面も持ちます。無駄なオブジェクト参照や、画面更新・イベント処理はパフォーマンスを著しく低下させます。
- 明示的なオブジェクト参照と解放: `Set obj = Nothing` によるオブジェクトの解放は、メモリリークを防ぎ、ガベージコレクションを促進する基本中の基本です。特にループ内でオブジェクトを扱う際は必須です。
- 画面更新とイベントの制御: `Application.ScreenUpdating = False` と `Application.EnableEvents = False` は、処理開始時に必ず設定し、処理終了時に元に戻す習慣をつけましょう。これは体感速度だけでなく、大規模プロジェクトファイルにおける処理時間を劇的に短縮します。
- 必要なプロパティのみを参照: オブジェクトのプロパティを不必要に何度も参照すると、その都度COM呼び出しが発生しオーバーヘッドになります。一度取得した値は変数に格納し再利用しましょう。
2.2. エラーハンドリングと入力値検証
エラーは発生するものです。重要なのは、それを適切に捕捉し、ユーザーに分かりやすい形でフィードバックし、システムを安全に終了させることです。
- `On Error GoTo` の活用: プロシージャレベルでのエラーハンドリングは必須です。エラー発生時に適切なクリーンアップ処理(画面更新の復元など)を行い、アプリケーションが不安定になるのを防ぎます。
- ロジックレベルでの入力値検証: 「分母がゼロになる」「オブジェクトがNothingである」など、予期される異常系はコードレベルで事前にガードします。これにより、実行時エラーの発生自体を減らします。
2.3. カスタムフィールドの選定と活用
Projectで独自の情報を管理する際の肝は、カスタムフィールドの賢明な利用です。
- 適切なデータ型の選択: 進捗率はパーセンテージ(数値)なので、`数値` 型のカスタムフィールドを選定します。
- 堅牢なフィールド指定: 文字列でフィールド名を直接指定すると、Projectのバージョンや言語設定によって不具合が生じる可能性があります。`Application.FieldNameToFieldConstant` メソッドを使用して、フィールド名を内部的なPjField定数に変換することで、より堅牢なコードになります。
- 明確な命名規則: カスタムフィールドには、その内容がすぐにわかるような名前をつけましょう(例: “進捗率 (工数ベース)”)。
3. 独自進捗率算出ロジックの定義
今回実装する独自の進捗率ロジックは、実績工数 (Actual Work) と残存工数 (Remaining Work) をベースとします。
進捗率 (%) = (実績工数) / (実績工数 + 残存工数) 100
この計算式にはいくつかの考慮点があります。
- 合計工数がゼロの場合: `(実績工数 + 残存工数)` がゼロ(つまり、工数が全く定義されていないタスク)の場合、分母がゼロとなりエラーになります。この場合は進捗率を0%と見なすか、特定のルールを適用します。
- 完了済みタスクの扱い: 実績工数があり、残存工数がゼロのタスクは、実質的に100%完了と見なすべきです。
これらの考慮事項を盛り込んだ、まさにプロダクションレベルで通用するコードを提示します。
4. プロダクションコード例:進捗率自動計算ツール
以下に、実績工数と残存工数に基づいた独自の進捗率を計算し、Projectのカスタム数値フィールドに書き込むVBAコードを提供します。このコードは、堅牢性、保守性、パフォーマンスを最大限に考慮して設計されています。
4.1. 前提条件の確認と設定
1. Projectファイルを開く: 計算対象のProjectファイルを開いてください。
2. カスタムフィールドの設定:
- Projectのメニューから `ファイル` > `情報` > `Organizer` を選択します。
- `Organizer` ダイアログで `フィールド` タブを選択し、`タスク` を選択します。
- `新しいフィールド` をクリックし、`種類` を `数値` に設定し、フィールド名に `進捗率 (工数ベース)` などと入力します。既定のカスタムフィールド(例: `数値1`)を使用する場合は、その名前を覚えておきます。
- このコードでは、`CUSTOM_PROGRESS_FIELD_NAME` 定数で指定されたカスタムフィールドを使用します。
4.2. VBAコード
VBAエディタ (`Alt + F11`) を開き、プロジェクトエクスプローラーの `VBAProject (<プロジェクト名>)` を展開し、`モジュール` を右クリックして `挿入` > `標準モジュール` を選択します。そこに以下のコードを貼り付けてください。
‘ このモジュールは、Project VBAで独自の進捗率を計算し、カスタムフィールドに書き込むためのものです。
‘
‘ 目的:
‘ Project標準の進捗率計算ロジックが業務要件に合わない場合、
‘ 実績工数と残存工数に基づいて独自の進捗率(パーセンテージ)を算出し、
‘ 指定されたカスタム数値フィールドにその結果を自動で書き込みます。
‘
‘ 開発者: チーフアーキテクト
‘ 日付: 2023/10/27
‘ バージョン: 1.0.0
‘
Option Explicit
‘ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
‘ // 定数と設定
‘ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
‘ 進捗率を格納するカスタム数値フィールドの名前を定義します。
‘ 例: “数値1”, “数値2”, …, “数値20”
‘ このフィールドはProject上で事前に設定しておく必要があります。
Private Const CUSTOM_PROGRESS_FIELD_NAME As String = “数値1″
‘ 計算対象とするタスクの条件を定義します。
‘ True: サマリータスクも計算対象とする(子タスクの進捗を反映しない単純な計算)
‘ False: サマリータスクは計算対象としない(子タスクのみを対象とするのが一般的)
‘ 注意: サマリータスクの進捗は通常、子タスクから集計されるべきですが、ここでは個別のタスクとして計算します。
‘ 集計ロジックが必要な場合は、別途実装が必要です。
Private Const INCLUDE_SUMMARY_TASKS As Boolean = False
‘ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
‘ // メイン処理プロシージャ
‘ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
”’
”’ 実績工数と残存工数に基づいた独自の進捗率を計算し、
”’ 指定されたカスタム数値フィールドに書き込みます。
”’
Public Sub CalculateCustomTaskProgress()
‘ 変数宣言
Dim pj As MSProject.Project
Dim tsk As MSProject.Task
Dim dblCustomProgress As Double
Dim customFieldID As Long ‘ カスタムフィールドのPjField定数を格納
Dim strCustomFieldNameForMsg As String ‘ メッセージ表示用のフィールド名
‘ エラーハンドリングの開始
On Error GoTo ErrorHandler
‘ 画面更新とイベント処理を一時的に無効化し、パフォーマンスを向上させます。
‘ Projectオブジェクトモデルの操作は比較的重いため、これは非常に重要です。
Application.ScreenUpdating = False
Application.EnableEvents = False
‘ 現在アクティブなプロジェクトを取得します。
Set pj = Application.ActiveProject
‘ カスタムフィールドの内部IDを取得します。
‘ FieldNameToFieldConstantはProjectの組み込み関数で、フィールド名をPjField定数に変換します。
‘ これにより、フィールドへのアクセスが堅牢になり、多言語環境やフィールド名の変更に強くなります。
strCustomFieldNameForMsg = CUSTOM_PROGRESS_FIELD_NAME ‘ メッセージ表示用に元の名前を保存
customFieldID = Application.FieldNameToFieldConstant(CUSTOM_PROGRESS_FIELD_NAME)
‘ カスタムフィールドIDが取得できなかった場合(フィールドが存在しない、または名前が間違っている)
If customFieldID = 0 Then
MsgBox “指定されたカスタムフィールド ‘” & strCustomFieldNameForMsg & “‘ が見つかりません。” & vbCrLf & _
“Project設定を確認し、カスタム数値フィールドが正しく作成されているか確認してください。”, vbCritical
GoTo CleanExit
End If
‘ 全タスクをループ処理します。
For Each tsk In pj.Tasks
‘ Nothingチェック: タスクが削除された後などに残骸としてループに出現する可能性があるため、常にチェックします。
If Not tsk Is Nothing Then
‘ サマリータスクを除外する設定の場合、Summaryプロパティを確認します。
If INCLUDE_SUMMARY_TASKS Or Not tsk.Summary Then
‘ 独自の進捗率を計算します。戻り値は0~1の小数値です。
dblCustomProgress = GetCustomProgressPercentage(tsk)
‘ 計算結果をカスタムフィールドに書き込みます。
‘ Projectのカスタム数値フィールドは通常、0~100のパーセンテージとして表示されるため、
‘ 計算結果を100倍し、小数点以下2桁でフォーマットして格納します。
‘ SetFieldメソッドはPjField定数と値を指定してフィールドを更新します。
tsk.SetField FieldID:=customFieldID, Value:=Format(dblCustomProgress 100, “0.00”)
End If
End If
Next tsk
MsgBox “カスタム進捗率の計算と更新が完了しました。” & vbCrLf & _
“更新されたフィールド: ” & strCustomFieldNameForMsg, vbInformation
CleanExit:
‘ 画面更新とイベント処理を元に戻します。
If Not Application Is Nothing Then ‘ Applicationオブジェクトが有効かチェック
Application.ScreenUpdating = True
Application.EnableEvents = True
End If
‘ オブジェクトの解放 (ガベージコレクションを促進し、メモリリークを防ぐ)
Set tsk = Nothing
Set pj = Nothing
Exit Sub
ErrorHandler:
‘ エラーが発生した場合の処理
MsgBox “エラーが発生しました: ” & Err.Description & ” (Err No: ” & Err.Number & “)” & vbCrLf & _
“タスク: ” & IIf(Not tsk Is Nothing, tsk.Name & ” (ID: ” & tsk.ID & “)”, “不明なタスク”), vbCritical
Resume CleanExit ‘ クリーンアップ処理へジャンプし、安全に終了
End Sub
‘ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
‘ // ヘルパー関数
‘ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
”’
”’
”’ 進捗率を計算する対象のタスクオブジェクト。
”’
Private Function GetCustomProgressPercentage(ByVal tsk As MSProject.Task) As Double
Dim dblActualWork As Double ‘ 実績工数 (分単位)
Dim dblRemainingWork As Double ‘ 残存工数 (分単位)
Dim dblTotalWork As Double ‘ 合計工数 (実績 + 残存)
‘ 入力タスクが有効でない場合は0を返す (堅牢性のため)
If tsk Is Nothing Then
GetCustomProgressPercentage = 0
Exit Function
End If
‘ タスクの工数情報を取得します。
‘ Projectの工数関連プロパティ (ActualWork, RemainingWorkなど) は内部的に「分」で管理されています。
‘ これらのプロパティは常に有効なTimeValueオブジェクトを返すため、IsNumericチェックは不要です。
dblActualWork = tsk.ActualWork
dblRemainingWork = tsk.RemainingWork
‘ 合計工数を計算します。
dblTotalWork = dblActualWork + dblRemainingWork
‘ 進捗率を計算します。分母がゼロになるケースを考慮します。
If dblTotalWork > 0 Then
‘ 進捗率 = 実績工数 / (実績工数 + 残存工数)
GetCustomProgressPercentage = dblActualWork / dblTotalWork
Else
‘ 合計工数がゼロの場合の振る舞いを定義します。
‘ これは、工数が全く計上されていないタスクや、完了済みだが工数ゼロのタスクなどに適用されます。
If dblActualWork = 0 And dblRemainingWork = 0 Then
‘ 実績も残存もゼロの場合、デフォルトでは0%と見なします。
‘ しかし、もしタスクが既に完了済みとマークされているなら100%とすべきかもしれません。
‘ この判断はプロジェクトの要件に依存します。ここでは工数ベースを厳守し、0とします。
GetCustomProgressPercentage = 0
ElseIf dblActualWork > 0 And dblRemainingWork = 0 Then
‘ 実績工数があり、残存工数がゼロであれば、実質的に完了とみなせるため100%
GetCustomProgressPercentage = 1
Else
‘ その他の予期せぬケースがあれば、デフォルトで0%
GetCustomProgressPercentage = 0
End If
End If
End Function
4.3. 実行方法
1. VBAエディタで、先ほど貼り付けたコード内の `CalculateCustomTaskProgress` プロシージャ内にカーソルを置きます。
2. `F5` キーを押すか、メニューバーの `実行` > `Sub/ユーザーフォームの実行` を選択します。
3. マクロの実行が完了すると、メッセージボックスが表示されます。
4. Projectのガントチャートビューに戻り、先ほど設定したカスタム数値フィールド(例: `数値1`)を列として追加すると、計算された進捗率が表示されます。
5. 発展的な考慮事項と注意点
このツールは強力ですが、さらに高度なプロジェクト管理を目指すならば、以下の点も考慮に入れるべきです。
- サマリータスクの集計ロジック: 現在のコードでは、`INCLUDE_SUMMARY_TASKS` が `False` の場合、サマリータスクは処理対象外です。もしサマリータスクにも進捗率を表示したい場合は、子タスクの進捗率を重み付け平均などで集計するロジックを別途実装する必要があります。これはかなり複雑になるため、慎重な設計が求められます。
- イベントドリブンな自動化: `TaskChange` イベントなどを使用して、タスクの工数が変更された際に自動的に進捗率を再計算することも可能です。しかし、これはパフォーマンスへの影響が大きく、無限ループのリスクも伴うため、実装には細心の注意が必要です。まずは手動実行、次にボタン化、その後にイベントドリブンを検討すべきでしょう。
- 複数プロジェクトへの適用: `Application.Projects` コレクションをループすることで、開かれているすべてのプロジェクトに対して一括で処理を実行することも可能です。ただし、各プロジェクトのカスタムフィールド設定が統一されている必要があります。
- データ永続性: 計算結果はプロジェクトファイル (.mpp) に保存されます。他のシステム(データベース、BIツールなど)と連携する場合は、計算結果をエクスポートする仕組みも検討が必要です。
- ログ出力: 大規模なプロジェクトやエラーが発生しやすい環境では、処理の開始・終了、エラー内容、対象タスクなどをログファイルに出力する機能を実装すると、デバッグや監査に役立ちます。
6. まとめ:Project VBAでプロジェクト管理を次のステージへ
Project VBAは、単なる定型作業の自動化に留まらない、プロジェクト管理そのものを最適化するための強力な武器です。今回示した「実績工数と残存工数に基づいた進捗率」は、その一例に過ぎません。
オブジェクトモデルの奥深さを理解し、パフォーマンスと堅牢性を両立させたコードを記述する。このチーフアーキテクトとしての思考プロセスこそが、皆さんの業務を自動化の極致へと導く鍵となります。
「なぜ標準機能では不十分なのか」「どうすればプロジェクトの実態を正確に捉えられるか」という問いに、自らの手で答えを導き出す。そのプロセスこそが、真のプロジェクト管理の達人への道です。このツールが、皆さんのプロジェクトを成功に導く一助となれば幸いです。
