Lua错误处理

错误

Errare humanum est(拉丁谚语:犯错是人的本性)。所以我们要尽可能的防止错误的发生,Lua 经常作为扩展语言嵌入在别的应用中,所以不能当错误发生时简单的崩溃或者退出。相反,当错误发生时 Lua 结束当前的 chunk 并返回到应用中。

当 Lua 遇到不期望的情况时就会抛出错误,比如:两个非数字进行相加;调用一个非函数的变量;访问表中不存在的值等(可以通过 metatables 修改这种行为,后面介绍)。

你也可以通过调用 error 函数显示的抛出错误,error 的参数是要抛出的错误信息。

print "enter a number:"
n = io.read("*number") 
if not n then error("invalid input") end
Lua 提供了专门的内置函数 assert 来完成上面类似的功能:
print "enter a number:"
n = assert(io.read("*number"), "invalid input") 

assert 首先检查第一个参数是否返回错误,如果不返回错误 assert 简单的返回,否则assert 以第二个参数抛出错误信息。第二个参数是可选的。注意 assert 是普通的函数,他会首先计算两个参数然后再调用函数,所以以下代码:

n = io.read() 
assert(tonumber(n), "invalid input: " .. n .. " is not a number") 

将会总是进行连接操作,使用显示的 test 可以避免这种情况。当函数遇到异常有两个基本的动作:返回错误代码或者抛出错误。这两种方式选择哪一种没有固定的规则,但有一般的原则:容易避免的异常应该抛出错误否则返回错误代码。

例如我们考虑 sin 函数,如果以一个 table 作为参数,假定我们返回错误代码,我们需要检查错误的发生,代码可能如下:

local res = math.sin(x) 
if not res then -- error 
 ... 
然而我们可以在调用函数以前很容易的判断是否有异常:
if not tonumber(x) then -- error: x is not a number 
 ... 

然而通常情况下我们既不是检查参数也不是检查返回结果,因为参数错误可能意味着我们的程序某个地方存在问题,这种情况下,处理异常最简单最实际的方式是抛出错误并且终止代码的运行。

再来看一个例子 io.open 函数用来打开一个文件,如果文件不存在结果会怎么样呢?

很多系统中,通过试着去打开文件来判断是否文件存在。所以如果 io.open 不能打开文件(由于文件不存在或者没有权限),函数返回 nil 和错误信息。以这种方式我们可以通过与用户交互(比如:是否要打开另一个文件)合理的处理问题:

local file, msg 
repeat 
 print "enter a file name:"
local name = io.read() 
if not name then return end -- no input 
 file, msg = io.open(name, "r") 
if not file then print(msg) end
until file 
如果你想偷懒不想处理这些情况,又想代码安全的运行,可以简单的使用 assert:
file = assert(io.open(name, "r")) 
Lua 中有一个习惯:如果 io.open 失败,assert 将抛出错误。
file = assert(io.open("no-file", "r")) 
 --> stdin:1: no-file: No such file or directory 

注意:io.open 返回的第二个结果(错误信息)作为 assert 的第二个参数。

异常和错误处理

很多应用中,不需要在 Lua 进行错误处理,一般有应用来完成。

通常应用要求 Lua运行一段 chunk,如果发生异常,应用根据 Lua 返回的错误代码进行处理。在控制台模式下的 Lua 解释器如果遇到异常,打印出错误然后继续显示提示符等待下一个命令。

如果在 Lua 中需要处理错误,需要使用 pcall 函数封装你的代码。
假定你想运行一段 Lua 代码,这段代码运行过程中可以捕捉所有的异常和错误。
第一步:将这段代码封装在一个函数内

function foo () 
 ... 
if unexpected_condition then error() end
 ... 
 print(a[i]) -- potential error: `a' may not be a table 
 ... 
end 
第二步:使用 pcall 调用这个函数
if pcall(foo) then
-- no errors while running `foo' 
 ... 
else 
-- `foo' raised an error: take appropriate actions 
 ... 
end 
当然也可以用匿名函数的方式调用 pcall:
if pcall(function () ... end) then ... 
else ... 

pcall 在保护模式下调用他的第一个参数并运行,因此可以捕获所有的异常和错误。

如果没有异常和错误,pcall 返回 true 和调用返回的任何值;否则返回 nil 加错误信息。

错误信息不一定非要是一个字符串(下面的例子是一个 table),传递给 error 的任何信息都会被 pcall 返回:

local status, err = pcall(function () error({code=121}) end) 
print(err.code) --> 121 

这种机制提供了我们在 Lua 中处理异常和错误的所需要的全部内容。我们通过 error抛出异常,然后通过 pcall 捕获他。

错误信息和回跟踪(Tracebacks)

虽然你可以使用任何类型的值作为错误信息,通常情况下,我们使用字符串来描述遇到的错误信息。如果遇到内部错误(比如对一个非table 的值使用索引下表访问)Lua将自己产生错误信息,否则 Lua 使用传递给 error 函数的参数作为错误信息。不管在什么情况下,Lua 都尽可能清楚的描述发生的错误。

local status, err = pcall(function () a = 'a'+1 end) 
print(err) 
--> stdin:1: attempt to perform arithmetic on a string value 
local status, err = pcall(function () error("my error") end) 
print(err) 
--> stdin:1: my error 

例子中错误信息给出了文件名(stdin)加上行号。
函数 error 还可以有第二个参数,表示错误的运行级别。有了这个参数你就无法抵赖错误是别人的了,比如,加入你写了一个函数用来检查 error 是否被正确的调用:

function foo (str) 
if type(str) ~= "string" then
 error("string expected") 
end 
 ... 
end 

可能有人这样调用这个函数:

foo({x=1}) 

Lua 会指出发生错误的是 foo 而不是 error,实际的错误是调用 error 时产生的,为了纠正这个问题修改前面的代码让 error 报告错误发生在第二级(你自己的函数是第一级)如下:

function foo (str) 
if type(str) ~= "string" then
 error("string expected", 2) 
end 
 ... 
end 

当错误发生的时候,我们常常需要更多的错误发生相关的信息,而不单单是错误发生的位置。

至少期望有一个完整的显示导致错误发生的调用栈的 tracebacks,当 pcall 返回错误信息的时候他已经释放了保存错误发生情况的栈的信息。因此,如果我们想得到tracebacks 我们必须在 pcall 返回以前获取。

Lua 提供了 xpcall 来实现这个功能,xpcall接受两个参数:调用函数和错误处理函数。

当错误发生时。Lua 会在栈释放以前调用错误处理函数,因此可以使用 debug 库收集错误相关的信息。有两个常用的 debug 处理函数:debug。debug 和 debug.traceback,前者给出 Lua 的提示符,你可以自己动手察看错误发生时的情况;后者通过 traceback 创建更多的错误信息,后者是控制台解释器用来构建错误信息的函数。你可以在任何时候调用debug.traceback获取当前运行的traceback信息:

print(debug.traceback())
发布了257 篇原创文章 · 获赞 152 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39885372/article/details/104337212
今日推荐