有句俗話說的好,一段優(yōu)秀的代碼,三分靠編寫七分靠調(diào)試。今天我就給大家聊一下VBA代碼調(diào)試的問題:一段代碼寫完了,運(yùn)算結(jié)果卻不對,到底應(yīng)該如何發(fā)現(xiàn)并改正錯誤?
本章概要如下:
? 語法檢查
? 邏輯檢查
? 樣本調(diào)式
? 運(yùn)行調(diào)試
? 錯誤處理
1、語法錯誤
對于新手而言,初期編寫的VBA代碼并不會有復(fù)雜的邏輯,最常見的錯誤就是語法錯誤。
典型有以下3種。
1) 聲明對象變量,漏了關(guān)鍵字Set。
Sub t()
Dim sht As Worksheet
sht = Worksheets(1)
MsgBox sht.Name
End Sub
以上代碼第3行將第1個工作表賦值變量sht,但由于并未使用關(guān)鍵字Set,代碼會返回下圖所示的錯誤信息:對象變量或with塊變量未設(shè)置。
正確代碼如下:
Sub t()
Dim sht As Worksheet
Set sht = Worksheets(1)
MsgBox sht.Name
End Sub
2)循環(huán)或判斷語句不完整。
當(dāng)有多層循環(huán)語句或者條件判斷語句嵌套時,新手朋友容易遺漏Next或者End If語句。需要注意的是,當(dāng)If語句嵌套在循環(huán)語句中時,如果缺少End If,系統(tǒng)會提示”編譯錯誤,Next沒有For”。這提示張冠李戴的不要太明顯。
以下代碼缺失End If語句。
Sub t3()
Dim sht As Worksheet
For Each sht In Worksheets
If sht.Name = "看見星光" Then
If sht.Cells(1, 1) = "excel" Then
MsgBox "對"
'這里少了End If 你發(fā)現(xiàn)了嗎?
Next
End Sub
運(yùn)行后提示錯誤如下:
解決此類錯誤,最好是養(yǎng)成代碼縮進(jìn)與提前輸入結(jié)構(gòu)語句的習(xí)慣。關(guān)于代碼縮進(jìn)的規(guī)則,VBA系列教程里有詳細(xì)的講述,這里不再啰嗦。而輸入結(jié)構(gòu)語句是指…
寫了For語句后,立刻空兩行寫Next語句,再在循環(huán)體中編寫其它語句。
For Each sht In Worksheets
Next
寫了If語句后,也空兩行寫End If語句。
If sht.Name = "看見星光" Then
End If
3)工作表對象缺失
這個錯誤基本上每個VBA學(xué)員都遇見。
有段代碼如下:
代碼看不全可以左右滑動…
Sub t4()
Dim arr
arr = Worksheets(1).Range("a1:b" & Cells(Rows.Count, 1).End(xlUp).Row)
End Sub
第3行代碼將第1個工作表的A:B列數(shù)據(jù)區(qū)域賦值數(shù)組arr。其中
Cells(Rows.Count, 1).End(xlUp).Row部分,
本意是返回Worksheets(1)第1列最后一個存在數(shù)據(jù)的單元格的行號,這代碼看起來似乎正常無誤。
但是,我們在VBA教程里講過,如果單元格前未指定工作表對象,則默認(rèn)為當(dāng)前活動工作表——當(dāng)前活動工作表,未必就是Worksheets(1),代碼運(yùn)行后,arr數(shù)組也就未必會返回正確的結(jié)果。
正確代碼參考如下:
Sub t4()
Dim arr
With Worksheets(1)
arr = .Range("a1:b" & .Cells(Rows.Count, 1).End(xlUp).Row)
End With
End Sub
注意Cells前有個.代表With所引用的Worksheets(1)對象。
2、邏輯錯誤
相比于語法錯誤,麻煩的是邏輯錯誤。
代碼運(yùn)算的邏輯,有些來源于數(shù)據(jù)分析與處理的基本邏輯,有些來源于公司的業(yè)務(wù)邏輯。對于后者,往往只有行業(yè)內(nèi)的人才能通過你的描述快速理解。
這時就有可能發(fā)生這樣的情景:有的朋友發(fā)出來一段代碼,也不說運(yùn)算邏輯,就問為什么代碼運(yùn)行后不提示錯誤,但結(jié)果并不對……
坦白的說,這種行為就給有人問為什么輸入公式1+1不提示錯誤,但結(jié)果也不等于預(yù)想的3,差不了多少——就讓人很無語。
如何梳理邏輯錯誤呢?
首先,正如我們一直強(qiáng)調(diào)的,所謂編程,就是順序、分支和循環(huán)。順序就是運(yùn)算的先后順序,分支就是運(yùn)算的條件層次,循環(huán)就是遍歷數(shù)據(jù),所以請養(yǎng)成做思維導(dǎo)圖的習(xí)慣,通過思維導(dǎo)圖梳理清楚代碼運(yùn)算的順序和條件層次——相信我,這非常有助于你快速而準(zhǔn)確的編寫代碼。
然后,在代碼中盡量增加注釋。注釋的好處我們在VBA系列教程中編寫VBA代碼有哪些注意事項(xiàng)里有詳細(xì)解釋,像我這么傲驕的人,這里不再重復(fù),你懂得圖片。
最后,請繼續(xù)往下看(*^▽^*)
3、樣本調(diào)試
不論是檢查代碼的語法錯誤還是邏輯錯誤,都離不開樣本調(diào)試;也就是用一個樣本數(shù)據(jù)逐步運(yùn)行代碼,發(fā)現(xiàn)并修正錯誤。上面這句話包含了兩個重點(diǎn)詞匯:樣本數(shù)據(jù)、逐步運(yùn)行。
樣本數(shù)據(jù)要求小而全。
小是指數(shù)據(jù)量必須小,比如,你需要從如上圖所示的10萬行數(shù)據(jù)中查找A列包含關(guān)鍵字”上海”、”福建”、”廣東”,同時B列性別等于男的結(jié)果表,你不能拿10萬個數(shù)據(jù)一個一個去測試,這樣你不是風(fēng)兒也是沙;實(shí)際上,有3條左右的樣本數(shù)據(jù)就足夠了。
全是指數(shù)據(jù)的代表性需全面,依然以上圖所示數(shù)據(jù)為例,C列的性別就不能只有男的,沒有女的,當(dāng)然,也不能只有女的,沒有男的。
參考代碼如下:
Sub t()
Dim aData, aRes, aRef, s
Dim i As Long, j As Long, k As Long
aData = Worksheets("數(shù)據(jù)源").Range("a1").CurrentRegion
ReDim aRes(1 To UBound(aData), 1 To UBound(aData, 2))
aRef = Array("上海", "福建", "廣東")
For i = 1 To UBound(aData)
If aData(i, 3) = "男" Then '判斷性別是否為男
For Each s In aRef '判斷是否包含城市關(guān)鍵字
If InStr(aData(i, 1), s) Then
k = k + 1
For j = 1 To UBound(aData, 2)
aRes(k, j) = aData(i, j)
Next
Exit For '退出循環(huán)
End If
Next
End If
Next
Worksheets("結(jié)果表").Select
Cells.ClearContents
Range("a1").Resize(1, UBound(aData, 2)) = aData '讀取標(biāo)題
Range("a2").Resize(k, UBound(aRes, 2)) = aRes
MsgBox "ok"
End Sub
4、代碼調(diào)試
重點(diǎn)說一下代碼逐步調(diào)試,這包含了逐語句調(diào)試、斷點(diǎn)調(diào)試等情況。
逐語句調(diào)試是指以語句為單位分步運(yùn)行代碼。按一次鍵,VBA將運(yùn)行當(dāng)前過程,然后高亮顯示下一個語句并進(jìn)入中斷模式。按多次鍵,即可逐語句運(yùn)行代碼。
當(dāng)代碼逐語句運(yùn)行時,我們可以通過本地窗口,實(shí)時查看變量內(nèi)容是否符合計(jì)算預(yù)期。
斷點(diǎn)調(diào)試就是在程序中設(shè)置代碼暫時停止運(yùn)行的位置,這個位置被稱為斷點(diǎn)。當(dāng)代碼運(yùn)行到斷點(diǎn)所在的語句時,程序會進(jìn)入中斷模式,同時高亮顯示斷點(diǎn)代碼行。
設(shè)置斷點(diǎn)最常用的方法是將鼠標(biāo)指針懸停在【代碼窗口】左側(cè)灰色區(qū)域內(nèi),當(dāng)鼠標(biāo)指針顯示為指向左上方的箭頭時,單擊即可設(shè)置該代碼行為斷點(diǎn)。
斷點(diǎn)設(shè)置完成后,會出現(xiàn)一個紅色大圓點(diǎn),單擊該斷點(diǎn)標(biāo)識,即可刪除斷點(diǎn)。
斷點(diǎn)可以存在多個,如果存在斷點(diǎn),按鍵后,VBA將運(yùn)行代碼直至斷點(diǎn)處進(jìn)入中斷模式。此時,通過本地窗口,或搭配運(yùn)行MsgBox語句,可以查看代碼中的變量值是否運(yùn)行有誤。
除此之外,使用Stop語句也可以實(shí)現(xiàn)斷點(diǎn)調(diào)試的效果。
以上述代碼為例,如果需要查看變量K的累加過程,可以在語句k=k+1后添加一行Stop語句,代碼運(yùn)行到Stop語句時將自動進(jìn)入中斷模式,再通過本地窗口,即可查看相關(guān)變量的數(shù)據(jù)。
不管是逐語句調(diào)試還是斷點(diǎn)調(diào)試,都是為了查看代碼的運(yùn)算過程,以及變量的值是否正確。
查看變量優(yōu)先推薦使用本地窗口,但有時候本地窗口的變量過多,如果只是查看個別變量,使用起來就不是很方便,相比之下,使用Msgbox語句更為合適。
以上述案例為例,如果需要查看第1條符合查詢規(guī)則的行號,可以在If判斷語句后增加以下兩行代碼。
…
MsgBox i
Stop
….
代碼運(yùn)行后返回結(jié)果如下圖所示。
本地窗口和Msgbox語句都是顯示某個運(yùn)算環(huán)境下的特定值,如果需要查看特定變量在整個過程中的全部值,可以使用Debug對象的Print方法,該方法可以在【立即窗口】打印不同類型的數(shù)據(jù)。
還是舉個例子。
在第10行If語句后,增加一行Debug.Print (i)語句,然后運(yùn)行過程,可以在立即窗口查看所有符合條件的所有行號。
Debug.Print 比較常用的一個情景是測試不同代碼的運(yùn)行速度。
以下代碼測試了將10000個數(shù)據(jù)寫入工作表的兩種方式的時間差異,這兩種方式一個是逐個單元格寫入,另一個是數(shù)組批量寫入。
Sub t2()
Dim i As Long, arr, t
t = Timer
For i = 1 To 10000 '在1萬個單元格中寫入數(shù)據(jù)
Cells(i, 1) = i
Next
Debug.Print ("逐個單元格寫入的時間是:" & Timer - t)
t = Timer
ReDim arr(1 To 10000, 1 To 1)
For i = 1 To 10000
arr(i, 1) = i
Next
Range("b1:b" & UBound(arr)) = arr
Debug.Print ("數(shù)組寫入的時間是:" & Timer - t)
End Sub
運(yùn)行代碼后結(jié)果如下圖所示:
最后需要補(bǔ)充說明兩點(diǎn):
1)當(dāng)過程重復(fù)運(yùn)行時,立即窗口的內(nèi)容并不會自動清除。
2)除了將變量數(shù)據(jù)寫入立即窗口,也可以將其寫入工作表中,兩者各有優(yōu)劣,看個人習(xí)慣和實(shí)際需求。
5、錯誤處理
無論你如何認(rèn)真的編寫代碼,程序運(yùn)行時仍然有可能出現(xiàn)錯誤,這也許會讓初學(xué)編程的你感到困惑,但從某種角度來說,錯誤確實(shí)是程序不可或缺的一部分,所以請?zhí)善轿⑿γ鎸﹀e誤,并堅(jiān)定不移的抱有三種態(tài)度:忽視它、捕捉它、反饋它。
使用On Error Resume Next語句,可以忽視程序中的錯誤,繼續(xù)運(yùn)行錯誤語句后的代碼。
以下代碼刪除名稱為”數(shù)據(jù)”的工作表。為了防止工作簿不存在相關(guān)名稱的工作表,造成第4行刪除工作表的代碼運(yùn)行錯誤,第3行代碼使用容錯語句。
Sub t3()
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("數(shù)據(jù)").Delete
MsgBox "名稱為數(shù)據(jù)的工作表已刪除"
Application.DisplayAlerts = True
End Sub
捕捉和反饋錯誤可以使用Err對象。
舉個例子,還是刪除名稱為”數(shù)據(jù)”的工作表,示例代碼如下:
Sub t4()
Dim d As Object
Application.DisplayAlerts = False
Set d = CreateObject("scripting.dictionary") '演示釋放變量
On Error GoTo ErrHander
Worksheets("數(shù)據(jù)").Delete
MsgBox "名稱為數(shù)據(jù)的工作表已刪除"
Application.DisplayAlerts = True
errExit:
Set d = Nothing
Exit Sub
ErrHander:
MsgBox "程序發(fā)生錯誤。" & vbCrLf & _
"錯誤編號:" & Err.Number & vbCrLf & _
"錯誤內(nèi)容:" & Err.Description
Resume errExit
End Sub
第5行代碼是On Error GoTo line語句。它可以跳轉(zhuǎn)到指定的錯誤處理程序入口,line代表代碼行標(biāo)簽或行號,本例為ErrHander。
第12至第16行代碼是ErrHander標(biāo)簽。第14行代碼使用Err對象的Number屬性返回錯誤的編號,第15行代碼使用Err對象的Description返回錯誤的描述內(nèi)容(這描述大部分時候不講人話,如下圖所示,就湊合用吧)。
第9至第11行代碼是errExit標(biāo)簽,作用是釋放指定對象的內(nèi)存。
使用Err.Number屬性可以判斷程序是否存在錯誤。
以下代碼刪除名稱為”數(shù)據(jù)”的工作表。如果不存在相關(guān)工作表,則告知用戶。
Sub t5()
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("數(shù)據(jù)").Delete
If Err.Number = 0 Then
MsgBox "名稱為數(shù)據(jù)的工作表已刪除"
Else
MsgBox "不存在名稱為數(shù)據(jù)的工作表"
End If
Application.DisplayAlerts = True
End Sub
第5行代碼判斷當(dāng)前程序是否存在錯誤,當(dāng)程序不存在錯誤時,Err的Number屬性為0。當(dāng)程序存在錯誤時,Number屬性可能是正數(shù)也可能是負(fù)數(shù),有學(xué)員將判斷條件寫成Err.Number > 0是錯誤的。
最后,使用Err.Clear可以清除Err對象的所有屬性,即清除錯誤。
假設(shè)需要刪除工作表名稱為”工作表1″, “工作表2”, “工作表3″,并將刪除的和不存在的分別彈窗告訴用戶,可以參考下代碼遍歷刪除。
Sub t6()
Dim aData, strName
Dim strDelName As String, strErrName As String
On Error Resume Next
Application.DisplayAlerts = False
aData = Array("工作表1", "工作表2", "工作表3")
For Each strName In aData
Err.Clear
Worksheets(strName).Delete
If Err.Number = 0 Then
strDelName = strDelName & "," & strName
Else
strErrName = strErrName & "," & strName
End If
Next
MsgBox "以下工作表已刪除:" & vbCrLf & Mid(strDelName, 2) & vbCrLf & _
"以下工作表不存在:" & vbCrLf & Mid(strErrName, 2)
Application.DisplayAlerts = True
End Sub
第4行代碼忽視程序運(yùn)行中的錯誤。
第8行代碼在每次刪除工作表前都清除Err對象的所有屬性。第10行代碼判斷Err對象的編號是否為0,如果為0,說明工作表成功刪除,否則,就假設(shè)工作簿中不存在相關(guān)工作表(攤手,是的,事實(shí)上,也有可能是工作簿結(jié)構(gòu)被保護(hù)了)。
代碼運(yùn)行后返回結(jié)果如下:
6、小結(jié)
同志們吶,代碼調(diào)試是一個需要保持耐心和細(xì)心的過程,這里重復(fù)一句話(小學(xué)老師說過這叫首尾呼應(yīng)),一段優(yōu)秀的代碼三分在編寫七分在調(diào)試,寫一段代碼你可能只需要十分鐘,而調(diào)試卻需要1小時——這都是很正常的。最后,用大老板的一句話勉勵大家:
“唯有不忘初心、牢記使命,戒驕戒躁、砥礪前行,方能行穩(wěn)致遠(yuǎn)!
承擔(dān)因您的行為而導(dǎo)致的法律責(zé)任,
本站有權(quán)保留或刪除有爭議評論。
參與本評論即表明您已經(jīng)閱讀并接受
上述條款。