导语:
上周有个读者急吼吼地私信我:“老师,我写了个VBA宏整理半年数据,结果跑到第800行的时候弹出个Error 1004,前功尽弃!有没有办法让宏‘抗摔’?”还有位Python新手更崩溃:“明明写了检查空值的代码,结果由于一个单元格是文本格式,整个脚本直接闪退,半小时白干……”
别慌!这不是你技术菜,而是没掌握编程圈的“保命神技”——错误处理。今天第五讲,我用20年编程经验总结的“防崩指南”,手把手教你让VBA和Python代码从“玻璃心”变“金刚不坏”,从此告别“改不完的bug,流不完的眼泪”!

第五讲:错误处理——让代码“稳如老狗”的终极法则
程序员圈有句扎心真相:“完美的代码不存在,但能优雅处理错误的代码才专业。” 无论是VBA还是Python,错误处理不是“加分项”,而是“生存线”。它能让你在用户骂“这破程序又崩了”时,淡定地说:“我早料到了。”
第一部分:VBA——用“安全网”兜住所有意外
VBA的错误处理逻辑像极了“老司机的应急反应”:预判可能翻车的点,提前铺好安全网,出事了就顺着网爬回安全区。 它的核心工具是On Error语句,这是VBA程序员的“保命符”。
1. 必杀技:On Error GoTo——精准跳转错误处理区
这是VBA最经典的错误捕获模式,相当于给代码装了个“GPS导航”:主逻辑跑着跑着出错了?立刻跳转到指定标签的“急救站”处理。
Sub 批量计算佣金()
' 1. 启动“安全模式”:一旦出错,跳转到ErrorHandler标签
On Error GoTo ErrorHandler
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("数据源") ' 可能出错点1:工作表不存在
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row ' 可能出错点2:A列全空
Dim i As Long, sales As Double, commission As Double
For i = 2 To lastRow
sales = ws.Cells(i, "A").Value
If sales < 0 Then
Err.Raise vbObjectError + 999, , "销售额不能为负!" ' 主动抛错(自定义错误)
End If
commission = sales * 0.1 ' 可能出错点3:sales是文本格式(类型错误)
ws.Cells(i, "B").Value = commission
Next i
MsgBox "计算完成!", vbInformation
Exit Sub ' 正常结束,跳过错误处理区
' 2. 错误处理区(急救站)
ErrorHandler:
' 显示具体错误信息(错误号+描述+来源)
MsgBox "错误号:" & Err.Number & vbCrLf & _
"错误缘由:" & Err.Description & vbCrLf & _
"错误来源:" & Err.Source, vbCritical, "程序异常"
' 可选操作:记录错误日志(写入文本文件)
Open ThisWorkbook.Path & "错误日志.txt" For Append As #1
Print #1, "[" & Now() & "] 错误号:" & Err.Number & ";描述:" & Err.Description
Close #1
' 清除错误状态,避免影响后续代码
Err.Clear
End Sub
这段代码能处理哪些崩溃?
- 如果“数据源”工作表被删除,触发错误号9:下标越界,跳转到处理区并记录日志;
- 如果A列全是空值,lastRow会是1,循环从2到1不执行,但不会报错;
- 如果A列有文本(如“无效数据”),sales = ws.Cells(i, “A”).Value会触发错误号13:类型不匹配,同样被捕获;
- 主动用Err.Raise抛出的自定义错误(如销售额为负),也会被精准捕获。
2. 进阶技巧:On Error Resume Next——小心使用的“免死金牌”
这条语句的意思是:“出错了?别管,继续执行下一句。” 它适合处理“无关痛痒”的小错误(列如删除一个不存在的文件),但新手慎用——用不好会让程序“带病运行”,埋下更大隐患。
Sub 清理临时文件()
On Error Resume Next ' 开启“免死金牌”
Kill "C:Tempold_report.xlsx" ' 文件不存在?忽略错误
Kill "D:Backup emp.xls" ' 路径错误?也忽略
On Error GoTo 0 ' 关闭“免死金牌”,恢复严格错误提示
MsgBox "临时文件清理完成(可能有遗漏)", vbInformation
End Sub
✔️ VBA错误处理哲学: 像老司机开车——提前检查路况(预判错误点),出事故立刻靠边停车(跳转处理区),修好后继续上路(清除错误状态)。
第二部分:Python——用“精密陷阱”捕获所有异常
Python的错误处理更像“工程师设计精密仪器”:提前预判所有可能的故障点,为每种故障定制“专属陷阱”,程序跑着跑着出错了?陷阱自动触发,稳稳接住! 它的核心是
try…except…else…finally结构,这是Python程序员的“防崩秘籍”。
1. 四大利器:
try…except…else…finally
- try:放可能出错的“危险代码”;
- except:针对不同错误类型(如除零、文件不存在)定制陷阱;
- else:如果try里的代码没出错,执行这里的“成功逻辑”;
- finally:无论是否出错,最后必定要执行的“收尾动作”(如关闭文件、释放资源)。
import xlwings as xw
import logging # 导入日志模块,专业记录错误
# 配置日志:将错误写入文件(比弹窗更专业)
logging.basicConfig(filename='error.log', level=logging.ERROR,
format='%(asctime)s - %(levelname)s - %(message)s')
def 计算佣金(sales):
try:
# 危险操作1:读取Excel单元格(可能为空、非数值)
if sales < 0:
raise ValueError("销售额不能为负数!") # 主动抛自定义异常
return sales * 0.1
except ZeroDivisionError:
logging.error("除零错误:销售额为0,无法计算佣金")
return 0
except TypeError as e:
logging.error(f"类型错误:输入应为数值,实际是{type(sales)},错误详情:{e}")
return None
except ValueError as e:
logging.error(f"自定义错误:{e}")
return None
except Exception as e: # 兜底陷阱,捕获所有未预期的错误
logging.error(f"未知错误:{e}", exc_info=True) # 记录完整堆栈信息
return None
else:
logging.info("佣金计算成功") # 仅当无错误时执行
finally:
print("佣金计算流程结束(无论成功与否)") # 必执行的收尾动作
# 主程序:循环处理数据
def 批量处理():
try:
wb = xw.books.open("销售数据.xlsx") # 危险操作2:打开不存在的文件
sheet = wb.sheets["数据"]
last_row = sheet.range("A" + str(sheet.cells.last_cell.row)).end("up").row
for row in range(2, last_row + 1):
sales = sheet.range(f"A{row}").value
commission = 计算佣金(sales)
sheet.range(f"B{row}").value = commission
except FileNotFoundError:
logging.critical("文件未找到:请检查'sales数据.xlsx'路径是否正确")
except PermissionError:
logging.critical("权限不足:请关闭Excel文件后重试")
except Exception as e:
logging.critical(f"批量处理失败:{e}", exc_info=True)
finally:
try:
wb.close() # 无论是否出错,尝试关闭工作簿
except:
pass # 如果wb未成功打开,忽略此错误
批量处理()
这段代码有多“抗造”?
- 文件不存在?触发FileNotFoundError,记录日志并提示;
- 权限不足?触发PermissionError,避免程序崩溃;
- 销售额是文本?触发TypeError,记录类型错误详情;
- 销售额为负?触发自定义ValueError,明确提示缘由;
- 所有操作结束后,无论成功与否,finally都会尝试关闭工作簿,防止资源泄露。
2. 高手进阶:自定义异常类——让错误“更懂你”
Python允许你自定义异常类型,让错误信息更贴合业务场景。列如电商系统中,定义库存不足异常、支付超时异常,代码会更清晰。
class 库存不足异常(Exception):
"""当商品库存小于订单数量时抛出的异常"""
def __init__(self, 商品名称, 当前库存, 订单数量):
self.商品名称 = 商品名称
self.当前库存 = 当前库存
self.订单数量 = 订单数量
super().__init__(f"{商品名称}库存不足!当前库存{当前库存},订单需要{订单数量}")
# 使用自定义异常
def 下单(商品名称, 当前库存, 订单数量):
if 订单数量 > 当前库存:
raise 库存不足异常(商品名称, 当前库存, 订单数量)
print("下单成功!")
# 测试
try:
下单("手机", 10, 15)
except 库存不足异常 as e:
print(f"下单失败:{e}") # 输出:下单失败:手机库存不足!当前库存10,订单需要15
✔️ Python错误处理哲学: 像工程师设计电路——每个可能的故障点都有对应的保险丝(except),主电路(try)负责正常工作,保险丝熔断(捕获错误)时启动备用方案(处理逻辑),最后无论是否熔断都要切断总电源(finally)。
核心实战:用错误处理“复活”崩溃脚本
场景: 你需要写一个脚本,从Excel读取员工绩效分,计算奖金(绩效分×1000),并将结果写入新工作表。但实际数据中可能存在:
- 绩效分是文本(如“优秀”);
- 绩效分超过100(系统限制最高100);
- 目标工作表已存在(需先删除旧表)。
VBA“防崩版”解法:
Sub 生成奖金表()
On Error GoTo ErrorHandler
Dim wsSource As Worksheet, wsTarget As Worksheet
Set wsSource = ThisWorkbook.Sheets("绩效数据")
Dim lastRow As Long: lastRow = wsSource.Cells(wsSource.Rows.Count, "A").End(xlUp).Row
' 检查目标表是否存在,存在则删除(可能出错点:工作表被保护)
On Error Resume Next
Application.DisplayAlerts = False ' 关闭删除确认弹窗
ThisWorkbook.Sheets("奖金表").Delete
Application.DisplayAlerts = True
On Error GoTo ErrorHandler
' 创建新目标表
Set wsTarget = ThisWorkbook.Sheets.Add(After:=ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count))
wsTarget.Name = "奖金表"
wsTarget.Range("A1:B1").Value = Array("姓名", "奖金")
' 计算奖金
For i = 2 To lastRow
Dim 姓名 As String, 绩效分 As Double, 奖金 As Double
姓名 = wsSource.Cells(i, "A").Value
绩效分 = wsSource.Cells(i, "B").Value
' 检查绩效分是否为数值
If Not IsNumeric(绩效分) Then
Err.Raise vbObjectError + 100, , "绩效分必须为数值!"
End If
' 检查绩效分是否超上限
If 绩效分 > 100 Then
Err.Raise vbObjectError + 101, , "绩效分不能超过100!"
End If
奖金 = 绩效分 * 1000
wsTarget.Cells(i, "A").Value = 姓名
wsTarget.Cells(i, "B").Value = 奖金
Next i
MsgBox "奖金表生成成功!", vbInformation
Exit Sub
ErrorHandler:
MsgBox "错误:" & Err.Description & vbCrLf & "请联系管理员。", vbCritical
' 记录日志(同上,此处省略)
Err.Clear
End Sub
Python“防崩版”解法:
import xlwings as xw
import logging
logging.basicConfig(filename='奖金表错误.log', level=logging.ERROR)
def 生成奖金表():
try:
wb = xw.books.open("绩效数据.xlsx")
ws_source = wb.sheets["绩效数据"]
last_row = ws_source.range("A" + str(ws_source.cells.last_cell.row)).end("up").row
# 删除已存在的奖金表(可能出错点:表被保护/不存在)
try:
wb.sheets["奖金表"].delete()
except:
pass # 表不存在时忽略
# 创建新奖金表
ws_target = wb.sheets.add("奖金表")
ws_target.range("A1:B1").value = ["姓名", "奖金"]
# 计算奖金
for row in range(2, last_row + 1):
姓名 = ws_source.range(f"A{row}").value
绩效分 = ws_source.range(f"B{row}").value
# 检查绩效分是否为数值
if not isinstance(绩效分, (int, float)):
raise TypeError(f"第{row}行绩效分非数值:{绩效分}")
# 检查绩效分是否超上限
if 绩效分 > 100:
raise ValueError(f"第{row}行绩效分超上限:{绩效分}")
ws_target.range(f"A{row}").value = 姓名
ws_target.range(f"B{row}").value = 绩效分 * 1000
wb.save("奖金表.xlsx")
wb.close()
print("奖金表生成成功!")
except FileNotFoundError:
logging.error("绩效数据.xlsx未找到,请检查路径")
except PermissionError:
logging.error("文件被占用,请关闭后重试")
except (TypeError, ValueError) as e:
logging.error(f"数据错误:{e}")
except Exception as e:
logging.error(f"未知错误:{e}", exc_info=True)
finally:
try:
wb.close() # 确保关闭工作簿
except:
pass
生成奖金表()
终极对比:VBA与Python的错误处理谁更强?
|
特性 |
VBA |
Python |
|
错误捕获方式 |
On Error GoTo跳转 |
try…except精准捕获 |
|
错误类型支持 |
仅内置错误号(如Error 1004) |
支持自定义异常类,类型更灵活 |
|
日志记录 |
需手动写文件(麻烦) |
内置logging模块(专业) |
|
代码可读性 |
错误处理与业务逻辑混杂(易乱) |
异常块独立(结构清晰) |
|
适用场景 |
小范围、单一功能的Excel宏 |
复杂系统、需要多人协作的脚本 |
|
比喻 |
老司机的“应急工具箱” |
工程师的“精密故障排查系统” |
千万别学Excel结语:错误处理,是程序员的“职业尊严”
从今天起,请记住:
- 写VBA时,每个Sub和Function的第一行都加上On Error GoTo,别让崩溃成为习惯;
- 写Python时,每个try块都明确列出可能出现的异常类型,别让“万能except”毁了代码的可维护性;
- 无论用什么语言,日志记录都是你的“后悔药”——出错了没关系,但至少要知道怎么错的。
最后送你一句话: 用户不会由于你处理了错误而夸你,但必定会由于你没处理错误而骂你。而专业的程序员,连“被骂的机会”都不给自己。
互动话题: 你在写代码时遇到过最离谱的崩溃是什么?是由于单元格格式错了?还是文件被同事误删了?在评论区分享你的“血泪史”!
Excel #Python #VBA #错误处理 #编程技巧 #办公自动化 #代码健壮性
【测试题】3道题测测你的错误处理水平
- VBA中,用于跳转到错误处理代码块的关键字是? A. On Error Resume Next B. On Error GoTo C. Exit Sub D. Err.Clear
- Python中,若要捕获“文件未找到”错误,应使用哪个异常类? A. TypeError B. ValueError C. FileNotFoundError D. ZeroDivisionError
- 以下哪项是错误处理的核心目的? A. 让代码永远不报错 B. 让程序崩溃时更美观 C. 在出错时优雅应对,减少损失 D. 提高代码运行速度
【答案】
- B(On Error GoTo用于跳转到错误处理标签)
- C(FileNotFoundError专门捕获文件未找到错误)
- C(错误处理的目的是在出错时减少损失,而非完全避免错误)
欢迎关注:千万别学excel,这里有数千篇excel相关视频和文章,带你玩转excel!祝你早日升任CEO,迎娶白富美,走上人生巅峰~( ̄▽ ̄~)~






崩溃要么是程序有问题,要么就是数据量太大,使用的VBA方法已经不能轻松胜任了
代码写的不好.换python也一样
收藏了,感谢分享