1. 程式人生 > >VBA中的錯誤處理

VBA中的錯誤處理

  從理論上講,VBA沒有提供任何的錯誤處理機制,這種被用在微軟Office產品中的以Visual Basic語言為基礎的指令碼語言根本就不要任何的錯誤處理,當程式出現錯誤或產生異常情況時,VBA會自動定位到出錯的程式碼行,然後提示使用者出錯的可能原因。這是典型的指令碼語言的錯誤提示,聯想到javascript語言,在瀏覽器中如果出現指令碼錯誤,瀏覽器會給出提示資訊,但這並不影響整個程式的正常執行,最多也就是出現錯誤之後的指令碼不被繼續解釋而已。不過即便如此,javascript還是提供了較為良好的錯誤處理機制,例如常見的try catch語句和alert提示,以及後來支援的debugger除錯資訊等,javascript在支援面嚮物件語言特性的同時也逐漸改善了它的錯誤處理和除錯方法。

    然而Visual Basci卻沒有這麼幸運,從誕生之初,Visual Basic就沒有提供一個比較好的錯誤處理機制,儘管我們在實際應用中總會遇到這樣或那樣的執行時錯誤(例如錯誤刪除檔案、磁碟驅動器空間不夠、網路通訊發生異常等),但是對於Visual Basic的過程來說根本就沒有錯誤處理,當錯誤產生時程式便停止執行,直到異常被清除。有關比較詳細的介紹Visual Basic的錯誤處理和除錯方法的文章,讀者可以參考下面這個連結。

    VBA的語言特性類似於Visual Basic,應該說它們屬於同一家族,所以,用來在Visual Basic中處理程式異常的方法也同樣可以被用在VBA中。

    在Visual Basic中,常用的程式錯誤處理的方式是設定或使用錯誤陷阱,以告訴應用程式當錯誤發生時轉移到何處(或處理當錯誤發生時要執行的程式碼),通過在程式碼中定義標籤來告知應用程式當錯誤發生時要轉到的地方。這一點和C系列語言的錯誤處理方式是相同的。基本步驟如下:

1. 設定一個有效的錯誤陷阱,以告訴應用程式發生錯誤時轉移到何處繼續執行。Visual Basic中的On Error語句可以使錯誤陷阱有效,併為應用程式指定錯誤處理的入口。

2. 在錯誤程式的入口處編寫響應錯誤的具體實現,如繼續嘗試執行之前的程式碼、或告知使用者出錯的具體原因以讓使用者嘗試去解決等。

3. 退出錯誤處理。

    有關如何使用Visual Basic的錯誤處理和On Error語句的具體含義,讀者可以仔細閱讀上面給出的那個連結的文章,裡面有非常詳細的介紹。我在這裡會結合實際應用來講講在VBA中如何具體使用錯誤處理。

    先看一個簡單的示例。

複製程式碼 PrivateSub CommandButton1_Click()
    
OnErrorGoTo Err_Handle
    
Dim a AsIntegerDim b AsIntegerDim c AsInteger
    a 
=10
    b 
=0
    c 
= a / b '除數為0會導致執行時錯誤MsgBox c
    
End Sub
Err_Handle:
    
MsgBox Err.Description
End Sub 複製程式碼

    在上述過程中,我們首先通過On Error語句設定了一個錯誤陷阱,該錯誤陷阱將自動被啟用,同時錯誤陷阱指向了程式碼中定義的標籤Error_Handle。當過程被呼叫時,如果出現異常,程式會自動執行標籤所指向的程式碼段,這裡會給使用者一個提示。Err物件為系統物件,其中包含了當錯誤發生時的描述資訊和錯誤編號,根據Err物件提供的這些簡單資訊我們也許可以告知使用者應用程式發生了什麼事情,從而最終找出出錯的具體原因。

    在Visual Basic中,我們通過On Error語句設定並激活了一個錯誤陷阱,直到程式退出過程或方法,該錯誤陷阱會一直有效。也就是說,我們需要給每一個過程或方法在需要的時候設定單獨的錯誤陷阱,這個有點類似於C的程式碼中在需要的地方插入try catch語句,錯誤處理程式在過程或方法內部定義的標籤開始的地方,在程式執行時如果錯誤沒有出現,則標籤之後的程式碼應該不會被執行到,因此我們通常都需要在錯誤處理程式碼前插入退出語句,例如End Sub或方法的返回語句。

    幸運的是我們通常沒有必要為每一個過程或方法定義錯誤陷阱,在VBA中,往往只有一少部分過程或方法需要定義錯誤陷阱,但是不排除複雜的VBA應用程式,當代碼量達到上千行,過程或方法上百個時,應該不亞於一個小的VB系統,這個時候編寫一個專門的錯誤處理函式還是很有必要的。在需要進行錯誤處理的過程或方法中設定好錯誤陷阱和用於處理錯誤的標籤,然後在標籤後呼叫錯誤處理函式並傳入Err物件,由錯誤處理函式專門處理程式中各種不同的錯誤。這個程式看起來大致是下面這個樣子。

複製程式碼 '-----Err number-----PrivateConst ErrNoPermissions =-2147217900PrivateConst ErrCannotLocateURL =-2146697211PrivateConst ErrDbDenyConnect =-2147217843PrivateConst ErrCannotFoundDbProvider =3706'--------------------' Errors handle:
'
 Return    Description
'
 0         Resume
'
 1         Resume Next
'
 2         ErrorFunction ErrorsHandle() AsIntegerDim intMsgType AsInteger, intResponse AsInteger, strMsg AsStringDim myDoc As Worksheet
   
Set myDoc = ActiveSheet
   
   
SelectCase Err.Number
          
Case ErrCannotLocateURL           ' Error -2146697211             strMsg ="Cannot connect to the website specified. Please make sure the URL is correct and the website is available."
             intMsgType 
= vbRetryCancel
          
Case ErrNoPermissions             ' Error -2147217900             myDoc.Protect 'Protect the sheet if current user doesn't have permissions to access the data             strMsg ="User "& glUserName &" doesnt have permissions to access the data."
             intMsgType 
= vbExclamation
          
Case ErrCannotFoundDbProvider     ' Error 3706             strMsg ="Please ensure 'Microsoft SQL Server Native Client for SQL2005' installed at first."& _
                      
"You can download at :http://download.microsoft.com/download/4/4/D/"& _
                      
"44DBDE61-B385-4FC2-A67D-48053B8F9FAD/sqlncli.msi"
             intMsgType 
= vbExclamation
          
Case ErrDbDenyConnect             ' Error -2147217843             myDoc.Protect 'Protect the sheet if current user doesn't have permissions to connect the database             strMsg ="Database login failed for user '"& glUserName &"'."
             intMsgType 
= vbExclamation
          
CaseElse
               strMsg 
="Fatal error."& Err.Description &".The error number is "& Err.Number
             intMsgType 
= vbCritical
   
EndSelect
      
   intResponse 
=MsgBox(strMsg, intMsgType)
   
SelectCase intResponse
          
Case46' Retry And Yes             ErrorsHandle =0Case5' Ignore             ErrorsHandle =1CaseElse' Cancel and Abort             ErrorsHandle =2EndSelectEnd Function 複製程式碼     稍微做一下解釋。當程式發生錯誤時,Err物件的Number屬性會返回一個錯誤程式碼,ErrorsHandle函式得到這個錯誤程式碼並通過Select Case語句逐一比對錯誤程式碼,找到事先定義好的錯誤處理方法從而返回給使用者最準確的資訊。程式的一開始定義了一組常量用來描述錯誤程式碼所表示的具體含義,在Select Case語句中根據不同的錯誤程式碼返回給使用者不同的錯誤描述資訊,並且根據錯誤的種類彈出不同型別的提示框(如確定、重試、取消等),這個是由MsgBox常數所決定的,該常數分為很多種類,可以彈出各種不同型別的提示框,讀者可以自己查閱Office幫助文件。如果需要,我們可以隨時在Select Case語句中補充更多的內容來定製內容更豐富的錯誤處理方法,而只需要確認何種錯誤程式碼代表何種具體的錯誤資訊即可,這個錯誤資訊我們也可以通過Err.Description屬性來獲取,儘管這個描述資訊通常都並不那麼精確。最後ErrorsHandle函式會返回三種不同的結果(當然如果需要你可以讓這個函式返回更多的值),用以表示呼叫它的過程或方法如何繼續處理,是終止程式執行,還是嘗試再次執行,或者忽略錯誤繼續執行下面的程式碼等。下面是呼叫的程式碼。 複製程式碼 Public Enum DebugMode
    Release 
=0
    Debugger 
=1End Enum

Public Mode As DebugMode

PrivateSub CommandButton1_Click()  
    Mode 
= DebugMode.Release 'Change the Mode value to Debugger or ReleaseIf Mode = Release ThenOnErrorGoTo Error_Handle
    
EndIf' TODO SomethingExitSub
    
Error_Handle:
    errNum 
= ErrorsHandle
    
If errNum =0ThenResumeElseIf errNum =1ThenResumeNextElseExitSubEndIfEnd Sub 複製程式碼

    我省略了過程中的具體實現,實際上你可以在這個過程中實現任何功能,只要程式出錯,ErrorsHandle方法就會被呼叫,並且按照事先定義好的錯誤處理方法告知使用者錯誤產生的具體原因並詢問使用者如何進行下一步操作。Resume Next語句會跳過錯誤行繼續執行下面的程式碼,Resume語句則會嘗試再次執行出錯的程式碼,其次則是直接退出過程結束程式。為了方便程式碼的除錯,我在程式的最開始設定了一個開關,該開關是一個列舉變數,擁有兩個值Release和Debugger,值為Release時設定錯誤陷阱,值為Debugger時不設定錯誤陷阱,這個時候VBA會自動定位到出錯的程式碼行並要求使用者除錯程式碼(事實上使用者根本就不會除錯任何程式碼,這個一般都是留給開發人員來用的)。這樣,每次我只需要修改這個列舉變數的值就可以在除錯模式和非除錯模式之間切換,而不需要每次都註釋掉很多程式碼,畢竟在程式開發過程中除錯是非常重要的,我們往往都需要知道程式究竟發生了什麼。

    這種錯誤處理的方式我在VBA應用中經常會用到,唯一不方便的就是我們需要在每一個可能出錯的過程或方法中設定錯誤陷阱並呼叫錯誤處理函式,可能還要做一些額外的操作,例如上面講到的設定除錯模式的開關、判斷錯誤處理函式的返回值並進行相應的操作等。

    其實,On Error語句除了在VBA中被用來進行錯誤處理外,還有一些特殊的用途,下面是一個例子。

複製程式碼 'Check whether the key in the collection ofPublicFunction KeyExists(col As Collection, Key) AsBooleanOnErrorResumeNextDim x As Variant
    x 
= col.Item(Key)
    KeyExists 
=Not (Err.Number =5)
    
OnErrorGoTo0End Function 複製程式碼

    VBA支援集合型別的物件,如Dim country As New Collection,我們可以往集合物件中新增項或移除項,以及查詢特定的項。但是VBA中的集合物件功能很有限,它居然沒有提供判斷集合中的Key(關鍵字)是否存在的方法。對於集合來說,這個方法非常重要,當你往集合中新增項時,如果Key重複,VBA是會丟擲異常的,因此我們需要自己編寫一個用來檢測集合中的Key是否存在的方法。藉助於On Error和Resume Next語句可以實現這個功能,我們可以反覆查詢集合中的項,如果要查詢的項不存在則會丟擲異常,這時利用Resume Next語句繼續執行下一條語句繼續進行查詢,直到找到要查詢的項。On Error GoTo 0用來關閉錯誤陷阱。

    類似於這樣的應用還有很多,我們可以自己控制當程式出錯時如何進行下一步操作,從而更方便的管理我們自己的應用程式。

    另外,VBA的編輯器也提供了一些輔助功能幫助我們除錯程式碼,如常用的在程式碼中設定斷點、新增和切換書籤,以及執行時的物件監視等,讀者可以自己去體會。