使用 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”不正确了。也许在这个例子中,它会简单地弹出到顶部并停止尝试,但“不正确级别”的原则至少仍然让人不舒服,它甚至可能从用户导致错误的地方“弹出”。
我可以看到一些解决方案:
- 不要设置级别,只是假设用户足够聪明,可以解决问题。
- 在 pcall 中将 api 入口点(
api_function
和 another_api_fn
)下方的任何内容包裹起来,捕获任何错误并使用已知的“良好”级别值重新冒泡。
- 永远不要在较低的 api 函数中出错,总是
return nil, error
或一些类似的模式,然后在 api_function
中检查并按要求行事。
我的问题是:
- return 关卡有问题吗?只是“随便”一个数字并希望它是好的形式似乎很糟糕。
- 如果这是一个问题,什么时候设置级别是个好习惯(可能超过 0 会禁用位置报告)
- 哪些解决方案(如果有的话)是最佳实践?我实际上应该怎么做才能编写更好的可维护代码?在 pcall 中包装似乎是最简单的,因为在测试时您仍然可以依赖“正常错误”,并且您的功能稍微简单一些,但不知何故,在我看来它感觉像是一种反模式。
在 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 级是唯一的预期 级别,但这并不意味着其他用例不存在。
您已经强调了(误)使用此语言功能可能导致的问题多于它解决的问题(也就是用无意义的错误代码误导用户)。良好实践的问题通常是相当主观的,但我们的工作是编写良好的、可维护的、无错误的代码,无论我们使用何种语言。该语言的设计者显然认为将此工具放在您的工具箱中是个好主意。您可以从阅读手册开始,但除此之外,您还需要了解该工具并善用它。如果您觉得该工具不应该存在,或者您只是觉得使用它不自在,那么请坚持使用您提到的替代方法之一。
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”不正确了。也许在这个例子中,它会简单地弹出到顶部并停止尝试,但“不正确级别”的原则至少仍然让人不舒服,它甚至可能从用户导致错误的地方“弹出”。
我可以看到一些解决方案:
- 不要设置级别,只是假设用户足够聪明,可以解决问题。
- 在 pcall 中将 api 入口点(
api_function
和another_api_fn
)下方的任何内容包裹起来,捕获任何错误并使用已知的“良好”级别值重新冒泡。 - 永远不要在较低的 api 函数中出错,总是
return nil, error
或一些类似的模式,然后在api_function
中检查并按要求行事。
我的问题是:
- return 关卡有问题吗?只是“随便”一个数字并希望它是好的形式似乎很糟糕。
- 如果这是一个问题,什么时候设置级别是个好习惯(可能超过 0 会禁用位置报告)
- 哪些解决方案(如果有的话)是最佳实践?我实际上应该怎么做才能编写更好的可维护代码?在 pcall 中包装似乎是最简单的,因为在测试时您仍然可以依赖“正常错误”,并且您的功能稍微简单一些,但不知何故,在我看来它感觉像是一种反模式。
在 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 级是唯一的预期 级别,但这并不意味着其他用例不存在。
您已经强调了(误)使用此语言功能可能导致的问题多于它解决的问题(也就是用无意义的错误代码误导用户)。良好实践的问题通常是相当主观的,但我们的工作是编写良好的、可维护的、无错误的代码,无论我们使用何种语言。该语言的设计者显然认为将此工具放在您的工具箱中是个好主意。您可以从阅读手册开始,但除此之外,您还需要了解该工具并善用它。如果您觉得该工具不应该存在,或者您只是觉得使用它不自在,那么请坚持使用您提到的替代方法之一。