第 14 章 错误处理
第 14 章 错误处理
Lua 的错误处理结合了返回值与异常式机制。可预期的失败可以返回 nil, message 或 nil, errorObject;无法继续执行的错误可以用 error 抛出。
error 与断言
local function divide(a, b)
if b == 0 then
error("division by zero", 2)
end
return a / b
end
error 的第二个参数是层级。1 表示在调用 error 的位置添加位置信息,2 表示指向上层调用者。封装校验函数时通常使用 2:
local function requireString(value, name)
if type(value) ~= "string" then
error(name .. " must be a string", 2)
end
return value
end
assert 在第一个参数为真时返回全部参数,否则抛出第二个参数:
local function openConfig(path)
local file, message = io.open(path, "r")
assert(file, message)
return file
end
不要用 assert 取代所有业务校验。需要恢复或向用户展示具体字段错误时,应返回结构化错误。
pcall 与 xpcall
pcall 调用函数并捕获错误:
local function fail()
error("boom")
end
local ok, message = pcall(fail)
print(ok) -- false
print(message) -- 包含错误信息
成功时 pcall 返回 true 和函数的全部返回值:
local function pair()
return 1, 2
end
local ok, first, second = pcall(pair)
print(ok, first, second)
xpcall 可以在错误传出前调用消息处理器,适合附加堆栈:
local function handleError(errorValue)
return debug.traceback(tostring(errorValue), 2)
end
local ok, trace = xpcall(function()
error("request failed")
end, handleError)
if not ok then
io.stderr:write(trace, "\n")
end
Lua 5.2 及以后,xpcall 可以额外传入参数:
local ok, value = xpcall(string.rep, debug.traceback, "a", 3)
print(ok, value) -- true aaa
错误对象与错误层级
错误值不一定是字符串。结构化错误对象更容易被上层程序处理:
local function validationError(field, message)
return {
code = "VALIDATION_ERROR",
field = field,
message = message
}
end
local ok, failure = pcall(function()
error(validationError("email", "invalid format"))
end)
if not ok then
print(failure.code) -- VALIDATION_ERROR
print(failure.field) -- email
print(failure.message) -- invalid format
end
错误对象不应包含密码、令牌或完整请求正文。日志中需要可追踪的请求 ID,同时保留用户可读的信息。
包装底层错误
local function loadConfig(path)
local file, message = io.open(path, "r")
if not file then
error({
code = "CONFIG_OPEN_FAILED",
path = path,
cause = message
}, 0)
end
local content = file:read("*a")
file:close()
return content
end
level = 0 不添加位置前缀,适合消息已经是结构化对象的场景。
资源清理模式
pcall 中手动清理
local file, message = io.open("data.txt", "r")
if not file then
error(message)
end
local ok, result = pcall(function()
return file:read("*a")
end)
file:close()
if not ok then
error(result, 0)
end
to-be-closed 变量
Lua 5.4 支持 <close> 变量。离开作用域时,包括发生错误时,会调用 __close:
local function withResource()
local resource <close> = setmetatable({}, {
__close = function(_, errorValue)
if errorValue then
print("清理并报告错误")
else
print("正常清理")
end
end
})
print("使用资源")
end
withResource()
自定义文件包装器也可以使用这一机制。__close 自身应尽量不抛错;如果清理失败,应记录并保留原始错误。
可观测的错误处理
生产代码应区分:
-
用户错误:输入缺失、格式错误,返回可展示的信息。
-
预期运行错误:网络断开、文件不存在,允许重试或降级。
-
程序缺陷:不变量被破坏,应记录堆栈并尽快失败。
日志至少包含错误码、操作名称、相关资源标识和请求 ID,不要只输出 "failed"。
不要吞掉错误
-- 不推荐
pcall(function()
saveData()
end)
-- 推荐
local ok, failure = pcall(saveData)
if not ok then
logError("save failed", failure)
return nil, failure
end
如果确实允许忽略错误,也要写明原因,例如清理临时文件失败不影响主流程。