VBA兩個Sub:如何合併執行?Call語法、重複宣告與錯誤排除

VBA兩個Sub想一次執行,不必硬把程式碼接成同一個程序,保留各自完整的Sub,再新增主程序用Call依序呼叫即可。贊贊小屋以下載臺灣銀行匯率、依日期複製工作表的兩個Sub實測,記錄註解合併遇到的重複宣告與中斷模式,以及改用主程序後成功執行。

VBA兩個Sub特色圖:主程序串接「下載匯率」與「複製並依日期命名」兩個獨立程序,標題為「VBA兩個Sub」與「Call依序執行」

一、VBA兩個Sub,原本可以各自正常執行

贊贊小屋的活頁簿裡原本就有兩個可以單獨執行的Sub。開啟「Microsoft Visual Basic for Applications」編輯器後,左側「專案-VBAProject」的「Microsoft Excel 物件」底下有「ThisWorkbook」,以及「工作表1」「最新匯率」「261027」三張工作表;「模組」資料夾裡則有「Module1」與「Module2」。點開Module1,右側程式碼視窗的第一行是Sub 取得臺灣銀行匯率(),這是第一個Sub,負責連線到臺灣銀行的匯率頁面,把資料寫進「最新匯率」工作表。Module2放的是第二個Sub複製最新匯率並依日期命名(),負責把「最新匯率」複製成一張以日期命名的新工作表。

回到Excel打開「巨集」對話框,「巨集名稱」清單裡正好列著「取得臺灣銀行匯率」與「複製最新匯率並依日期命名」兩項,「巨集存放在」是「所有開啟的活頁簿」。選取其中一項按「執行」,就只會跑那一個Sub,所以要先下載匯率、再複製成當天的工作表,就得分別執行兩次。工作表標籤已經有「最新匯率」與「261027」,表示這兩個Sub單獨執行時都能正常完成工作,贊贊小屋想解決的是怎麼讓它們一次跑完。

Sub是VBA常用的程序(Procedure)類型,從Sub 程序名稱()開始,到End Sub結束,中間放要執行的敘述,完整的Sub寫法可以參考VBA Sub用法。以下只是說明結構的示意,不是贊贊小屋實際使用的程式:

VBA程式|Sub 的基本結構(示意)Sub 程序名稱()
    '這裡放要執行的敘述
End Sub

模組(Module)則是存放程序的容器。一個模組可以放多個Sub,一個VBA專案也可以有多個模組;贊贊小屋這次剛好把兩個Sub分別放在Module1與Module2,這種擺法本身不會妨礙它們連續執行。第一個Sub用QueryTables把臺灣銀行匯率頁面的表格匯入工作表,從提出需求到取得匯率資料的操作過程,可以參考Excel 自動抓取網頁資料。

VBA編輯器的專案視窗列出Module1與Module2,Module1中是Sub 取得臺灣銀行匯率() Excel「巨集」對話框列出「取得臺灣銀行匯率」與「複製最新匯率並依日期命名」兩個巨集,工作表標籤有「最新匯率」與「261027」 知識卡:一個VBA專案裡Module與Sub的關係,Module1與Module2各放一個Sub,各自可執行但要分兩次啟動

二、嘗試用註解方式,將兩個Sub合併

贊贊小屋最直覺的想法,是把兩個Sub接成一個長程序。於是新增「Module3」,把兩段程式碼放在同一個模組裡,接著在程式碼視窗選取第一個Sub結尾的End Sub,連同第二個Sub的宣告行Sub 複製最新匯率並依日期命名(),再按上方工具列的「使程式行變為註解」按鈕。

按下去之後,選取的三行前面各多了一個單引號',文字變成綠色,成為'End Sub與'Sub 複製最新匯率並依日期命名(),中間的空白行也只剩一個單引號。對VBA來說,這幾行已經不再是程式敘述,第一個Sub原本的結尾和第二個Sub的開頭都被忽略,兩段程式碼在畫面上就接成連續的一段。

VBA的註解是在文字前面加單引號,單引號之後的內容會被當成說明文字而不執行,常用來暫時停用程式或補充說明。可是註解只是讓這幾行不再生效,並不會替兩個原本獨立的程序重新安排邏輯:變數宣告、錯誤處理與程序的結束位置,全都還留在原處。這個做法後來接連引發了好幾個問題,所以它只是贊贊小屋的第一次嘗試,並不是建議的做法。

VBA編輯器選取Module3中的End Sub與Sub 複製最新匯率並依日期命名(),滑鼠指向「使程式行變為註解」按鈕 End Sub與Sub 複製最新匯率並依日期命名()兩行已變成綠色註解,前面加上單引號 比較卡:保留獨立程序結構與註解程序邊界的差異,註解End Sub與Sub宣告不等於正確整合兩個程序

三、VBA巨集清單出現兩個同名程序,執行時跳出重複宣告錯誤

兩段程式碼放進Module3之後,再打開「巨集」對話框,「巨集名稱」清單變成三項:「Module1.取得臺灣銀行匯率」「Module3.取得臺灣銀行匯率」,以及「複製最新匯率並依日期命名」。原本只有一個Sub叫這個名字時,清單只顯示程序名稱;現在Module1與Module3各有一個同名的取得臺灣銀行匯率,Excel就在前面加上模組名稱來區分。贊贊小屋選取的是「Module3.取得臺灣銀行匯率」,準備執行合併後的版本。

執行時,VBA跳出「Microsoft Visual Basic for Applications」視窗,標題是「編譯錯誤:」,內容是「在目前的有效範圍內重複宣告」。編輯器同時把Module3裡第二段程式碼開頭的Dim ws As Worksheet反白,指出問題就在這一行;它的上方,正是剛才被註解掉的'End Sub與'Sub 複製最新匯率並依日期命名()。

這個現象要分清兩層名稱:「Module1」「Module3」是模組名稱,取得臺灣銀行匯率是模組裡的程序名稱。不同的標準模組可以各自有同名的Sub,巨集清單才會用「模組名稱.程序名稱」的格式區分。依Microsoft的說明,各模組的程序只呼叫自己模組裡的同名程序時不會衝突,但如果從第三個模組呼叫,又沒有用模組名稱限定,就可能出錯;同一個模組裡放兩個同名的程序,則會造成命名衝突。所以測試過程留下的舊版本最好刪除或改名,否則很容易從清單裡選到錯誤的那一個,之後呼叫時也可能出問題。

至於重複宣告,是因為變數的有效範圍以程序為單位。兩個獨立的Sub各自寫Dim ws As Worksheet不會衝突,因為兩個ws分別存在於各自程序的區域範圍,彼此不相干;但贊贊小屋把兩個程序之間的End Sub與Sub宣告註解掉之後,兩個Dim ws As Worksheet就落進同一個程序、同一個有效範圍,VBA便認定同一個名字被宣告了兩次,在執行前先以編譯錯誤擋下來。

Excel「巨集」對話框出現Module1.取得臺灣銀行匯率與Module3.取得臺灣銀行匯率兩個同名程序 VBA編譯錯誤視窗顯示「在目前的有效範圍內重複宣告」,編輯器停在Dim ws As Worksheet 比較卡:兩個獨立Sub可以各自宣告Dim ws,註解程序分界後同一程序出現兩次Dim ws而造成重複宣告

四、修改宣告之後,VBA又進入中斷模式

看到重複宣告的錯誤,贊贊小屋在第二個Dim ws As Worksheet前面加上單引號,變成綠色的'Dim ws As Worksheet,下一行的Dim 新工作表名稱 As String則保留。這樣合併後的程序只剩第一次宣告的ws,重複的那一行不再生效。

再次執行時,VBA又跳出新的訊息:「處於中斷模式時,不能執行程式碼」,編輯器視窗的標題列也多了「[中斷]」。

註解掉第二個宣告,只是解決了「同一個程序重複宣告」這一個編譯錯誤,並不代表兩個Sub已經整合成功。這個錯誤有兩種處理方式:把重複的那一行註解掉,或直接刪除多餘的宣告,兩者效果相同;更根本的做法,則是一開始就不要把兩個程序接在一起。

中斷模式是VBA的暫停狀態:程式的執行被暫停在某個位置,讓開發者檢查目前的程式與變數。依Microsoft Learn的無法在中斷模式中執行程式碼說明,發生語法錯誤或執行階段錯誤時,可能在不知不覺中就進入中斷模式;程式還停在暫停狀態時,不能再另外執行程式碼,必須先繼續執行被暫停的程式,或先結束它,才能重新執行,所以才會看到「處於中斷模式時,不能執行程式碼」。這個訊息表示執行狀態被暫停,不能直接解讀成電腦記憶體不足,也不是記憶體沒有釋放。

第二個Dim ws As Worksheet前面加上單引號變成綠色註解,下一行Dim 新工作表名稱 As String保留 ![VBA視窗標題列顯示[中斷],並跳出「處於中斷模式時,不能執行程式碼」的訊息](assets/a00114-42.webp) 知識卡:註解第二個Dim只解除重複宣告這一個編譯錯誤,仍會進入中斷模式,不代表整合成功

五、使用「重新設定」離開中斷模式,再次執行

要離開這個暫停狀態,贊贊小屋打開編輯器上方的「執行」選單。選單裡有「繼續(C)」(快速鍵F5)、「中斷(K)」、「重新設定(R)」與「設計模式(M)」;因為程式已經處在中斷狀態,「中斷(K)」呈現灰色無法選取,贊贊小屋選擇「重新設定(R)」,讓暫停中的程式結束,回到可以重新執行的狀態。

重新設定後再次執行,Excel跳出訊息「臺灣銀行匯率下載完成!」。但工作表標籤仍然只有「工作表1」「最新匯率」「261027」,並沒有新增當天日期的工作表。也就是說,下載那一段執行了,第二段「複製最新匯率並依日期命名」卻沒有跟著跑,這正是贊贊小屋拿去請教ChatGPT的問題。

「繼續」會從暫停的位置往下執行,「重新設定」則是結束目前暫停中的程式,並清除呼叫堆疊與模組層級變數,之後可以重新從頭執行。選單裡「重新設定」旁邊的藍色方塊圖示,與工具列上的藍色方塊按鈕是同一個功能。這一步也提醒了一件事:訊息方塊顯示「下載完成」,只代表第一段程式跑完,不代表整件工作都完成;要確認結果,還是得回到工作表標籤,檢查有沒有真的出現新的工作表。

VBA編輯器的「執行」選單列出繼續、中斷、重新設定與設計模式,其中重新設定被標示 再次執行後Excel跳出「臺灣銀行匯率下載完成!」,工作表標籤只有最新匯率與261027,沒有261028 判斷卡:離開中斷模式後重新執行,看到下載完成仍要檢查是否新增當天日期工作表

六、使用ChatGPT找出兩個Sub合併失敗的原因

卡在這裡,贊贊小屋把問題交給ChatGPT。提問只有一句話,後面直接貼上合併後的程式碼:

傳給ChatGPT的提問我用註解方式將兩個 Sub 合併,但第二個 Sub 卻沒有複製成今天的工作表,是哪裡設錯了?

這句話交代了兩件事:做了什麼(用註解合併兩個Sub),以及實際結果(第二個Sub沒有複製出今天的工作表),再加上完整的程式碼,ChatGPT就有足夠的資料判斷問題。

ChatGPT的回覆先給結論:「不是第二個 Sub 的複製指令寫錯,而是你用註解合併兩個程序時,連第一個程序的結束位置與錯誤處理區塊也一起合併了。」接著指出目前的程式有兩個主要問題,第一個是「你把第一個 Sub 的結束指令註解掉了」,原本應該有End Sub,再接著Sub 複製最新匯率並依日期命名(),現在這兩行都被註解了。

要看懂這個診斷,得先分清楚Exit Sub與End Sub。End Sub是程序的結束界線,Exit Sub則是在程序執行到一半時立刻離開,並不定義程序在哪裡結束,Microsoft Learn的Exit陳述式說明也特別提醒不要把Exit與End混為一談。贊贊小屋的第一個Sub,在下載完成、跳出完成訊息之後,馬上就寫了Exit Sub,後面才是錯誤處理:區塊。合併後的Module3,相關片段是這樣:

VBA程式|Module3 合併版的相關片段    MsgBox "臺灣銀行匯率下載完成!", vbInformation

    Exit Sub

錯誤處理:
    MsgBox "下載匯率時發生錯誤。" & vbCrLf & _
           "請檢查網路連線、工作表名稱或網站是否正常。", _
           vbExclamation

'End Sub
'
'Sub 複製最新匯率並依日期命名()

    'Dim ws As Worksheet
    Dim 新工作表名稱 As String

正常下載時,程式跑到Exit Sub就離開,位在後面的複製程式碼不會執行,所以才會只看到下載完成的訊息,沒有新增工作表。反過來說,只有下載途中出錯、跳到錯誤處理:顯示錯誤訊息之後,程式才會順著往下掉進被接起來的複製程式碼,這剛好和原本想要的順序相反。

這次求助也示範了怎麼向AI提供除錯資訊:把預期的結果、實際看到的結果、做過的操作和相關的完整程式碼一起交給它,比只貼一句「程式壞了」更容易讓AI判斷問題出在哪裡。不過ChatGPT的說明只是幫忙定位問題,是否正確,還是要回到實際程式碼和Excel執行結果確認;ChatGPT後來提供的做法也一樣,要等贊贊小屋實測成功,才確定有效。

在ChatGPT提問「我用註解方式將兩個 Sub 合併,但第二個 Sub 卻沒有複製成今天的工作表,是哪裡設錯了?」並貼上程式碼 ChatGPT回覆指出不是第二個Sub的複製指令寫錯,而是註解合併時連第一個程序的結束位置與錯誤處理區塊一起合併了 流程卡:Module3合併版中,下載成功會在Exit Sub離開,後面的複製程式碼不會執行,下載出錯才會落入後方程式碼

七、使用Call讓兩個程序依序執行

要讓兩個Sub依序執行,最直接的做法是保留兩個Sub各自完整,再新增一個主程序,用Call依序呼叫它們。ChatGPT接著給出的解法正是如此,標題是「二、正確做法:保留兩個程序,再新增一個總開關」。它說明不需要用註解把兩個Sub合併,最簡單、最安全的方法,是讓兩個程序各自保持完整,再增加一個主程序,依序呼叫它們,並整理成Excel 2016可以使用、可整段覆蓋貼上的完整VBA程式。畫面中的主程序先判斷取得臺灣銀行匯率()是否等於True,成立才用Call執行複製最新匯率並依日期命名。

贊贊小屋回到VBA編輯器,新增「Module4」,貼上ChatGPT整理的程式,貼入程式與執行巨集的完整步驟,可以參考ChatGPT寫ExcelVBA。程式碼視窗最上方是主程序Sub 一次執行匯率下載與複製(),它下方原本的下載程序,已經從Sub改成Function 取得臺灣銀行匯率() As Boolean,也就是會回傳「是否成功」的程序。Module4的完整程式如下:

VBA程式|Module4 的主程序與兩個程序'==================================================
' 主程序:一次執行下載匯率及複製工作表
'==================================================
Sub 一次執行匯率下載與複製()

    '先下載臺灣銀行匯率
    If 取得臺灣銀行匯率() = True Then

        '下載成功後,才複製並命名工作表
        Call 複製最新匯率並依日期命名

    End If

End Sub


'==================================================
' 第一個程序:下載臺灣銀行匯率
'==================================================
Function 取得臺灣銀行匯率() As Boolean

    Dim ws As Worksheet
    Dim 網址 As String
    Dim 最後列 As Long
    Dim i As Long

    '預設為下載失敗
    取得臺灣銀行匯率 = False

    On Error GoTo 錯誤處理

    '設定臺灣銀行匯率網址
    網址 = "https://rate.bot.com.tw/xrt?Lang=zh-TW"

    '尋找「最新匯率」工作表
    Set ws = Nothing

    On Error Resume Next
    Set ws = ThisWorkbook.Worksheets("最新匯率")
    On Error GoTo 錯誤處理

    '如果工作表不存在,就自動新增
    If ws Is Nothing Then
        Set ws = ThisWorkbook.Worksheets.Add
        ws.Name = "最新匯率"
    End If

    '清除舊資料
    ws.Cells.Clear

    '從臺灣銀行網站匯入網頁表格
    With ws.QueryTables.Add( _
        Connection:="URL;" & 網址, _
        Destination:=ws.Range("A1"))

        .WebSelectionType = xlAllTables
        .WebFormatting = xlWebFormattingNone
        .Refresh BackgroundQuery:=False
        .Delete

    End With

    '刪除沒有匯率數字的多餘資料列
    最後列 = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    For i = 最後列 To 1 Step -1

        If Application.WorksheetFunction.Count( _
            ws.Range("B" & i & ":E" & i)) = 0 Then

            ws.Rows(i).Delete

        End If

    Next i

    '調整欄寬
    ws.Columns.AutoFit

    '回到最新匯率工作表
    ws.Activate

    '設定下載成功
    取得臺灣銀行匯率 = True

    MsgBox "臺灣銀行匯率下載完成!", vbInformation

    Exit Function

錯誤處理:

    MsgBox "下載匯率時發生錯誤。" & vbCrLf & _
           "請檢查網路連線、工作表名稱或網站是否正常。", _
           vbExclamation

End Function


'==================================================
' 第二個程序:複製最新匯率並依日期命名
'==================================================
Sub 複製最新匯率並依日期命名()

    Dim ws As Worksheet
    Dim 新工作表名稱 As String

    On Error GoTo 錯誤處理

    '取得今天的日期,例如 261028
    新工作表名稱 = Format(Date, "yymmdd")

    '確認「最新匯率」工作表是否存在
    Set ws = Nothing

    On Error Resume Next
    Set ws = ThisWorkbook.Worksheets("最新匯率")
    On Error GoTo 錯誤處理

    If ws Is Nothing Then
        MsgBox "找不到「最新匯率」工作表!", vbExclamation
        Exit Sub
    End If

    '確認今天的工作表是否已經存在
    Set ws = Nothing

    On Error Resume Next
    Set ws = ThisWorkbook.Worksheets(新工作表名稱)
    On Error GoTo 錯誤處理

    If Not ws Is Nothing Then

        MsgBox "工作表「" & 新工作表名稱 & _
               "」已經存在,請勿重複複製!", vbExclamation

        Exit Sub

    End If

    '複製「最新匯率」工作表
    ThisWorkbook.Worksheets("最新匯率").Copy _
        After:=ThisWorkbook.Worksheets( _
        ThisWorkbook.Worksheets.Count)

    '將複製後的工作表重新命名
    ActiveSheet.Name = 新工作表名稱

    MsgBox "匯率工作表複製完成!" & vbCrLf & _
           "新工作表名稱:" & 新工作表名稱, vbInformation

    Exit Sub

錯誤處理:

    MsgBox "複製或重新命名工作表時發生錯誤。" & vbCrLf & _
           "錯誤原因:" & Err.Description, vbExclamation

End Sub

執行主程序一次執行匯率下載與複製之後,先執行下載,跳出「臺灣銀行匯率下載完成!」,接著執行複製,出現「匯率工作表複製完成!新工作表名稱:261028」。工作表標籤在「最新匯率」「261027」之後多了「261028」,贊贊小屋實測確認,一次執行就完成下載匯率與依日期複製工作表兩件事。

主程序用的Call是呼叫其他程序的指令:Call 程序名稱會讓VBA先去執行指定的Sub,執行完再回到主程序往下走,完整語法可以參考Microsoft Learn的Call陳述式。不過Call並不是必要的,直接寫程序名稱也能呼叫,例如把Call 複製最新匯率並依日期命名改成複製最新匯率並依日期命名,結果相同。要注意的是傳遞參數時的括號:使用Call時,參數要放在括號裡;省略Call,參數就不加括號。這次兩個程序都沒有參數,所以不會遇到這個問題。

下載程序改成Function,是因為Sub只負責執行一連串動作,不會回傳值,也不能當成運算式的一部分;Function除了執行動作,還能回傳一個值,可以直接寫在If判斷裡使用,主程序要知道「下載有沒有成功」,就需要這個值。Module4裡的Function一開始先把取得臺灣銀行匯率設成False,也就是預設失敗;只有程式一路跑到最後,才把它設成True。中途出錯會跳進錯誤處理:區塊,顯示錯誤訊息後結束,回傳值仍然是False,主程序的If條件不成立,就不會去複製工作表。這是用Boolean(布林值,只有True與False兩種結果)當成功狀態的簡單防呆。

不過回傳True的條件,只是下載程序執行到最後都沒有進入錯誤處理,並不代表網頁上的資料內容一定正確或完整,使用者仍然需要看一下「最新匯率」工作表裡的幣別與數字。日期命名也有類似的限制:Format(Date, "yymmdd")產生的名稱每天不同,但同一天再執行一次,就會得到同樣的名稱。依Module4的程式設計,複製程序會先檢查同名的工作表(檢查前先把ws清空的原因,可以參考VBA錯誤處理),如果當天已經存在,就跳出「工作表「261028」已經存在,請勿重複複製!」這類提醒並結束,不會重複建立;同一天重複執行主程序時,下載仍然會再跑一次,被擋下的只有複製這一步。

把兩個功能拆成獨立程序,再用主程序串接,就是常說的模組化設計。每個程序只負責一件事,哪個環節出問題,就只需要修改那一段;將來若有別的工作也需要「複製成日期工作表」,直接呼叫同一個程序就可以,不必再複製貼上一份。

ChatGPT建議保留兩個程序再新增一個總開關,並列出主程序一次執行匯率下載與複製的程式碼 Module4程式碼視窗最上方是主程序一次執行匯率下載與複製,下方是回傳Boolean的Function 取得臺灣銀行匯率 Excel跳出「匯率工作表複製完成!新工作表名稱:261028」,工作表標籤新增261028 流程卡:主程序用If判斷下載是否成功,True才Call複製最新匯率並依日期命名,False不複製

VBA兩個Sub,保留獨立程序更容易維護

回頭看這一路,贊贊小屋原本以為合併兩個Sub,就是把程式碼接在一起,沒想到每解決一個錯誤,後面又冒出下一個:先是重複宣告,再是中斷模式,最後連複製也沒有跑。真正有效的做法反而是什麼都不硬接,讓下載和複製各自保持完整,再用一個主程序決定它們的先後順序。

這也是處理VBA兩個Sub比較穩的方式:程序保留清楚的功能與邊界,要組合時再用Call或程序名稱串起來,哪個環節需要修改,就只動那一個程序。至於ChatGPT提供的方案,贊贊小屋同樣是貼進Excel實測,看到「261028」真的出現,才確認它可用;AI給的程式碼可以省下很多摸索的時間,但最後的判斷,還是得回到自己的工作表上驗證。

總結卡:從合併失敗到正確串接,兩個Sub不必硬合併,保留清楚的程序結構,再用主程序串接並實測驗證
發布日期:
分類:AI工具、ChatGPT VBA