【Access VBAを掌握する極限の知見】第4回:生きたDBから仕様書を秒速で逆アセンブルする。TableDef完全同期型「データ辞書」自動生成エンジンの構築
開発現場で最も不毛な作業は何か。私は迷わず「ドキュメントと実装の乖離との闘い」と答える。
システム改修のたびにExcelの設計書を手動で修正し、バージョン管理に苦悶する。そんな前時代的なワークフローに、今日で終止符を打つ。
Accessの真骨頂は、データベースエンジン(ACE/Jet)のメタデータをプログラムから完全に掌握できる点にある。テーブル定義(TableDef)、フィールド、リレーションシップ、そして拡張プロパティ。これらはすべてコードから直接読み出し、加工できる一級市民だ。
今回は、Accessの内部構造からメタデータを寸分たがわず抽出し、「常に最新を維持するExcelデータ辞書(設計書)」を全自動で生成・同期するプロダクションコードを授与する。
—
なぜ「手動の仕様書」は必ず破綻するのか
プログラマが仕様書を直すのを忘れるから? いや、問題の本質はそこではない。
「データ構造の真実(Single Source of Truth)」がデータベースファイル(.accdb)の内部にありながら、その表現を人間が手作業でExcelに転記しているという構造的欠陥にある。
手動運用の何が危険か:
1. 変更ミスの温床: フィールドの型変更やサイズ変更がExcelに反映されず、後続のバッチや外部連携で型ミスマッチエラーが発生する。
2. インデックスや制約の迷子: どのフィールドにインデックスが貼られているか、主キーは何かといった物理制約がドキュメントから抜け落ちる。
3. メンテナンスコストの増大: 小さな改修のたびに「設計書チェック」という無駄な工数が発生する。
この課題に対するエンジニアリング上の解は一つしかない。「実行可能なコードによって、データベース自体をドキュメント化し続けること」である。
—
設計思想:ロバストなデータ辞書エンジンの要件
今回のツール作成にあたり、プロのアーキテクトとして以下の要件を定義する。
- システムテーブルの完全排除: `MSys`で始まるシステムテーブルや一時テーブルは、業務設計書にとってはノイズでしかない。これらをクエリレベルではなくコードベースで厳格にフィルタリングする。
- 物理名と論理名(キャプション)の完全マッピング: Accessのフィールドには `Description` プロパティや `Caption` プロパティが存在する。これらを抽出し、物理名と論理名を並記した「現場で使える」仕様書にする。
- Excelオブジェクトのライフサイクル管理: COM Interopの闇である「Excelプロセスの残骸(メモリリーク)」を絶対に発生させないエラーハンドリングを実装する。
—
実装コード:データ辞書自動生成エンジン
以下のコードをAccess側の標準モジュールに実装し、実行せよ。
DAO(Data Access Objects)を用いてテーブル定義の隅々までスキャンし、Excelの指定シートへ美しいフォーマットで流し込む。
Option Explicit
Option Compare Database
‘ =========================================================================
‘ モジュール名: modDataDictionaryGenerator
‘ 概要 : TableDefからメタデータを抽出し、Excelデータ辞書を自動生成する
‘ アーキテクチャ: DAOによるメタデータ解析 + 堅牢なExcelオートメーション
‘ =========================================================================
Public Sub GenerateDataDictionary()
Dim db As DAO.Database
Dim tdef As DAO.TableDef
Dim fld As DAO.Field
Dim xlApp As Object
Dim xlWb As Object
Dim xlWs As Object
Dim rowIdx As Long
Dim tableCount As Long
Dim startTime As Double
startTime = Timer
Set db = CurrentDb
‘ 1. Excelアプリケーションのインスタンス生成(早期バインディング推奨だが環境依存レスのため遅延バインディング)
On Error GoTo ErrorHandler
Set xlApp = CreateObject(“Excel.Application”)
xlApp.Visible = False
xlApp.ScreenUpdating = False
xlApp.DisplayAlerts = False
Set xlWb = xlApp.Workbooks.Add
Set xlWs = xlWb.Sheets(1)
xlWs.Name = “データ辞書”
‘ 2. ヘッダー行の構築
Call SetupHeader(xlWs)
rowIdx = 2
tableCount = 0
‘ 3. TableDefコレクションのイテレーション
For Each tdef In db.TableDefs
‘ システムテーブル(MSys~)およびリンクテーブル(Connectプロパティあり)を除外
If Not (tdef.Name Like “MSys” Or tdef.Name Like “~”) Then
If Len(tdef.Connect) = 0 Then ‘ ローカルテーブルのみ対象
tableCount = tableCount + 1
For Each fld In tdef.Fields
‘ A列: テーブル物理名
xlWs.Cells(rowIdx, 1).Value = tdef.Name
‘ B列: フィールド物理名
xlWs.Cells(rowIdx, 2).Value = fld.Name
‘ C列: データ型(数値から文字列へ変換)
xlWs.Cells(rowIdx, 3).Value = GetDataTypeName(fld.Type)
‘ D列: サイズ
xlWs.Cells(rowIdx, 4).Value = fld.Size
‘ E列: 必填(Required)
xlWs.Cells(rowIdx, 5).Value = IIf(fld.Required, “Yes”, “No”)
‘ F列: ゼロ長文字列許可(AllowZeroLength)
xlWs.Cells(rowIdx, 6).Value = IIf(fld.AllowZeroLength, “Yes”, “No”)
‘ G列: 説明(Descriptionプロパティの安全な取得)
xlWs.Cells(rowIdx, 7).Value = GetFieldDescription(tdef, fld.Name)
rowIdx = rowIdx + 1
Next fld
End If
End If
Next tdef
‘ 4. 書式設定とテーブルスタイルの適用
Call FormatWorksheet(xlWs, rowIdx – 1)
‘ 5. 保存ダイアログの処理(デスクトップにタイムスタンプ付きで保存)
Dim savePath As String
savePath = CreateObject(“WScript.Shell”).SpecialFolders(“Desktop”) & _
“\DataDictionary_” & Format(Now, “yyyymmdd_hhnnss”) & “.xlsx”
xlWb.SaveAs savePath
xlWb.Close False
xlApp.Quit
‘ 処理終了の通知
MsgBox “データ辞書の生成が完了しました。” & vbCrLf & _
“対象テーブル数: ” & tableCount & “件” & vbCrLf & _
“保存先: ” & savePath, vbInformation, “完了”
CleanUp:
‘ 確実なリソース解放(メモリリーク防止)
Set xlWs = Nothing
Set xlWb = Nothing
Set xlApp = Nothing
Set fld = Nothing
Set tdef = Nothing
Set db = Nothing
Exit Sub
ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Description, vbCritical, “致命的エラー”
If Not xlWb Is Nothing Then xlWb.Close False
If Not xlApp Is Nothing Then xlApp.Quit
Resume CleanUp
End Sub
‘ =========================================================================
‘ ヘルパー関数群
‘ =========================================================================
Private Sub SetupHeader(ByRef ws As Object)
Dim headers As Variant
headers = Array(“テーブル名”, “フィールド名”, “データ型”, “サイズ”, “必須”, “ゼロ長許可”, “説明・備考”)
Dim i As Long
For i = LBound(headers) To UBound(headers)
With ws.Cells(1, i + 1)
.Value = headers(i)
.Interior.Color = RGB(51, 51, 51) ‘ ダークグレー
.Font.Color = RGB(255, 255, 255) ‘ 白文字
.Font.Bold = True
.HorizontalAlignment = -4108 ‘ 中央揃え
End With
Next i
End Sub
Private Function GetDataTypeName(ByVal dataType As Integer) As String
Select Case dataType
Case dbBoolean: GetDataTypeName = “Yes/No (Boolean)”
Case dbByte: GetDataTypeName = “バイト (Byte)”
Case dbInteger: GetDataTypeName = “整数 (Integer)”
Case dbLong: GetDataTypeName = “長整数 (Long)”
Case dbCurrency: GetDataTypeName = “通貨 (Currency)”
Case dbSingle: CGetDataTypeName = “単精度 (Single)”
Case dbDouble: GetDataTypeName = “倍精度 (Double)”
Case dbDate: GetDataTypeName = “日付/時刻 (Date)”
Case dbText: GetDataTypeName = “短いテキスト (Text)”
Case dbLongBinary: GetDataTypeName = “OLE オブジェクト”
Case dbMemo: GetDataTypeName = “長いテキスト (Memo)”
Case dbGUID: GetDataTypeName = “GUID”
Case dbAttachment: GetDataTypeName = “添付ファイル”
Case Else: GetDataTypeName = “その他 (” & dataType & “)”
End Select
End Function
Private Function GetFieldDescription(ByRef tdef As DAO.TableDef, ByVal fieldName As String) As String
On Error Resume Next
GetFieldDescription = tdef.Fields(fieldName).Properties(“Description”).Value
If Err.Number <> 0 Then GetFieldDescription = “”
On Error GoTo 0
End Function
Private Sub FormatWorksheet(ByRef ws As Object, ByVal lastRow As Long)
If lastRow < 2 Then Exit Sub
With ws.Range("A1:G" & lastRow)
.Font.Name = "Meiryo UI"
.Font.Size = 9.5
.Rows.AutoFit
End With
' グリッド線の表示確保
ws.Application.ActiveWindow.DisplayGridlines = True
End Sub
---
プロダクションコードの急所:アーキテクトの解説
このコードには、現場で幾多の修羅場をくぐり抜けてきたエンジニアの知見が凝縮されている。
1. `TableDef.Connect` によるリンクテーブルの排除
Accessアプリでは、バックエンド(BE)のデータとフロントエンド(FE)のリンクテーブルが混在することが多々ある。もし `TableDef` を無条件にループさせると、リンク先(外部DB)のテーブルまで辞書化されてしまうか、あるいは接続エラーを引き起こす。
`Len(tdef.Connect) = 0` という条件式を挟むことで、「純粋なローカル定義のみ」を正確にフィルタリングしている。
2. プロパティ不存在例外のスマートな握りつぶし (`On Error Resume Next`)
DAOの `Properties` コレクションは曲者だ。`Description` プロパティは、開発者がテーブルデザイナー上で明示的に説明を入力していない限り、コレクション自体が存在しない。存在しないプロパティにアクセスした瞬間、VBAは容赦なく実行時エラー(エラー3270: プロパティが見つかりません)を吐き出す。
`GetFieldDescription` 関数内では、あえてエラートラップを局所化し、プロパティが未定義であっても処理が止まらないタフな設計にしている。
3. COMオブジェクトの参照リークを許さないクリーンアップ設計
Excelを背後で操作するVBAスクリプトで最も多いバグが、処理完了後もタスクマネージャーに `EXCEL.EXE` がゾンビとして居座り続ける現象だ。
本コードでは `ErrorHandler` ラベルを設け、予期せぬ例外が発生した場合でも必ずオブジェクト変数を `Nothing` に解放し、Excelのプロセスを安全にシャットダウンするフローを担保している。
—
運用への組み込み:さらなる高みへ
このスクリプトを単なる「手動実行マクロ」で終わらせてはならない。
真の自動化エンジニアであれば、これを「データベース起動時(Autoexec)」や、メイン画面の「仕様書出力ボタン」にフックさせ、日々の開発サイクルのなかに組み込む。
さらに発展させるならば、以下のアプローチも有効だ:
- 前回生成分とのDiff(差分)検出: Git管理されたExcel出力結果と突き合わせ、どのテーブルが改修されたのかをイミディエイトウィンドウにログ出力する。
- リレーションシップ(Relation)情報の追加: `db.Relations` を走査し、テーブル間の外部キー制約(1対多の紐付け)までデータ辞書に自動マッピングする。
「ドキュメントは、コード(あるいは実体)から自動生成されるべきである」
この鉄則をあなたのAccess開発環境に導入し、無意味な手作業の呪縛からチームを解放してほしい。
