错误处理

       本节将全面讲解Lua的错误类型、错误处理机制、调试技巧以及在Android平台下的特殊处理方案。

1. 错误类型

1.1 语法错误

-- 常见语法错误示例
local examples = {
    "local x = ",           -- 不完整的表达式
    "if a then end else",   -- 错误的if结构
    "function() end",       -- 匿名函数未赋值
    "t = {1, 2, }",        -- 末尾逗号
    "break",               -- 无效的break位置
    "goto undefined",      -- 未定义的标签
    "x = nil + 1"          -- 无效运算
}

-- 检测语法错误
function checkSyntax(code)
    local func, err = load("return "..code)
    if not func then
        print("语法错误:", err)  -- 输出类似:[string "return local x = "]:1: unexpected symbol near 'local'
    end
end

注意事项:

1.2 运行时错误

-- 常见运行时错误类型
local runtimeErrors = {
    arithmetic = function() return 1 + "a" end,          -- 算术错误
    index = function() return t.undefined end,           -- 索引nil值
    call = function() local f = nil; f() end,            -- 调用nil值
    conversion = function() return "x" + 1 end,          -- 类型转换
    file = function() io.open("nonexistent.txt") end,    -- 文件操作
    memory = function() while true do table.insert({}, {}) end end,  -- 内存溢出
    custom = function() error("自定义错误") end          -- 主动抛出
}

-- 捕获运行时错误
for name, func in pairs(runtimeErrors) do
    local ok, err = pcall(func)
    if not ok then
        print(name.."错误:", err:match(":(.+)"))  -- 提取错误信息
    end
end

注意事项:

1.3 资源错误

-- Android平台特有错误
local resourceErrors = {
    asset = function() 
        local content = android.loadAsset("nonexistent.txt")
        if not content then error("资源加载失败") end
    end,
    memory = function()
        local hugeTable = {}
        for i = 1, 1e8 do hugeTable[i] = i end  -- 内存不足
    end,
    thread = function()
        local Thread = luajava.bindClass("java.lang.Thread")
        Thread():start():start()  -- 非法线程状态
    end
}

-- 处理资源错误
function handleResourceError(f)
    local ok, err = xpcall(f, function(e)
        return debug.traceback(e, 2)  -- 带堆栈的错误
    end)
    if not ok then
        print("资源错误:", err)
        -- Android日志系统
        android.util.Log.e("LUA_ERROR", err)
    end
end

Android特别提示:

2. 错误处理机制

2.1 保护模式调用

-- 基本保护调用
local function riskyOperation()
    if math.random() > 0.5 then
        error("操作失败")
    end
    return "成功"
end

-- pcall示例
local status, result = pcall(riskyOperation)
if status then
    print("结果:", result)
else
    print("错误:", result)
end

-- xpcall示例(带错误处理函数)
local function errorHandler(err)
    local trace = debug.traceback("错误追踪:", 2)
    return string.format("%s\n%s", err, trace)
end

local status, result = xpcall(riskyOperation, errorHandler)

最佳实践:

2.2 错误传播

-- 多层调用栈错误传递
function level1()
    level2()
end

function level2()
    level3()
end

function level3()
    error("深层错误")
end

-- 捕获调用栈
local ok, err = xpcall(level1, function(e)
    return debug.traceback(e, 3)  -- 获取3层堆栈
end)
print(err)

-- 输出类似:
-- [string "level3"]:1: 深层错误
-- stack traceback:
--     [string "level3"]: in function 'level3'
--     [string "level2"]: in function 'level2'
--     [string "level1"]: in function 'level1'

调试建议:

3. 高级错误处理

3.1 错误元表

-- 自定义错误对象
local Error = {}
Error.__index = Error

function Error.new(code, message, extra)
    return setmetatable({
        code = code,
        message = message,
        extra = extra or {},
        timestamp = os.time(),
        stack = debug.traceback("", 2)
    }, Error)
end

function Error:__tostring()
    return string.format("[%s] %s\n%s", 
        self.code, self.message, self.stack)
end

-- 使用示例
local function validateInput(input)
    if not input then
        error(Error.new("INVALID_INPUT", "输入不能为空", {
            field = "username",
            value = input
        }))
    end
end

local ok, err = pcall(validateInput, nil)
if not ok then
    if getmetatable(err) == Error then
        print("错误代码:", err.code)
        print("错误详情:", err)
    else
        print("原生错误:", err)
    end
end

设计原则:

3.2 错误恢复

-- 带恢复点的错误处理
function withRetry(fn, maxRetries, delay)
    local retries = 0
    while true do
        local ok, result = pcall(fn)
        if ok then return result end
        
        retries = retries + 1
        if retries >= maxRetries then
            error(string.format("超过最大重试次数(%d): %s", 
                  maxRetries, result))
        end
        
        print(string.format("重试 %d/%d: %s", 
              retries, maxRetries, result))
        os.execute("sleep "..tonumber(delay))
    end
end

-- 使用示例
local function unstableAPI()
    if math.random() < 0.7 then
        error("API请求超时")
    end
    return "数据"
end

local data = withRetry(unstableAPI, 3, 1)  -- 最多重试3次,间隔1秒

重试策略:

4. Android平台实践

4.1 全局异常处理

-- 设置全局错误处理器
local originalTraceback = debug.traceback
debug.traceback = function(msg, level)
    local trace = originalTraceback(msg, level)
    -- 添加Android环境信息
    local envInfo = string.format("\n[ANDROID_ENV]\nSDK: %s\nModel: %s",
        android.os.Build.VERSION.SDK,
        android.os.Build.MODEL)
    return trace .. envInfo
end

-- 全局保护函数
function androidRunSafe(fn)
    xpcall(fn, function(err)
        local fullError = debug.traceback(err)
        -- 记录到Android系统
        android.util.Log.e("LUA_CRASH", fullError)
        -- 显示用户友好提示
        activity.runOnUiThread(function()
            local AlertDialog = luajava.bindClass("android.app.AlertDialog")
            AlertDialog.Builder(activity)
                :setTitle("应用错误")
                :setMessage("发生意外错误,已记录")
                :setPositiveButton("确定", nil)
                :show()
        end)
    end)
end

-- 使用示例
androidRunSafe(function()
    require("module.undefined")  -- 故意引发错误
end)

关键点:

4.2 错误上报系统

-- 错误上报模块
local ErrorReporter = {
    API_URL = "https://api.example.com/errors",
    enabled = true,
    context = nil
}

function ErrorReporter.init(context)
    ErrorReporter.context = context
end

function ErrorReporter.report(err, level)
    if not ErrorReporter.enabled then return end
    
    local report = {
        platform = "Android",
        appVersion = ErrorReporter.context.packageManager
            .getPackageInfo(ErrorReporter.context.packageName, 0).versionName,
        error = tostring(err),
        stack = debug.traceback("", level or 2),
        deviceInfo = {
            model = android.os.Build.MODEL,
            sdk = android.os.Build.VERSION.SDK_INT,
            memory = android.os.Debug.getNativeHeapAllocatedSize()
        },
        timestamp = os.date("%Y-%m-%d %H:%M:%S")
    }
    
    -- 异步上报
    local Thread = luajava.bindClass("java.lang.Thread")
    Thread{
        run = function()
            local HttpURLConnection = luajava.bindClass("java.net.HttpURLConnection")
            local conn = luajava.newInstance("java.net.URL", ErrorReporter.API_URL)
                :openConnection()
            conn:setRequestMethod("POST")
            conn:setDoOutput(true)
            local out = conn:getOutputStream()
            out:write(luajava.newInstance("org.json.JSONObject", report):toString():getBytes())
            out:close()
            conn:getResponseCode()  -- 触发请求
        end
    }:start()
end

-- 自动注册全局错误处理
debug.sethook(function(event)
    if event == "error" then
        ErrorReporter.report(debug.getinfo(2, "S").short_src..":"..
                            debug.getinfo(2, "l").currentline)
    end
end, "r")

上报策略:

5. 调试技巧

5.1 堆栈追踪增强

-- 增强版堆栈追踪
function enhancedTraceback(err, level)
    local trace = debug.traceback(err, level)
    -- 添加局部变量信息
    local i = 1
    while true do
        local name, value = debug.getlocal(level + 1, i)
        if not name then break end
        trace = trace .. string.format("\n  local %s = %s", 
               name, tostring(value))
        i = i + 1
    end
    -- 添加上值信息
    local func = debug.getinfo(level + 1, "f").func
    i = 1
    while true do
        local name, value = debug.getupvalue(func, i)
        if not name then break end
        trace = trace .. string.format("\n  upvalue %s = %s", 
               name, tostring(value))
        i = i + 1
    end
    return trace
end

-- 使用示例
local function buggyFunc()
    local x = 10
    local t = {1, 2, 3}
    error("测试错误")
end

xpcall(buggyFunc, enhancedTraceback)

性能控制:

5.2 交互式调试

-- 简单REPL调试器
function startDebugger(co, err)
    co = co or coroutine.running()
    print("进入调试模式,输入命令或Lua表达式:")
    print("(bt)查看堆栈 (up n)上值 (loc n)局部变量 (eval)执行代码 (c)继续")
    
    local level = 2
    while true do
        io.write("debug> ")
        local input = io.read()
        
        if input == "bt" then
            print(debug.traceback(co, level))
        elseif input:match("^up (%d+)") then
            local n = tonumber(input:match("^up (%d+)"))
            for i = 1, n do
                local name, value = debug.getupvalue(debug.getinfo(level, "f").func, i)
                if name then
                    print(string.format("%d: %s = %s", i, name, tostring(value)))
                end
            end
        elseif input:match("^loc (%d+)") then
            local n = tonumber(input:match("^loc (%d+)"))
            for i = 1, n do
                local name, value = debug.getlocal(level, i)
                if name then
                    print(string.format("%d: %s = %s", i, name, tostring(value)))
                end
            end
        elseif input == "eval" then
            io.write("输入Lua代码> ")
            local code = io.read()
            local chunk = load("return "..code)
            if chunk then
                setfenv(chunk, getfenv(debug.getinfo(level, "f").func)
                print(chunk())
            else
                print("语法错误")
            end
        elseif input == "c" then
            break
        else
            print("未知命令")
        end
    end
end

-- 使用示例
xpcall(buggyFunc, function(err)
    startDebugger(coroutine.running(), err)
end)

安全提示: