<返回目录     Powered by claud/xia兄

第13课: 错误处理

error函数

错误处理机制原理:

Lua的错误处理基于"非局部跳转"(non-local goto)机制。当调用error()时,Lua会沿着调用栈向上查找pcall/xpcall保护。如果找到,就跳转到对应的错误处理器;如果没有找到保护,就终止程序。错误对象可以是任何类型,但通常是字符串或table。错误级别参数控制错误消息的显示位置:0(默认,在error位置)、1(在调用error的位置)、2(在调用者的位置)。

-- 基本错误抛出
function divide(a, b)
    if b == 0 then
        error("除数不能为0")
    end
    return a / b
end

-- divide(10, 0)  -- 报错: 除数不能为0

-- 指定错误级别
function checkAge(age)
    if age < 0 then
        error("年龄不能为负数", 2)  -- 2表示调用者的位置
    end
end

-- 使用table作为错误对象
function createError(code, message)
    return {
        code = code,
        message = message,
        timestamp = os.time()
    }
end

function validate(data)
    if not data.name then
        error(createError(400, "缺少name字段"))
    end
    if not data.age then
        error(createError(400, "缺少age字段"))
    end
end

-- 捕获table错误
local ok, err = pcall(validate, {name = "张三"})
if not ok then
    print("错误代码:", err.code)
    print("错误消息:", err.message)
    print("时间戳:", os.date("%Y-%m-%d %H:%M:%S", err.timestamp))
end

assert函数

-- assert用于条件检查
function openFile(filename)
    local file = assert(io.open(filename, "r"), "文件打开失败: " .. filename)
    return file
end

-- 等价于
function openFile(filename)
    local file, err = io.open(filename, "r")
    if not file then
        error("文件打开失败: " .. filename)
    end
    return file
end

-- 使用示例
local file = assert(io.open("test.txt", "r"))
local content = file:read("*a")
file:close()

-- 检查参数
function setAge(age)
    assert(type(age) == "number", "年龄必须是数字")
    assert(age >= 0 and age <= 150, "年龄范围无效")
    return age
end

pcall保护调用

-- pcall捕获错误
function riskyOperation()
    error("出错了!")
end

local status, err = pcall(riskyOperation)
if status then
    print("成功")
else
    print("错误:", err)
end

-- 带参数的pcall
function divide(a, b)
    if b == 0 then
        error("除数不能为0")
    end
    return a / b
end

local status, result = pcall(divide, 10, 2)
if status then
    print("结果:", result)  -- 5
else
    print("错误:", result)
end

local status, result = pcall(divide, 10, 0)
if status then
    print("结果:", result)
else
    print("错误:", result)  -- 除数不能为0
end

xpcall高级错误处理

-- 自定义错误处理函数
function errorHandler(err)
    print("捕获到错误:", err)
    print("调用栈:")
    print(debug.traceback())
    return "处理后的错误信息"
end

function riskyFunction()
    local function inner()
        error("内部错误")
    end
    inner()
end

local status, result = xpcall(riskyFunction, errorHandler)
print("状态:", status)
print("结果:", result)

-- 简化的错误处理
local status, result = xpcall(
    function()
        return divide(10, 0)
    end,
    function(err)
        return "计算错误: " .. err
    end
)
print(result)

调试库debug

-- 获取调用栈信息
function showStack()
    print(debug.traceback())
end

function level3()
    showStack()
end

function level2()
    level3()
end

function level1()
    level2()
end

level1()

-- 获取函数信息
function myFunction(a, b)
    local info = debug.getinfo(1)
    print("函数名:", info.name)
    print("行号:", info.currentline)
    print("源文件:", info.source)
end

myFunction(1, 2)

-- 获取局部变量
function inspectLocals()
    local x = 10
    local y = 20

    local i = 1
    while true do
        local name, value = debug.getlocal(1, i)
        if not name then break end
        print(name, "=", value)
        i = i + 1
    end
end

inspectLocals()

自定义错误类

-- 创建错误类
ValidationError = {}
ValidationError.__index = ValidationError

function ValidationError:new(message, field)
    local obj = {
        message = message,
        field = field,
        type = "ValidationError"
    }
    setmetatable(obj, ValidationError)
    return obj
end

function ValidationError:__tostring()
    return string.format("[%s] %s: %s", self.type, self.field, self.message)
end

-- 使用自定义错误
function validateUser(user)
    if not user.name or user.name == "" then
        error(ValidationError:new("名字不能为空", "name"))
    end
    if not user.age or user.age < 0 then
        error(ValidationError:new("年龄无效", "age"))
    end
end

-- 捕获并处理
local status, err = pcall(validateUser, {name = "", age = 25})
if not status then
    if type(err) == "table" and err.type == "ValidationError" then
        print("验证错误:", err.field, err.message)
    else
        print("其他错误:", err)
    end
end

错误恢复策略

-- 重试机制
function retry(func, maxAttempts, delay)
    local attempts = 0
    while attempts < maxAttempts do
        local status, result = pcall(func)
        if status then
            return true, result
        end
        attempts = attempts + 1
        if attempts < maxAttempts then
            print("重试 " .. attempts .. "/" .. maxAttempts)
            if delay then
                os.execute("sleep " .. delay)
            end
        end
    end
    return false, "达到最大重试次数"
end

-- 使用
local success, result = retry(
    function()
        -- 模拟可能失败的操作
        if math.random() > 0.7 then
            return "成功"
        else
            error("操作失败")
        end
    end,
    3,  -- 最多重试3次
    1   -- 每次延迟1秒
)

if success then
    print("操作成功:", result)
else
    print("操作失败:", result)
end

安全调用包装器

-- 创建安全包装器
function safe(func)
    return function(...)
        local status, result = pcall(func, ...)
        if status then
            return result
        else
            print("错误:", result)
            return nil
        end
    end
end

-- 使用
local safeDivide = safe(function(a, b)
    if b == 0 then
        error("除数不能为0")
    end
    return a / b
end)

print(safeDivide(10, 2))  -- 5
print(safeDivide(10, 0))  -- nil (打印错误信息)

日志记录

-- 简单日志系统
Logger = {}
Logger.level = {
    DEBUG = 1,
    INFO = 2,
    WARN = 3,
    ERROR = 4
}
Logger.currentLevel = Logger.level.INFO

function Logger:log(level, message)
    if level >= self.currentLevel then
        local levelName = {"DEBUG", "INFO", "WARN", "ERROR"}
        local timestamp = os.date("%Y-%m-%d %H:%M:%S")
        print(string.format("[%s] [%s] %s", timestamp, levelName[level], message))
    end
end

function Logger:debug(message)
    self:log(self.level.DEBUG, message)
end

function Logger:info(message)
    self:log(self.level.INFO, message)
end

function Logger:warn(message)
    self:log(self.level.WARN, message)
end

function Logger:error(message)
    self:log(self.level.ERROR, message)
end

-- 使用
Logger:info("程序启动")
Logger:warn("这是一个警告")
Logger:error("发生错误")

异常链

-- 保留原始错误信息
function wrapError(err, context)
    return {
        message = context,
        cause = err,
        traceback = debug.traceback()
    }
end

function operation3()
    error("底层错误")
end

function operation2()
    local status, err = pcall(operation3)
    if not status then
        error(wrapError(err, "operation2失败"))
    end
end

function operation1()
    local status, err = pcall(operation2)
    if not status then
        error(wrapError(err, "operation1失败"))
    end
end

-- 捕获并打印完整错误链
local status, err = pcall(operation1)
if not status then
    local current = err
    while current do
        if type(current) == "table" then
            print("错误:", current.message)
            current = current.cause
        else
            print("原始错误:", current)
            break
        end
    end
end

调试技巧

-- 条件断点
function conditionalBreak(condition, message)
    if condition then
        print("断点:", message)
        print(debug.traceback())
        io.read()  -- 等待输入继续
    end
end

-- 使用
for i = 1, 100 do
    conditionalBreak(i == 50, "i达到50")
    -- 其他代码
end

-- 变量监视
function watch(name, value)
    print(string.format("变量 %s = %s", name, tostring(value)))
end

local x = 10
watch("x", x)
x = x + 5
watch("x", x)

-- 性能分析
function profile(func, ...)
    local start = os.clock()
    local result = {func(...)}
    local elapsed = os.clock() - start
    print(string.format("执行时间: %.6f秒", elapsed))
    return table.unpack(result)
end

-- 使用
profile(function()
    local sum = 0
    for i = 1, 1000000 do
        sum = sum + i
    end
    return sum
end)
重要提示:
练习题:
  1. 实现一个完整的日志系统,支持文件输出
  2. 创建一个错误处理中间件
  3. 编写单元测试框架的断言函数
  4. 实现一个调试器,支持单步执行