4

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

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

这将返回“不错”的错误,但是,想象一下您也可以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

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

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

  • 不要设置级别,只是假设用户足够聪明,可以解决问题。
  • 在 pcall 中包装 api 入口点 ( api_function& another_api_fn) 以下的任何内容,捕获任何错误并使用已知的“良好”级别值重新冒泡。
  • 永远不要在较低的 api 函数中出错,总是return nil, error或类似的模式,然后检查它api_function并根据需要采取行动。

我的问题是:

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

4 回答 4

2

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

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

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调用代表代码中的错误,则不要使用级别,因为您无法知道是什么触发了错误。

于 2020-11-17T15:59:09.033 回答
1

在 Lua 中使用error()“冒泡”错误是非常少见的。更常见的是返回nil后跟一个描述错误的字符串,并让调用者决定失败的函数是否可以从中恢复,是否应该重试等等。

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

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

于 2020-11-17T08:56:52.030 回答
1

这似乎是为特定目的而存在的特定工具的情况。从文档中:

错误函数有一个额外的第二个参数,它给出了它应该报告错误的级别;有了它,您可以将错误归咎于其他人。例如,假设你编写了一个函数,它的第一个任务是检查它是否被正确调用:

然后,有人用错误的参数调用你的函数:

Lua 将矛头指向你的函数——毕竟,调用错误的是 foo——而不是真正的罪魁祸首,即调用者。为了更正它,您通知错误您报告的错误发生在调用层次结构的第 2 级(第 1 级是您自己的函数):

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

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

于 2020-11-17T16:39:59.057 回答
0

返回错误的级别是否有问题?只是“是的,随便”一个数字似乎很糟糕,希望它是好的。

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

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

我通常只看到级别 1 和 2,后者通常用于指示传递给函数的参数不正确。但这是我的观察,没有具体的数据来支持它。

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

于 2020-11-17T13:29:37.320 回答