人人皆难免犯错误。因此,我们必须尽可能地处理错误。由于 Lua
语言是一种经常被嵌入在应用程序中的扩展语言,所以当程序发生时并不能简单地崩溃或退出。相反,只要错误发生, Lua
语言就必须提供处理错误的方式。
Lua
语言会在遇到非预期的情况时引发错误。例如,当试图将两个非数值类型的值相加,对不是函数的值进行调用,对不是表类型的值进行索引等。我们也可以显式地通过调用函数 error
并传入一个错误信息作为参数来引发一个错误。通常,这个函数就是在代码中提示出错的合理方式:
print "enter a number" n = io.read("n") if not n then error("invalid input") end
由于“针对某些情况调用函数error”这样的代码结构太常见了,所以 Lua
语言提供了一个内建的函数 assert
来完成这类工作:
print "enter a number" n = assert(io.read("*n"), "invalid input")
函数 assert
检查其第一个参数是否为真,如果该函数为真则返回该参数;如果该参数为假则引发一个错误。该函数的第 2
个参数是一个可选的错误信息。不过,要注意函数 assert
只是一个普通函数,所以 Lua
语言总是在调用该函数前先对参数进行求值。
如果编写形如:
n = io.read() assert(tonumber(n), "invalid input" .. n .. " is not a number")
的代码,那么即使 n
是一个数值类型, Lua
语言也总是会进行字符串连接。在这种情况下使用显式的测试可能更加明智。
当一个函数发现某种意外的情况发生时(即异常exception
),在进行异常处理时可以采用两种基本方式:
- 返回错误代码(通常是
nil
或者false
) - 通过调用函数
error
引发一个错误。
如何在这两种方式之间进行选择并没有固定的规则,但是按照编码规范(自定的),通常遵循如下的指导原则:
容易避免的异常应该引发错误,否则应该返回错误码。
以函数 math.sin
为例,当调用时参数传入了一个表该如何反应呢?如果要检查错误,那么就不得不编写如下的代码:
local res = math.sin(x) if not res then -- 错误? -- error-handling code
当然,也可以调用函数前轻松地检查出这种异常:
if not tonumber(x) then -- x 是否为数字: -- error-handling code
通常,我们既不会检查参数也不会检查函数 sin
的返回值;如果 sin
的参数不是一个数值,那么就意味着我们的程序可能出现了问题。此时,处理异常最简单也是最实用的做法就是停止运行,然后输出一条错误信息。
另一方面,让我们再考虑以下用于打开文件的函数 io.open
。如果要打开的文件不存在,那么该函数应该有怎样的行为呢?在这种情况下,没有什么简单的方法可以在调用函数前检测到这种异常。在很多系统中,判断一个文件是否存在的唯一方法就是试着取打开这个文件。因此,如果由于外部原因(比如“文件不存在(file does not exist)”或“权限不足(permission denied)”)导致函数 io.open
无法打开一个文件,那么它应返回 false
及一条错误信息。通过这种方式,我们就有机会采取恰当的方式来处理异常情况,例如要求用户提供另一个文件名:
local file, msg repeat print "enter a file name" local name = io.read() if not name then return end -- 没有输入 file, msg = io.open(name, "r") if not file then print(msg) end util file
如果不想处理这些情况,但又想安全地运行程序,那么只需要使用 assert
:
file = assert(io.open(name, "r")) --> stdin:1: no-file: No such file or directory点击复制复制失败已复制
这是 Lua
语言中一种典型的技巧:如果函数 io.open
执行失败, assert
就引发阿一个错误。