Go语言的创造者写道:

Go doesn't provide assertions. They are undeniably convenient, but our experience has been that programmers use them as a crutch to avoid thinking about proper error handling and reporting. Proper error handling means that servers continue operation after non-fatal errors instead of crashing. Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace. Precise errors are particularly important when the programmer seeing the errors is not familiar with the code.

你对此有什么看法?


当前回答

当我看到这个的时候,我真想踢作者的头。

我在代码中一直使用断言,并最终在编写更多代码时将它们全部替换掉。当我还没有编写所需的逻辑时,我就会使用它们,当我运行到代码时,我想要得到警告,而不是编写一个将在项目接近完成时删除的异常。

异常也更容易融入到产品代码中,这是我不喜欢的。断言比抛出新异常("一些通用的msg或'pretend i am An assert'")更容易注意到;

其他回答

Assert非常有用,可以在出现意外错误时通过在出现问题的第一个迹象时停止程序来节省大量回溯时间。

另一方面,断言很容易被滥用。

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

正确的说法应该是:

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

所以…从长远来看……从大局来看……我必须同意断言可能会被滥用。我一直都这么做。

我最近开始在我的代码中添加一些断言,这是我一直在做的:

我在心里把代码分为边界代码和内部代码。边界代码是处理用户输入、读取文件和从网络获取数据的代码。在这段代码中,我在一个循环中请求输入,该循环仅在输入有效时退出(在交互式用户输入的情况下),或者在不可恢复的文件/网络损坏数据的情况下抛出异常。

内部代码就是一切。例如,在我的类中设置变量的函数可以定义为

void Class::f (int value) {
    assert (value < end);
    member = value;
}

从网络获取输入的函数可以这样读:

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

This gives me two layers of checks. Anything where the data is determined at run-time always gets an exception or immediate error handling. However, that extra check in Class::f with the assert statement means that if some internal code ever calls Class::f, I still have a sanity check. My internal code might not pass a valid argument (because I may have calculated value from some complex series of functions), so I like having the assertion in the setting function to document that regardless of who is calling the function, value must not be greater than or equal to end.

这似乎符合我在一些地方读到的内容,即在一个功能良好的程序中,断言应该是不可能违反的,而例外应该是针对仍然可能发生的异常和错误情况。因为理论上我要验证所有输入,所以我的断言不应该被触发。如果是,我的程序就错了。

这种情况经常出现,我认为让断言辩护令人困惑的一个问题是,它们通常基于参数检查。所以考虑一下这个不同的例子,当你使用断言时:

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

您对输入使用异常是因为您预计有时会得到错误的输入。您断言对列表进行排序是为了帮助您找到算法中的bug,根据定义,这是您所不期望的。断言只存在于调试版本中,因此即使检查的开销很大,您也不介意对例程的每次调用都进行检查。

您仍然需要对产品代码进行单元测试,但这是确保代码正确的一种不同的补充方式。单元测试确保您的例程符合其接口,而断言是一种更细粒度的方法,以确保您的实现完全按照您的期望进行。

我从不使用assert(),示例通常是这样的:

int* ptr = new int[10];
assert(ptr);

这很糟糕,我从来没有这样做过,如果我的游戏分配了一堆怪物怎么办?为什么我要让游戏崩溃,相反,你应该优雅地处理错误,所以可以这样做:

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}

作为附加信息,go提供了一个内置函数panic。这可以用来代替assert。如。

if x < 0 {
    panic("x is less than 0");
}

Panic将打印堆栈跟踪,因此在某种程度上它具有assert的目的。