使用 lua 的 error(.., level) 是反模式吗?

mar*_*son 5 error-handling lua lua-5.1

Lua 5.1 的 API 提供了一个error()函数,它接受一个字符串(错误消息)和一个“级别”。

我的理解是level,可以让您向上移动调用堆栈,这样您就可以提供更好的错误报告,尤其是在将模块作为 API 提供时。

例如,假设用户api_function(x)使用进行呼叫x = nil。这将是一个错误,但 API 直到深入了解其代码后才知道。

它可能会导致以下调用堆栈:

api_function(x)                     : user_file.lua:30
  -> api_function                   : api.lua:20
    -> some_function                : api.lua:250
      -> handle_when_x_string       : api_string.lua:20
        -> error("value is nil")    : api_string.lua:66
Run Code Online (Sandbox Code Playgroud)

正如所写的,用户会看到类似的东西api_string.lua:66 error: value is nil,而他们真正想要看到的“不错”的错误,user_file.lua:30 error: value is nil。(“这个错误是我的错还是 API 中的错误?”)

现在,我们可以将代码更改为“弹出调用堆栈”,

api_function(x)                     : user_file.lua:30
  -> api_function                   : api.lua:20
    -> some_function                : api.lua:250
      -> handle_when_x_string       : api_string.lua:20
        -> error("value is nil", 5) : api_string.lua:66
Run Code Online (Sandbox Code Playgroud)

这将返回“nice”错误,但是,想象一下您也可以handle_when_x_string更直接地调用(抛开糟糕的 API 设计不谈),

another_api_fn(x)                     : user_file.lua:44
  -> another_api_fn                   : api.lua:11
    -> handle_when_x_string           : api_string.lua:20
      -> error("value is nil", 5)     : api_string.lua:66
Run Code Online (Sandbox Code Playgroud)

现在我们的“流行水平”不正确。也许在这个例子中,它会简单地弹出到顶部并停止尝试,但“不正确级别”的原则至少仍然令人不舒服,它甚至可能会“弹出”用户导致错误的位置。

我可以看到一些解决方案:

  • 不要设定水平,只是假设用户足够聪明来解决这个问题。
  • 将 api 入口点 ( api_function& another_api_fn) 以下的任何内容包装在 pcall 中,捕获任何错误并使用已知的“良好”级别值重新冒泡。
  • 永远不要在较低的 api 函数中出错,始终return nil, error或某种类似的模式,然后检查api_function并按要求执行操作。

我的问题是:

  • 返回错误级别是否有问题?只是“是的,无论如何”那里的数字并希望它是好的,这似乎很糟糕。
  • 如果这是一个问题,什么时候设置级别是一个好习惯(可能超过 0,这会禁用位置报告)
  • 哪些解决方案(如果有)是最佳实践?我实际上应该做什么才能编写更好的可维护代码?用 pcall 包装似乎是最简单的,因为在测试时您仍然可以依赖“正常错误”,并且您的函数稍微简单一些,但在我看来,它感觉像是一种反模式。

lut*_*her 3

首先,您需要区分由于错误的 API 调用而导致的错误以及代码中的实际错误。

如果调用的目的error是告诉 API 用户他们传递了错误的参数,那么您应该验证每个 API 函数中的参数,以便错误级别是可知的,并且库的其余部分知道它正在使用有效的参数论据。如果您最终得到了验证函数的复杂层次结构,它们可以采用函数名称和错误级别的参数。这是一个非常人为的示例,说明如何使用错误级别:

local function lessThan100(x, funcName, errorLevel)
  if x >=100 then
    error(funcName .. ' needs a number less than 100', errorLevel)
  end
end

local function numLessThan100(x, funcName, errorLevel)
  if type(x) ~= 'number' then
    error(funcName .. ' needs a number', errorLevel)
  end
  lessThan100(x, funcName, errorLevel + 1)
end

-- API function
local function printNum(x)
  numLessThan100(x, 'printNum', 3)
  print(x)
end
Run Code Online (Sandbox Code Playgroud)

如果error调用代表代码中的错误,则不要使用级别,因为您无法知道是什么触发了错误。