VBAのプログラム説明書の必要性
VBAで作られたシステムを保守するとき、システム設計書がない、あっても最新ではない。などで、現行のVBAプログラムの解析、リファクタリングに相当の工数を要した経験から、VBAプログラムソースからプログラム説明書を作るVBAプログラムを作成中です。
プログラムの構造はそのままで、適当なコメントを挿入していく必要があります。
あるシステムでは合計有効行数5000行のプログラムの分析しながら約1000行(2割)のコメントを入れました。
@Mikoshiba_Kyu さんの記事を大いに参考にして作成しました。優れている点も多いのでぜひご覧ください。
なお、分析するエクセルブックを開いて操作しますので、コピーを対象にするなど自己責任でお使いください。
出力例
このVBAソースを分析出力した一部です
定数定義
Option Explicit
'--------------------------------------------------
'>> プロシージャ・ファンクションの概要説明書を作成する
'> [使用上の注意]対象のブックをオープンするので念の為コピーを対象にすること
'> 他のエクセルブックは閉じておいてください。
'> 自己責任で使用してください。
'> いろいろ漏れがあると思いますので自由に改変してください。
'> それぞれの記法に合わせるには fg_lineType あたりを変更してください
'> 下記は省略しています。ご希望があれば追加で掲載します
'> 処理速度向上のための非表示処理など
'> 利用している外部DBのテーブル名の表示
'--------------------------------------------------
'> 定数変数を定義
Const TITLE_ROW = 2 'タイトル行番号
Dim code_count As Long '有効行数
Dim in_line As String 'ソース一行
Dim gaiyo_line As String 'プロシージャ概要
Dim comment_line As String 'プロシージャコメント
Dim pro_line As String '有効ソース行
Dim isModuleBegin As Boolean 'モジュール開始
Dim module_gaiyo_line As String 'モジュール概要
Dim module_comment_line As String 'モジュールコメント
Dim code_count_mod As Long '有効行数モジュール計
Dim inBookName As String '分析するエクセルブック
Dim outWorkbook As Workbook '出力するエクセルブック
Dim outWorksheet As Object '出力するエクセルシート
Dim procedureNames As Collection 'プロシージャ一覧
Dim I As Long
Dim I_mod As Long
Dim xlModule As Object
Dim myCodeModule As Object
Dim moduleName As String
Dim moduleType As String
Dim procedureName As String
Dim startLine As Long 'プロシージャ開始行
Dim endLine As Long 'プロシージャ終了行
'> 列の位置を定義
Enum itemCOL
左端列 = 1
モジュール名
モジュール型
プロシージャ名
プロシージャ型
概要
処理内容
呼び出
行位置
行数
End Enum
メイン部
'--------------------------------------------------
'プロシージャ・ファンクションの概要説明書を作成する(メインフロー)
'--------------------------------------------------
Public Sub makeVBA_Document()
'1.分析するエクセルブックを選択して開く
inBookName = openVBAFILE
'2.選択しなければ終了
If inBookName = "" Then Exit Sub
'3.出力するワークブックを新規追加
Set outWorkbook = Workbooks.Add
'4.出力するワークシートを設定
Set outWorksheet = outWorkbook.ActiveSheet
'5.全プロシージャ名を取得してコレクションに設定する
Call setProcedureNames
'6.プロシージャ毎の初期処理
Call initProcedure
With outWorksheet
I = TITLE_ROW
'7.対象ブックの各モジュールを処理する
For Each xlModule In Workbooks(inBookName).VBProject.VBComponents
'7.1.モジュールのコードを取得
Set myCodeModule = xlModule.codemodule
'7.2.モジュール初期処理
I = I + 1
moduleName = xlModule.Name
moduleType = xlModule.Type
.Cells(I, itemCOL.モジュール名) = moduleName
.Cells(I, itemCOL.モジュール型) = fg_moduleType(moduleType)
Call initModule
procedureName = ""
'7.3.モジュールコードの各行を処理
Dim L As Long
For L = 1 To myCodeModule.CountOfLines
If procedureName <> myCodeModule.ProcOfLine(L, 0) Then
'7.3.1.プロシージャが変わったときの処理
Call procedureChange(L)
End If
'7.3.2.現在行を取得する
in_line = myCodeModule.Lines(L, 1)
Select Case L
Case Is < startLine
'7.3.3.プロシージャ宣言行の前の処理
Call editBeforeProcedure
Case Is = startLine
'7.3.4..プロシージャ宣言行の処理
Call editTeigiLine
Case Is < endLine
'.プロシージャ内容行の処理
Call editProcedureNaiyo
Case Is = endLine
'7.3.5.プロシージャ最終行の処理
Call writeProcedureLine
End Select
Next L
'7.4.モジュール開始フラグ=Trueのときモジュール開始処理
If isModuleBegin Then
Call moduleBegin
isModuleBegin = False
End If
Next xlModule
'8.分析したエクセルブックを閉じる
If inBookName <> ThisWorkbook.Name Then Workbooks(inBookName).Close False
'9.ワークシートの書式を設定する
Call layoutSheet(I)
End With
MsgBox "モジュール分析出力しました。エクセルブックを保存して下さい。"
Windows(ThisWorkbook.Name).Activate
Windows(outWorkbook.Name).Activate
End Sub
サブルーチン
'--------------------------------------------------
'プロシージャが変わったときの処理
'--------------------------------------------------
Private Sub procedureChange(ByVal L As Long)
'1.プロシージャ初期処理
Call initProcedure
'2.プロシージャ名を取得
procedureName = myCodeModule.ProcOfLine(L, 0)
On Error Resume Next
'3.プロシージャ開始行番号を取得
startLine = myCodeModule.ProcBodyLine(procedureName, 0)
If Err.Number <> 0 Then
MsgBox Err.Description
Err.Clear
End If
On Error GoTo 0
'4.プロシージャ終了行番号を計算
endLine = myCodeModule.ProcStartLine(procedureName, 0) + myCodeModule.ProcCountLines(procedureName, 0) - 1
End Sub
'--------------------------------------------------
'各行の種類を返す
' 引数 入力行
'戻り値 入力行の種類(LONG)
'--------------------------------------------------
Private Function fg_lineType(ByVal p_line As String) As Long
Dim w_line As String
'1.入力行の前後の空白を除く
w_line = Trim(p_line)
'2.空白行、区切り表示行なら0を返す
If (w_line = "") Or (Left(w_line, 4) = "'---") Then
fg_lineType = 0
Exit Function
End If
'3.モジュール概要なら3を返す
If Left(w_line, 3) = "'>>" Then
fg_lineType = 3
Exit Function
End If
'4.モジュール説明なら4を返す
If Left(w_line, 2) = "'>" Then
fg_lineType = 4
Exit Function
End If
'5.コメント行なら2を返す
If Left(w_line, 1) = "'" Then
fg_lineType = 2
Exit Function
End If
'6.上記以外は1を返す(一般行)
fg_lineType = 1
End Function
'--------------------------------------------------
'プロシージャ宣言の前の内容から編集する
'--------------------------------------------------
Private Sub editBeforeProcedure()
Select Case fg_lineType(in_line)
'1.入力行を判定して該当の処理を実行
Case 3
'1.1.モジュール概要行編集
module_gaiyo_line = module_gaiyo_line & Trim(Mid$(in_line, 4)) & vbLf
Case 4
'1.2.モジュール説明行編集
module_comment_line = module_comment_line & Trim(Mid$(in_line, 3)) & vbLf
Case 2
'1.3.プロシージャ概要行編集
gaiyo_line = gaiyo_line & Mid(Trim(in_line), 2) & vbLf
End Select
End Sub
'--------------------------------------------------
'プロシージャ宣言行のとき編集出力する
'--------------------------------------------------
Private Sub editTeigiLine()
With outWorksheet
If isModuleBegin Then
'1.モジュールの出力が未だなら概要・処理内容を出力する
I_mod = I
.Cells(I, itemCOL.概要) = module_gaiyo_line
.Cells(I, itemCOL.処理内容) = module_comment_line
isModuleBegin = False
End If
'2.一行追加して基本項目を出力
I = I + 1
.Cells(I, itemCOL.モジュール名) = moduleName
.Cells(I, itemCOL.モジュール型) = fg_moduleType(moduleType)
.Cells(I, itemCOL.プロシージャ名) = procedureName
.Cells(I, itemCOL.プロシージャ型) = fg_procedureType(in_line)
End With
End Sub
'--------------------------------------------------
'プロシージャ内容行のとき編集する
'--------------------------------------------------
Private Sub editProcedureNaiyo()
Select Case fg_lineType(in_line)
Case 1
'1.一般のコード行なら呼び出しているプロシージャ名を取得する
code_count = code_count + 1
Call setUseProcedure(procedureName)
Case 2
'2.コメント行ならコメント編集行に追加し改行する
comment_line = comment_line & Mid(Trim(in_line), 2) & vbLf
End Select
End Sub
'--------------------------------------------------
'プロシージャ行を出力する
'--------------------------------------------------
Private Sub writeProcedureLine()
'1.プロシージャの概要、処理内容、呼び出、行位置、行数を出力する
With outWorksheet
.Cells(I, itemCOL.概要) = gaiyo_line
.Cells(I, itemCOL.処理内容) = comment_line
.Cells(I, itemCOL.呼び出) = pro_line
.Cells(I, itemCOL.行位置) = startLine
.Cells(I, itemCOL.行数) = code_count
End With
End Sub
'--------------------------------------------------
'呼び出しているプロシージャ名を編集する
'--------------------------------------------------
Private Sub setUseProcedure(ByVal inName As String)
Dim proName As Variant
For Each proName In procedureNames
'1.プロシージャ名一覧コレクションを検索し
If (proName <> inName) And InStr(in_line, proName) > 0 Then
'1.1.ヒットしたら呼び出し編集行に追加し改行する
If InStr(pro_line, proName) = 0 Then
pro_line = pro_line & proName & vbLf
End If
End If
Next
End Sub
'--------------------------------------------------
'プロシージャ毎の初期化
'--------------------------------------------------
Private Sub initProcedure()
'1.作業行やカウンターをクリアする
comment_line = ""
gaiyo_line = ""
pro_line = ""
code_count = 0
End Sub
'--------------------------------------------------
'全プロシージャ名をコレクションに設定する
'--------------------------------------------------
Private Sub setProcedureNames()
Set procedureNames = New Collection
Dim xlModule As Object
Dim myCodeModule As Object
For Each xlModule In Workbooks(inBookName).VBProject.VBComponents
Set myCodeModule = xlModule.codemodule
Dim procName As String: procName = ""
Dim L As Long
For L = 1 To myCodeModule.CountOfLines
If myCodeModule.ProcOfLine(L, 0) <> procName Then
procName = myCodeModule.ProcOfLine(L, 0)
procedureNames.Add procName
End If
Next L
Next xlModule
End Sub
'--------------------------------------------------
'モジュール毎の初期化
'--------------------------------------------------
Private Sub initModule()
'1.isModuleBegin = True に設定
isModuleBegin = True
'2.作業行やカウンターをクリアする
module_gaiyo_line = ""
module_comment_line = ""
code_count_mod = 0
startLine = 9999999
endLine = 9999999
End Sub
'--------------------------------------------------
'モジュール型を返す
'引数:モジュール型(Long)
'戻り値:モジュール型名(String)
'--------------------------------------------------
Private Function fg_moduleType(ByVal moduleTyoe As Long) As String
Select Case moduleType
Case 1
fg_moduleType = "標準モジュール"
Case 2
fg_moduleType = "クラスモジュール"
Case 3
fg_moduleType = "ユーザーフォーム"
Case 100
fg_moduleType = "Excelオブジェクト"
Case Else
fg_moduleType = "その他"
End Select
End Function
'--------------------------------------------------
'プロシージャ型を返す
'引数:宣言行(string)
'戻り値:プロシージャ型名(String)
'--------------------------------------------------
Private Function fg_procedureType(ByVal p_line As String) As String
Select Case True
Case Trim(p_line) Like "Private *"
fg_procedureType = "Private"
Case Trim(p_line) Like "Friend *"
fg_procedureType = "Friend"
Case Trim(p_line) Like "Static *"
fg_procedureType = "Static"
Case Else
fg_procedureType = "Public"
End Select
End Function
'--------------------------------------------------
'モジュールの概要等が未出力のとき出力する
'--------------------------------------------------
Private Sub moduleBegin()
With outWorksheet
I_mod = I
.Cells(I, itemCOL.概要) = module_gaiyo_line
.Cells(I, itemCOL.処理内容) = module_comment_line
End With
End Sub
'--------------------------------------------------
'ワークシートの書式設定
'--------------------------------------------------
Private Sub layoutSheet(ByVal lastRow As Long)
With outWorksheet
'1.項目名を設定
.Cells(TITLE_ROW, itemCOL.モジュール名).Value = "モジュール名"
.Cells(TITLE_ROW, itemCOL.モジュール型).Value = "モジュール型"
.Cells(TITLE_ROW, itemCOL.プロシージャ名).Value = "プロシージャ名"
.Cells(TITLE_ROW, itemCOL.プロシージャ名).Value = "プロシージャ名"
.Cells(TITLE_ROW, itemCOL.プロシージャ型).Value = "プロシージャ型"
.Cells(TITLE_ROW, itemCOL.概要).Value = "概要"
.Cells(TITLE_ROW, itemCOL.処理内容).Value = "処理内容"
.Cells(TITLE_ROW, itemCOL.呼び出).Value = "呼び出"
.Cells(TITLE_ROW, itemCOL.行位置).Value = "先頭行番号"
.Cells(TITLE_ROW, itemCOL.行数).Value = "有効行数"
'2.項目列幅を設定
.Cells(TITLE_ROW, itemCOL.モジュール名).ColumnWidth = 20
.Cells(TITLE_ROW, itemCOL.モジュール型).ColumnWidth = 10
.Cells(TITLE_ROW, itemCOL.プロシージャ型).ColumnWidth = 10
.Cells(TITLE_ROW, itemCOL.概要).ColumnWidth = 30
.Cells(TITLE_ROW, itemCOL.処理内容).ColumnWidth = 65
.Cells(TITLE_ROW, itemCOL.呼び出).ColumnWidth = 20
.Cells(TITLE_ROW, itemCOL.行位置).ColumnWidth = 5
.Cells(TITLE_ROW, itemCOL.行数).ColumnWidth = 5
'3.折り返し設定
.Range(Cells(TITLE_ROW, 1), Cells(TITLE_ROW, itemCOL.行数)).WrapText = True
.Range(Cells(TITLE_ROW + 1, itemCOL.モジュール名), Cells(lastRow, itemCOL.呼び出)).WrapText = True
'4.罫線を引く
.Range(Cells(TITLE_ROW, itemCOL.モジュール名), Cells(lastRow, itemCOL.行数)).Borders.LineStyle = xlContinuous
End With
End Sub
対象ファイルを選択しオープン
Public Function openVBAFILE() As String
Dim targetFileName As String
targetFileName = Application.GetOpenFilename("マクロ有効 EXCELブック, *.xlsm")
If targetFileName = "False" Then Exit Function
Application.EnableEvents = False
Workbooks.Open targetFileName, ReadOnly:=True
Application.EnableEvents = True
If Not (ActiveWindow Is Nothing) Then
ActiveWindow.Visible = False
End If
openVBAFILE = Mid(targetFileName, InStrRev(targetFileName, "\") + 1)
End Function