【VBA】コードの差分を出す

Japanese

はじめに

「ExcelのVBAで組まれたコードの差分を出したいな」と思うことがあると思います。
私は実際、自作で作成したコードの修正を行った際にどこを修正したのかを知りたかったり、古いプロジェクトのツールとしてVBAが使用されているケースなど。

特に企業などで管理しているコードなんかは変更するたびに差分を出し、レビューをしてもらうことがほぼ必須となっていますよね、、、

しかしVBAはsourcetreeを使用できないし、いちいち資料にコピペ記載するのも怠い。

ということを解決する記事です。

構想

結論から言うと、VBAのプログラムをテキスト化してWinMargeで差分を取る。

以上。

最強のツール

まず初めにエンジニアなら聞いたことがあるソフトをダウンロードしましょう。

日本語版

WinMerge 日本語版

本家

WinMerge - You will see the difference…
WinMerge is an Open Source differencing and merging tool for Windows. WinMerge can compare both folders and files, prese...

差分を出力するExcelファイルの作成

次にVBAで選択したExcelファイルのVBAコードを出力するツールを作成します。

こちらはコピペできるようにしているためそのままmoduleに張り付けてください。

Option Explicit

' VBAコード(モジュール/クラス/フォーム/ThisWorkbook/シート等)をテキストとして一括書き出し
Public Sub VBA書き出し()
    Dim srcPath As String
    Dim outRoot As String
    Dim outDir As String
    Dim wb As Workbook
    Dim vbProj As VBIDE.VBProject
    Dim vbComp As VBIDE.VBComponent
    Dim ext As String
    Dim exportPath As String
    Dim baseName As String
    
    On Error GoTo EH
    
    '--- 1) 対象ファイル選択
    srcPath = PickExcelFile()
    If Len(srcPath) = 0 Then Exit Sub
    
    '--- 2) 出力先フォルダ選択
    outRoot = PickFolder()
    If Len(outRoot) = 0 Then Exit Sub
    
    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
    
    ' 対象ブックを開く(読み取り専用推奨)
    Set wb = Application.Workbooks.Open(Filename:=srcPath, ReadOnly:=True, UpdateLinks:=0, AddToMru:=False)
    
    ' 出力フォルダ(ブック名_日時)
    baseName = GetFileBaseName(srcPath)
    outDir = EnsureTrailingSlash(outRoot) & baseName & "_" & Format(Now, "yyyymmdd_HHMMSS")
    CreateFolderIfNotExists outDir
    
    Set vbProj = wb.VBProject
    
    '--- 3) 書き出し
    For Each vbComp In vbProj.VBComponents
        ext = ComponentExtension(vbComp.Type)
        exportPath = EnsureTrailingSlash(outDir) & SafeFileName(vbComp.Name) & ext
        
        ' 同名があれば上書き(念のため削除)
        If Dir(exportPath, vbNormal) <> "" Then Kill exportPath
        
        vbComp.Export exportPath
    Next vbComp
    
    ' ブックは保存せず閉じる
    wb.Close SaveChanges:=False
    
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    
    '--- 4) 完了メッセージ
    MsgBox "VBAコードの書き出しが完了しました。" & vbCrLf & "出力先: " & outDir, vbInformation, "完了"
    Exit Sub

EH:
    Application.DisplayAlerts = True
    Application.ScreenUpdating = True
    
    ' 可能なら閉じる
    On Error Resume Next
    If Not wb Is Nothing Then wb.Close SaveChanges:=False
    On Error GoTo 0
    
    MsgBox "エラーが発生しました。" & vbCrLf & "内容: " & Err.Description, vbExclamation, "エラー"
End Sub

'========================
' ファイル選択(xlsm/xlsb等)
'========================
Private Function PickExcelFile() As String
    Dim fd As FileDialog
    Set fd = Application.FileDialog(msoFileDialogFilePicker)
    
    With fd
        .Title = "VBAを書き出すExcelファイルを選択してください"
        .AllowMultiSelect = False
        .Filters.Clear
        .Filters.Add "Excel Files", "*.xls; *.xlsx; *.xlsm; *.xlsb", 1
        .Filters.Add "Macro Enabled", "*.xlsm; *.xlsb", 2
        If .Show <> -1 Then
            PickExcelFile = ""
        Else
            PickExcelFile = .SelectedItems(1)
        End If
    End With
End Function

'========================
' 出力先フォルダ選択
'========================
Private Function PickFolder() As String
    Dim fd As FileDialog
    Set fd = Application.FileDialog(msoFileDialogFolderPicker)
    
    With fd
        .Title = "出力先フォルダを選択してください"
        .AllowMultiSelect = False
        If .Show <> -1 Then
            PickFolder = ""
        Else
            PickFolder = .SelectedItems(1)
        End If
    End With
End Function

'========================
' VBComponent Type → 拡張子
'========================
Private Function ComponentExtension(ByVal compType As Long) As String
    Select Case compType
        Case 1 ' vbext_ct_StdModule
            ComponentExtension = ".bas"
        Case 2 ' vbext_ct_ClassModule
            ComponentExtension = ".cls"
        Case 3 ' vbext_ct_MSForm
            ComponentExtension = ".frm"
        Case 100 ' vbext_ct_Document
            ComponentExtension = ".cls"
        Case Else
            ComponentExtension = ".txt"
    End Select
End Function

'========================
' ユーティリティ
'========================
Private Function EnsureTrailingSlash(ByVal path As String) As String
    If Right$(path, 1) = "\" Then
        EnsureTrailingSlash = path
    Else
        EnsureTrailingSlash = path & "\"
    End If
End Function

Private Sub CreateFolderIfNotExists(ByVal folderPath As String)
    If Dir(folderPath, vbDirectory) = "" Then
        MkDir folderPath
    End If
End Sub

Private Function GetFileBaseName(ByVal fullPath As String) As String
    Dim f As String
    f = Mid$(fullPath, InStrRev(fullPath, "\") + 1)
    If InStrRev(f, ".") > 0 Then
        GetFileBaseName = Left$(f, InStrRev(f, ".") - 1)
    Else
        GetFileBaseName = f
    End If
End Function

Private Function SafeFileName(ByVal s As String) As String
    ' Windowsのファイル名に使えない文字を置換
    Dim badChars As Variant, i As Long
    badChars = Array("\", "/", ":", "*", "?", """", "<", ">", "|")
    SafeFileName = s
    For i = LBound(badChars) To UBound(badChars)
        SafeFileName = Replace(SafeFileName, badChars(i), "_")
    Next i
End Function

エラーがでる場合

信頼設定

エラーが出る場合、マクロの信頼に対する設定を変更していないことが多いため確認しましょう。

1.オプション < トラストセンター < トラストセンターの設定

2.マクロの設定

・VBAマクロの有効
・VBAプロジェクトオブジェクトモデルへのアクセス信頼

これらのチェックをしましょう。

参照設定

VBAの開発画面に行き、参照設定を開きます。

そして以下をチェックしているか確認しましょう。

Microsoft Visual Basic for Applications Extensibility 5.3

実践

3つ用意しました。
一番上が今回使用するツールで、v1.0とv1.1は差分取るようです。

中身は先ほどの上に記載したコードが入っています。

v.1.0を選択します。

出力先を選択してOK

完了です。
これをv.1.1にも行います。

出力先のフォルダにはこのようにExcelファイル名_日付_時間が記載されたフォルダが生成されます。

比較

するとこのように差分が取ることができます。

まとめ

今回はVBAのコード差分の取り方について解説しました。
正直、VBAなんて時代遅れかもしれませんが意外と使用しているプロジェクトもあるためまだまだ廃れないとは思います。

sourcetreeのように複数人開発はできませんが(VBAでそれはもうないと思いますが…)このように差分を取ることでバージョン管理も一応できます。

他にもVBAに関する記事を投稿していますのでよかったら見てってください。

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