Skip to content

Lua 错误处理

有效的错误处理在软件开发中至关重要。文件 I/O、网络请求、数据库交互或复杂计算等操作都可能失败。未处理的错误可能导致程序崩溃、数据损坏或用户体验差。健壮的错误处理使应用程序更可靠且更容易调试。

在 Lua 中,错误通常可以分为以下几类:

  • 语法错误 (Syntax Errors): Lua 代码结构或语法中的错误。这些通常在执行前由 Lua 编译器/解析器捕获。
  • 运行时错误 (Runtime Errors): 程序运行时发生的错误,例如尝试对错误的数据类型执行操作、除以零,或试图像访问表格一样访问一个 nil 值。

Lua 在尝试编译代码时会检测到语法错误。解释器通常会报告错误的位置和性质。

-- 语法错误的示例:for 循环中缺少 'do'
for i = 1, 10
print(i)
end

运行上述代码可能会产生如下错误消息:

lua: SCRIPT_NAME.lua:2: 'do' expected near 'print'

语法错误通常很容易修复,因为错误消息通常会直接指出问题所在。

运行时错误发生在程序执行期间。它们可能由逻辑错误、无效输入或意外条件引起。

-- 运行时错误的示例:尝试对 nil 值进行算术运算
local function add(a, b)
return a + b
end
add(10) -- 'b' will be nil inside the function

这段代码可以编译通过,但运行时会产生一个运行时错误:

lua: SCRIPT_NAME.lua:2: attempt to perform arithmetic on local 'b' (a nil value)
stack traceback:
SCRIPT_NAME.lua:2: in function 'add'
SCRIPT_NAME.lua:5: in main chunk
[C]: in ?

错误消息指示了错误的类型,并提供了堆栈回溯 (stack traceback) 以帮助定位错误发生的位置。

assert(value, message): 检查 value 是否为真 (即不是 false 也不是 nil)。如果 value 是 false 或 nil,assert 将抛出一个错误。可选的 message 字符串将用作错误消息。如果 value 为真,assert 将返回其所有参数。

error(message [, level]): 抛出一个带有给定 message 的错误。可选的 level 参数指定如何在调用堆栈 (call stack) 中获取错误位置。Level 1 (默认) 指向调用 error 的位置。Level 2 指向调用 error 的函数被调用的位置,依此类推。Level 0 避免添加错误位置信息。

使用 assert 的示例:

local function process_data(data)
assert(type(data) == "table", "Input must be a table")
assert(data.value, "Table must contain 'value' field")
assert(type(data.value) == "number", "'value' field must be a number")
-- 处理 data.value
print("Processing value:", data.value)
end
-- 这会正常工作
-- process_data({ value = 10 }) -- This would work
-- 这会在第二次 assert 时出错
-- process_data({ name = "test" }) -- This will error on the second assert
-- 这会在第一次 assert 时出错
process_data(nil) -- This will error on the first assert

如果调用 process_data(nil),输出可能是:

lua: SCRIPT_NAME.lua:2: Input must be a table
stack traceback:
[C]: in function 'assert'
SCRIPT_NAME.lua:2: in function 'process_data'
SCRIPT_NAME.lua:11: in main chunk
[C]: in ?

为了在不终止脚本的情况下优雅地处理错误,Lua 提供了 pcall (保护性调用, protected call) 和 xpcall (扩展保护性调用, extended protected call)。

pcall(func, arg1, ...):

以保护模式 (protected mode) 调用函数 func 并传递给定参数。如果在 func 执行期间没有错误发生,pcall 返回 true,后跟 func 的所有结果。如果发生错误,pcall 会捕获它,返回 false,然后是错误消息或错误对象。

local function might_fail(x, y)
if y == 0 then
error("division by zero")
end
return x / y
end
local status, result_or_error = pcall(might_fail, 10, 2)
if status then
print("Success:", result_or_error) -- 成功: 5
else
print("Failure:", result_or_error) -- 错误消息
end
local status2, error_msg2 = pcall(might_fail, 10, 0)
if status2 then
print("Success:", error_msg2)
else
print("Failure:", error_msg2) -- 失败: SCRIPT_NAME.lua:3: division by zero
end

xpcall(func, err_handler_func):

与 pcall 类似,但允许您指定一个自定义的错误处理函数 (err_handler_func)。如果在 func 中发生错误,xpcall 会调用 err_handler_func 并将错误对象作为参数传递。xpcall 的返回值与 pcall 类似:成功时返回 true 和结果,失败时返回 false 和错误处理函数的返回值。

local function my_function_that_might_error()
-- 这会引发错误:尝试对全局变量 'n' (一个 nil 值) 进行算术运算
-- n = n / nil -- This would cause an error: attempt to perform arithmetic on global 'n' (a nil value)
-- 抛出一个表格作为错误对象
error({ code = 101, message = "A custom error occurred" }) -- Throwing a table as error object
end
local function my_error_handler(err_obj)
print("--- Custom Error Handler --- ")
if type(err_obj) == "table" then
print("Error Code:", err_obj.code)
print("Error Message:", err_obj.message)
else
print("Error:", err_obj)
end
-- 这个值在返回 'false' 后由 xpcall 返回
return "Error handled gracefully" -- This value is returned by xpcall after 'false'
end
local status, result = xpcall(my_function_that_might_error, my_error_handler)
print("xpcall Status:", status)
print("xpcall Result:", result)

xpcall 示例的预期输出:

--- Custom Error Handler ---
Error Code: 101
Error Message: A custom error occurred
xpcall Status: false
xpcall Result: Error handled gracefully

最佳实践:

  • 在函数内部使用 assert 进行前置条件 (preconditions) 和不变量 (invariants) 的检查——这些情况表明您自己的代码中存在 bug。
  • 在调用外部代码、库函数或任何可能因外部因素 (例如,文件未找到、网络问题) 而可预测地失败的操作时,使用 pcall 或 xpcall。
  • 提供清晰且信息丰富的错误消息。
  • 在使用 error() 或从 xpcall 中的错误处理函数返回时,考虑使用表格作为错误对象,以提供更结构化的错误信息。