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