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

Is using lua's error(.., level) an anti pattern?

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

我的理解是 level,让您可以向上移动调用堆栈,因此您可以提供更好的错误报告,尤其是在交付模块时 API。

例如,假设用户用 x = nil 调用 api_function(x)。这将是一个错误,但 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

正如所写,用户会看到类似 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

这将 return 出现“不错”的错误,但是 ,想象一下您也可以更直接地调用 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

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

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

我的问题是:

在 Lua 中使用 error() 来“冒出”错误是非常罕见的。更常见的是 return nil 后跟一个描述错误的字符串,并将其留给调用者来决定函数失败是否可以从中恢复,是否应该重试等.

这也更高效,因为它比调用 error() 的开销更少(这只是一个普通的函数调用)

过度使用 pcall 和转发错误是一个非常糟糕的主意。它只会让您的代码更难理解。

Is it a problem to return the wrong level? It seems poor form to just "yeah whatever" a number in there and hope it's good.

不,除了引起混乱。错误消息是有帮助的,如果你去“随便”他们不会。从实现的角度来看,没有风险:luaB_error uses luaL_where 添加带有错误位置的消息。如果你走得太远,信息将被简单地跳过:

function foo ()
   error("where am I", 7)
end
foo() -- lua: where am I
      -- stack traceback: ...

我一般只见过1级和2级,后者通常用来表示传递给函数的参数不正确。但这是我的观察,没有具体数据支持。

至于剩下的问题,主要是看个人意见。使用适合您和您的问题的任何东西。 error()nil + 消息都是合理的方法。你在研究它方面做得很好,似乎有足够的理解来做出决定。

首先,您需要区分错误 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

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

这似乎是为特定目的而存在的特定工具的情况。 来自 documentation:

The error function has an additional second parameter, which gives the level where it should report the error; with it, you can blame someone else for the error. For instance, suppose you write a function and its first task is to check whether it was called correctly:

Then, someone calls your function with a wrong argument:

Lua points its finger to your function---after all, it was foo that called error---and not to the real culprit, the caller. To correct that, you inform error that the error you are reporting occurred on level 2 in the calling hierarchy (level 1 is your own function):

没有一种语言是完美的,有时设计者会为了解决特定问题而创建一个功能强大但具有潜在危险的工具。 (例如 C# 反射)在这种情况下,其目的似乎是在发出调用的函数而不是接收它的函数中报告错误的 API 调用。因此,根据文档,第 1 级和第 2 级是唯一的预期 级别,但这并不意味着其他用例不存在。

您已经强调了(误)使用此语言功能可能导致的问题多于它解决的问题(也就是用无意义的错误代码误导用户)。良好实践的问题通常是相当主观的,但我们的工作是编写良好的、可维护的、无错误的代码,无论我们使用何种语言。该语言的设计者显然认为将此工具放在您的工具箱中是个好主意。您可以从阅读手册开始,但除此之外,您还需要了解该工具并善用它。如果您觉得该工具不应该存在,或者您只是觉得使用它不自在,那么请坚持使用您提到的替代方法之一。