强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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、栈遍历

下一章:文件与 I/O / File & System I/O