4

我很难选择是否应该在 D 中“强制”一个条件或“断言”一个条件。(不过,这与语言无关。)

从理论上讲,我知道您使用断言来查找错误,并强制执行其他条件以检查非典型条件。例如,您可能会说assert(count >= 0)为您的方法提供一个参数,因为这表明调用者存在错误,并且您会说enforce(isNetworkConnected),因为那不是错误,它只是您假设的东西,这很可能不是真的超出您控制范围的合法情况。

此外,断言可以作为优化从代码中删除,没有副作用,但不能删除强制执行,因为它们必须始终执行其条件代码。因此,如果我正在实现一个惰性填充容器,它在第一次访问它的任何方法时填充自己,我说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();
    }
}

这应该是断言还是强制执行?(范围没有同时为空是调用者的错吗?它可能无法控制范围的来源——它可能来自用户——但话又说回来,它仍然看起来像一个错误,不是吗?)

或者这是另一个伪代码示例:

uint getFileSize(string path)
{
    HANDLE hFile = CreateFile(path, ...);
    assert(hFile != INVALID_HANDLE_VALUE); //Assertion or enforcement?
    return GetFileSize(hFile); //and close the handle, obviously
}
...

这应该是断言还是强制执行?路径可能来自用户——因此它可能不是一个错误——但它仍然是此方法的前提条件,即路径应该是有效的。我主张还是强制执行?

谢谢!

4

3 回答 3

1

我不确定它是否完全与语言无关。我使用的任何语言都没有 enforce()如果我遇到了这样的语言,那么我会想以他们想要的方式使用assertenforce这可能是该语言的惯用语。

例如assert在 C 或 C++ 中,当程序失败时停止程序,它不会抛出异常,因此它的用法可能与您所说的不同。你不要assert在 C++ 中使用,除非你认为调用者已经犯了一个严重到无法依靠它们来清理的错误(例如传递负数),或者其他地方的一些其他代码已经做了一个错误如此严重,以至于程序应被视为处于未定义状态(例如,您的数据结构似乎已损坏)。不过,C++ 确实区分了运行时错误和逻辑错误,这可能大致对应,但我认为主要是关于可避免与不可避免的错误。

如果add作者的意图是提供不匹配列表的程序存在错误并需要修复,或者如果它只是可能发生的事情之一,那么您会使用逻辑错误。例如,如果您的函数要处理任意生成器,而这些生成器不一定能够报告它们的长度而不是破坏性地评估整个序列,那么您更有可能认为这是一个不可避免的错误条件。

将其称为逻辑错误意味着调用者有责任在调用之前检查长度add,如果他们不能通过纯粹的理由来确保它。因此,如果不先明确检查长度,他们就不会从用户那里传递一个列表,老实说,他们应该算自己幸运,他们甚至得到了异常而不是未定义的行为。

将其称为运行时错误表示传入不同长度的列表是“合理的”(如果异常),但表示它发生在这种情况下的例外情况。因此,我认为是强制执行而不是断言。

filesize: 对于文件的存在的情况下,如果可能,您应该将其视为潜在的可恢复故障(强制),而不是错误(断言)。原因很简单,调用者无法确定文件是否存在 - 在检查是否存在和调用filesize. 因此,当调用代码不存在时,它不一定是一个逻辑缺陷(尽管最终用户可能会在自己的脚下开枪)。由于这个事实,很可能会有调用者将其视为发生的事情之一,这是一种不可避免的错误情况。创建文件句柄也可能因内存不足而失败,这是大多数系统上另一个不可避免的错误,尽管如果启用了过度提交,则不一定是可恢复的。

另一个要考虑的例子是operator[]at()C++ 的向量的对比。at()throws out_of_range,一个逻辑错误,不是因为调用者可能想要恢复是不可想象的,或者因为你必须是某种麻木的人才能犯使用访问超出范围的数组at()的错误,而是因为如果该错误是完全可以避免的调用者希望它是 -size()如果您没有其他方法知道您的索引是否良好,您可以随时检查之前的访问。因此operator[]根本不保证任何检查,并且以效率的名义,超出范围的访问具有未定义的行为。

于 2011-02-25T19:06:49.443 回答
1

assert应该被认为是“运行时检查的注释”,表明程序员当时做出的假设。assert是函数实现的一部分。在做出错误假设的地方,失败assert应该总是被认为是一个错误,所以在断言的代码位置。要修复该错误,请使用适当的方法来避免这种情况。

避免错误函数输入的正确方法是合约,因此示例函数应该有一个输入合约来检查 range2 是否至少与 range1 一样长。然后,实现内部的断言仍然可以保留。尤其是在更长更复杂的实现中,这样的断言可能会提高可理解性。

Anenforce是一种抛出运行时异常的惰性方法。对于快速而肮脏的代码来说这很好,因为最好在那里进行检查,而不是默默地忽略不良条件的可能性。对于生产代码,应该用适当的机制替换它,该机制会引发更有意义的异常。

于 2018-07-18T06:24:48.583 回答
0

我相信您自己已经部分回答了您的问题。断言必然会破坏流程。如果您的断言是错误的,您将不同意继续做任何事情。如果你强制执行某事,你就是在根据情况做出允许某事发生的决定。如果您发现不满足条件,您可以强制拒绝进入特定部分。

于 2011-02-25T17:28:13.563 回答