12 - 错误处理 / Error Handling
错误处理 / Error Handling
Lua 没有 try/catch,但有 error() + pcall() + xpcall(),简洁且灵活。
Lua has no try/catch, but error() + pcall() + xpcall() are clean and flexible.
🟢 基础 / Basics
1. error() — 抛出错误
-- error(message, level)
-- level: 0=不报告位置, 1=调用者(默认), 2=调用者的调用者
local function divide(a, b)
if b == 0 then
error("Division by zero!", 2)
end
return a / b
end
-- 这会终止程序:
-- divide(10, 0)
-- stdin:3: Division by zero!
2. pcall() — 安全调用
-- pcall(func, arg1, arg2, ...)
-- 返回 ok, result_or_error
local ok, result = pcall(divide, 10, 0)
if ok then
print("Result: " .. result)
else
print("Error: " .. result) -- Error: stdin:3: Division by zero!
end
-- pcall 也可以捕获字符串错误
local ok, err = pcall(function()
error("something broke")
end)
print(ok, err) -- false something broke
3. xpcall() — 带错误处理函数
-- xpcall(func, errfunc, arg1, arg2, ...)
-- errfunc 可以转换/记录错误
local ok, err = xpcall(
function() return divide(10, 0) end,
function(e)
return "Caught: " .. tostring(e) .. "\n" .. debug.traceback()
end
)
if not ok then
print(err)
-- Caught: stdin:3: Division by zero!
-- stack traceback:
-- ...
end
🟡 进阶 / Intermediate
1. assert() — 快速检查
-- assert(v, message) 如果 v 为 false/nil 则 error
local function readFile(path)
local file = assert(io.open(path, "r"))
local content = assert(file:read("*a"))
file:close()
return content
end
-- 等价于:
-- local file = io.open(path, "r")
-- if not file then error("Cannot open: " .. path) end
2. 保护模式下的限制 / Restrictions in Protected Mode
-- pcall 内部调用的函数不能 yield
-- 在 Lua 5.1/5.2 中,pcall 内的 yield 会报错
-- Lua 5.2+ 中可以使用 coroutine.resume 代替 pcall
-- Lua 5.4 的解决方案:
local co = coroutine.create(function()
local ok, err = pcall(function()
coroutine.yield() -- 在 Lua 5.4+ 中可以正常工作
end)
end)
3. 错误传播策略 / Error Propagation Strategy
-- 策略一:尽早失败 (fail fast)
local function process(data)
assert(data, "data is nil")
assert(type(data.name) == "string", "name must be string")
assert(data.age > 0, "age must be positive")
-- ...
end
-- 策略二:返回 nil + 错误消息(类似 Go)
local function safeProcess(data)
if not data then return nil, "data is nil" end
if type(data.name) ~= "string" then return nil, "name must be string" end
return true
end
local ok, err = safeProcess(nil)
if not ok then print("Failed: " .. err) end
-- 策略三:错误对象(Lua 5.4 table error)
local function divide(a, b)
if b == 0 then
error({
code = "DIV_BY_ZERO",
message = "Cannot divide by zero",
args = {a = a, b = b},
})
end
return a / b
end
local ok, err = pcall(divide, 10, 0)
if not ok and type(err) == "table" then
print("Error code:", err.code)
print("Message:", err.message)
end
🔴 高级 / Advanced
1. 自定义错误处理器 / Custom Error Handler
-- 统一的错误处理框架
local ErrorHandler = {}
function ErrorHandler.wrap(fn)
return function(...)
local ok, result = xpcall(fn, debug.traceback, ...)
if ok then
return result
else
ErrorHandler.log(result)
return nil, result
end
end
end
function ErrorHandler.log(traceback)
-- 可以发送到日志系统、告警等
io.stderr:write("[ERROR] " .. os.date() .. "\n")
io.stderr:write(traceback .. "\n")
end
-- 使用
local safeDivide = ErrorHandler.wrap(divide)
local result, err = safeDivide(10, 0)
2. debug.traceback 的工作原理
-- debug.traceback 遍历调用栈,收集每一帧的信息
-- 每帧包含:函数名、源文件、行号
-- 手动遍历调用栈
local function dumpStack()
local level = 1
while true do
local info = debug.getinfo(level, "Sln")
if not info then break end
print(string.format(
" [%d] %s:%d in %s",
level,
info.short_src or "?",
info.currentline or -1,
info.name or "?"
))
level = level + 1
end
end
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | error()、pcall()、xpcall()、assert() |
| 🟡 进阶 | 返回 nil+err 模式、table error、保护模式限制 |
| 🔴 高级 | 错误处理框架、debug.traceback、栈遍历 |