断言()与强制():选择哪个?
我很难选择是应该在 D 中“强制”一个条件还是“断言”一个条件。(不过,这是语言中立的。)
理论上,我知道你使用断言来查找错误,并且你强制其他情况,以检查是否存在非典型情况。例如,您可能会说 assert(count >= 0)
作为方法的参数,因为这表明调用者存在错误,并且您会说 enforce(isNetworkConnected)< /code>,因为这不是一个错误,它只是您假设的东西,在您无法控制的合法情况下很可能不是真的。
此外,断言可以作为优化从代码中删除,没有副作用,但强制不能删除,因为它们必须始终执行其条件代码。因此,如果我要实现一个在第一次访问其任何方法时自动填充的惰性填充容器,我会说 enforce(!empty())
而不是 assert(!empty( ))
,因为对 empty()
的检查必须始终发生,因为它会延迟执行内部代码。
所以我想我知道它们应该的意思。但理论比实践容易,而且我在实际应用这些概念时遇到了困难。
考虑以下情况:
我正在创建一个范围(类似于迭代器),它迭代其他两个范围,并将结果相加。 (对于函数式程序员:我知道我可以使用 map!("a + b")
代替,但我现在忽略它,因为它没有说明问题。)所以我的代码在伪代码中看起来像这样:
void add(Range range1, Range range2)
{
Range result;
while (!range1.empty)
{
assert(!range2.empty); //Should this be an assertion or enforcement?
result += range1.front + range2.front;
range1.popFront();
range2.popFront();
}
}
这应该是断言还是强制执行? (范围不同时清空是调用者的错吗?它可能无法控制范围的来源——它可能来自用户——但话又说回来,它看起来仍然像一个bug,不是吗?)
或者这是另一个伪代码示例:
uint getFileSize(string path)
{
HANDLE hFile = CreateFile(path, ...);
assert(hFile != INVALID_HANDLE_VALUE); //Assertion or enforcement?
return GetFileSize(hFile); //and close the handle, obviously
}
...
这应该是断言还是强制执行?该路径可能来自用户 - 因此它可能不是一个错误 - 但路径应该有效仍然是此方法的前提条件。我主张还是强制执行?
谢谢!
I'm having a hard time choosing whether I should "enforce" a condition or "assert" a condition in D. (This is language-neutral, though.)
Theoretically, I know that you use assertions to find bugs, and you enforce other conditions in order to check for atypical conditions. E.g. you might say assert(count >= 0)
for an argument to your method, because that indicates that there's a bug with the caller, and that you would say enforce(isNetworkConnected)
, because that's not a bug, it's just something that you're assuming that could very well not be true in a legitimate situation beyond your control.
Furthermore, assertions can be removed from code as an optimization, with no side effects, but enforcements cannot be removed because they must always execute their condition code. Hence if I'm implementing a lazy-filled container that fills itself on the first access to any of its methods, I say enforce(!empty())
instead of assert(!empty())
, because the check for empty()
must always occur, since it lazily executes code inside.
So I think I know that they're supposed to mean. But theory is easier than practice, and I'm having a hard time actually applying the concepts.
Consider the following:
I'm making a range (similar to an iterator) that iterates over two other ranges, and adds the results. (For functional programmers: I'm aware that I can use map!("a + b")
instead, but I'm ignoring that for now, since it doesn't illustrate the question.) So I have code that looks like this in pseudocode:
void add(Range range1, Range range2)
{
Range result;
while (!range1.empty)
{
assert(!range2.empty); //Should this be an assertion or enforcement?
result += range1.front + range2.front;
range1.popFront();
range2.popFront();
}
}
Should that be an assertion or an enforcement? (Is it the caller's fault that the ranges don't empty at the same time? It might not have control of where the range came from -- it could've come from a user -- but then again, it still looks like a bug, doesn't it?)
Or here's another pseudocode example:
uint getFileSize(string path)
{
HANDLE hFile = CreateFile(path, ...);
assert(hFile != INVALID_HANDLE_VALUE); //Assertion or enforcement?
return GetFileSize(hFile); //and close the handle, obviously
}
...
Should this be an assertion or an enforcement? The path might come from a user -- so it might not be a bug -- but it's still a precondition of this method that the path should be valid. Do I assert or enforce?
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我不确定它是否完全与语言无关。我使用的语言都没有有
enforce()
,如果我遇到有这种情况的语言,那么我会想使用assert
和enforce
按照它们的预期方式,这可能是该语言的惯用方式。例如,C 或 C++ 中的
assert
会在程序失败时停止程序,它不会抛出异常,因此它的用法可能与您所说的不同。你不会在 C++ 中使用assert
,除非你认为调用者已经犯了一个非常严重的错误,以至于不能依赖他们来清理(例如传递负数),或者否则其他地方的一些其他代码犯了一个非常严重的错误,以至于程序应该被视为处于未定义状态(例如,您的数据结构似乎已损坏)。不过,C++ 确实区分了运行时错误和逻辑错误,这可能大致对应,但我认为主要是关于可避免的错误和不可避免的错误。在
add
的情况下,如果作者的意图是提供不匹配列表的程序存在错误并需要修复,则您将使用逻辑错误,或者如果它只是可能发生的情况之一,则使用运行时异常发生。例如,如果您的函数要处理任意生成器,则不一定有报告其长度的方法,除非破坏性地评估整个序列,您更有可能将其视为不可避免的错误情况。称其为逻辑错误意味着调用者有责任在调用
add
之前检查长度,如果他们无法通过纯粹的理由来确保这一点。因此,他们不会在没有首先明确检查长度的情况下从用户传递列表,并且老实说,他们应该庆幸自己甚至得到了异常而不是未定义的行为。称其为运行时错误表示传入不同长度的列表是“合理的”(如果异常),但例外情况表明它发生在这种情况下。因此,我认为是强制执行而不是断言。
对于
filesize
:对于文件的存在,如果可能的话,您应该将其视为潜在的可恢复故障(强制执行),而不是错误(断言)。原因很简单,调用者无法确定文件是否存在 - 在检查文件是否存在和调用之间,总是有具有更多权限的人可以删除它,或者卸载整个文件系统。文件大小
。因此,当它不存在时,它不一定是调用代码中的逻辑缺陷(尽管最终用户可能搬起石头砸自己的脚)。因此,很可能会有调用者将其视为发生的事情之一,即不可避免的错误情况。创建文件句柄也可能因内存不足而失败,这是大多数系统上另一个不可避免的错误,尽管如果启用了过度提交,则不一定是可恢复的错误。另一个需要考虑的例子是 C++ 向量的
operator[]
与at()
。at()
抛出out_of_range
,一个逻辑错误,并不是因为调用者可能想要恢复是不可想象的,或者因为你必须是某种麻木才会犯这个错误使用at()
访问超出范围的数组,但是如果调用者希望的话,该错误是完全可以避免的 - 您始终可以在之前检查size()
如果您没有其他方式知道您的索引是否良好,请访问。因此,operator[] 根本不保证任何检查,并且以效率的名义,超出范围的访问具有未定义的行为。I'm not sure it is entirely language-neutral. No language that I use has
enforce()
, and if I encountered one that did then I would want to useassert
andenforce
in the ways they were intended, which might be idiomatic to that language.For instance
assert
in C or C++ stops the program when it fails, it doesn't throw an exception, so its usage may not be the same as what you're talking about. You don't useassert
in C++ unless you think that either the caller has already made an error so grave that they can't be relied on to clean up (e.g. passing in a negative count), or else some other code elsewhere has made an error so grave that the program should be considered to be in an undefined state (e.g. your data structure appears corrupt). C++ does distinguish between runtime errors and logic errors, though, which may roughly correspond but I think are mostly about avoidable vs. unavoidable errors.In the case of
add
you'd use a logic error if the author's intent is that a program which provides mismatched lists has bugs and needs fixing, or a runtime exception if it's just one of those things that might happen. For instance if your function were to handle arbitrary generators, that don't necessarily have a means of reporting their length short of destructively evaluating the whole sequence, you'd be more likely consider it an unavoidable error condition.Calling it a logic error implies that it's the caller's responsibility to check the length before calling
add
, if they can't ensure it by the exercise of pure reason. So they would not be passing in a list from a user without explicitly checking the length first, and in all honesty should count themselves lucky they even got an exception rather than undefined behavior.Calling it a runtime error expresses that it's "reasonable" (if abnormal) to pass in lists of different lengths, with the exception indicating that it happened on this occasion. Hence I think an enforcement rather than an assertion.
In the case of
filesize
: for the existence of a file, you should if possible treat that as a potentially recoverable failure (enforcement), not a bug (assertion). The reason is simply that there is no way for the caller to be certain that a file exists - there's always someone with more privileges who can come along and remove it, or unmount the entire fielsystem, in between a check for existence and a call tofilesize
. It's therefore not necessarily a logical flaw in the calling code when it doesn't exist (although the end-user might have shot themselves in the foot). Because of that fact it's likely there will be callers who can treat it as just one of those things that happens, an unavoidable error condition. Creating a file handle could also fail for out-of-memory, which is another unavoidable error on most systems, although not necessarily a recoverable one if for example over-committing is enabled.Another example to consider is
operator[]
vs.at()
for C++'s vector.at()
throwsout_of_range
, a logic error, not because it's inconceivable that a caller might want to recover, or because you have to be some kind of numbskull to make the mistake of accessing an array out of range usingat()
, but because the error is entirely avoidable if the caller wants it to be - you can always check thesize()
before access if you have no other way of knowing whether your index is good or not. And sooperator[]
doesn't guarantee any checks at all, and in the name of efficiency an out of range access has undefined behavior.assert
应被视为“运行时检查注释”,指示程序员当时所做的假设。assert
是函数实现的一部分。失败的断言在做出错误假设的地方(因此在断言的代码位置)应始终被视为错误。要修复该错误,请使用适当的方法来避免这种情况。避免错误函数输入的正确方法是契约,因此示例函数应该有一个输入契约来检查 range2 是否至少与 range1 一样长。实现中的断言仍然可以保留。特别是在更长、更复杂的实现中,这样的断言可以提高可理解性。
enforce
是一种抛出运行时异常的惰性方法。这对于快速而肮脏的代码来说是很好的,因为最好在那里进行检查,而不是默默地忽略出现不良情况的可能性。对于生产代码,它应该被替换为抛出更有意义的异常的适当机制。assert
should be considered a "run-time checked comment" indicating an assumption that the programmer makes at that moment. Theassert
is part of the function implementation. A failedassert
should always be considered a bug at the point where the wrong assumption is made, so at the code location of the assert. To fix the bug, use a proper means to avoid the situation.The proper means to avoid bad function inputs are contracts, so the example function should have a input contract that checks that range2 is at least as long as range1. The assertion inside the implementation could then still remain in place. Especially in longer more complex implementations, such an assert may inprove understandability.
An
enforce
is a lazy approach to throwing runtime exceptions. It is nice for quick-and-dirty code because it is better to have a check in there rather then silently ignoring the possibility of a bad condition. For production code, it should be replaced by a proper mechanism that throws a more meaningful exception.我相信您自己已经部分回答了您的问题。断言必然会破坏流程。如果你的主张是错误的,你将不会同意继续做任何事。如果你强制执行某件事,你就是在根据情况做出允许某事发生的决定。如果您发现不满足条件,您可以强制拒绝进入特定部分。
I believe you have partly answered your question yourself. Assertions are bound to break the flow. If your assertion is wrong, you will not agree to continue with anything. If you enforce something you are making a decision to allow something to happen based on the situation. If you find that the conditions are not met, you can enforce that the entry to a particular section is denied.