代码之家  ›  专栏  ›  技术社区  ›  Nathan

断言是邪恶的吗?[关闭]

  •  193
  • Nathan  · 技术社区  · 5 年前

    这个 Go 语言创建者 write :

    Go不提供断言。 不可否认,它们很方便,但我们的经验是,程序员使用它们作为拐杖,以避免考虑正确的错误处理和报告。正确的错误处理意味着服务器在发生非致命错误后继续运行,而不是崩溃。正确的错误报告意味着错误是直接的,而且是切中要害的,这样可以避免程序员解释一个大的崩溃跟踪。当程序员不熟悉代码时,精确的错误尤其重要。

    你对此有何看法?

    21 回复  |  直到 10 年前
        1
  •  315
  •   caf    15 年前

    不,没有什么问题 assert 只要你按计划使用它。

    也就是说,它应该用于在调试期间捕获“不可能发生”的情况,而不是正常的错误处理。

    • 断言:程序逻辑本身的故障。
    • 错误处理:错误的输入或系统状态,不是由程序中的错误引起的。
        2
  •  106
  •   gahooa    15 年前

    不,都不 goto 也不 assert 是邪恶的。但两者都可能被误用。

    断言用于进行健全性检查。如果不正确的话,应该杀死程序的东西。不用于验证或作为错误处理的替代。

        3
  •  61
  •   jalf    15 年前

    按照这种逻辑,断点也是邪恶的。

    断言应该用作调试辅助,而不是其他任何东西。”邪恶”是当你尝试使用它们时 相反 错误处理。

    断言可以帮助您、程序员、检测和修复不存在的问题,并验证您的假设是否正确。

    它们与错误处理无关,但不幸的是,一些程序员滥用它们,然后宣称它们是“邪恶的”。

        4
  •  39
  •   arhuaco    15 年前

    我很喜欢使用断言。当我第一次构建应用程序时(也许对于新的域),我发现它非常有用。我没有做非常花哨的错误检查(我会考虑提前优化),而是快速编码,并添加了很多断言。在我了解了更多关于事情如何工作的知识之后,我重写并删除一些断言,然后更改它们以更好地处理错误。

    由于断言的原因,我花费了大量的时间编写/调试程序。

    我还注意到断言帮助我思考许多可能破坏程序的事情。

        5
  •  30
  •   Magnar    10 年前

    作为附加信息,Go提供了内置功能 panic . 这可以用来代替 assert . 例如。

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

    恐慌 将打印堆栈跟踪,因此在某种程度上它具有 断言 .

        6
  •  29
  •   Alex Budovski    15 年前

    它们应该用于检测程序中的错误。不错的用户输入。

    如果使用正确,它们是 邪恶的。

        7
  •  13
  •   jtolle    15 年前

    这就产生了很多问题,我认为一个使断言的辩护变得混乱的问题是,它们通常基于论点检查。所以考虑一下这个不同的例子,当您可能使用断言时:

    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
    

    您对输入使用异常,因为您希望有时会得到错误的输入。您断言对列表进行排序是为了帮助您在算法中找到一个错误,根据定义,这是您所不期望的。断言只在调试版本中,所以即使检查很昂贵,您也不介意在每次调用例程时都这样做。

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

        8
  •  8
  •   figurassa    15 年前

    断言并不邪恶,但很容易被滥用。我同意“断言经常被用作拐杖,以避免考虑正确的错误处理和报告”。我经常看到这个。

    就我个人而言,我喜欢使用断言,因为它们记录了我在编写代码时可能做出的假设。如果在维护代码的同时打破了这些假设,则可以在测试期间检测到问题。但是,在进行生产构建(即,使用ifdefs)时,我确实要从代码中剥离出每个断言。通过剥离生产构建中的断言,我消除了任何人将它们作为拐杖滥用的风险。

    断言还有另一个问题。断言只在运行时检查。但通常情况下,您希望执行的检查可能是在编译时执行的。最好在编译时检测一个问题。对于C++程序员来说,Boost提供了BooStyStistAsAsHyt,允许您这样做。对于C程序员,本文( link text )描述一种可用于在编译时执行断言的技术。

    总之,我遵循的经验法则是:不要在生产构建中使用断言,如果可能的话,只对编译时无法验证的东西使用断言(即必须在运行时检查)。

        9
  •  5
  •   StackedCrooked    15 年前

    我承认在考虑错误报告时使用了断言。但是,这并不能说明它们在正确使用时非常有用。

    如果你想遵循“早点崩溃”的原则,它们尤其有用。例如,假设您正在实现一个引用计数机制。在代码的某些位置,您知道refcount应该是零或一。另外,假设refcount错误,程序不会立即崩溃,但是在下一个消息循环期间,很难找出出错的原因。断言将有助于检测更接近其来源的错误。

        10
  •  5
  •   Pavel Radzivilovsky    15 年前

    我更喜欢避免在调试和发布中执行不同操作的代码。

    但是,在条件下中断调试器并拥有所有文件/行信息非常有用,这也是精确表达式和精确值。

    断言“只在调试中评估条件”可能是一种性能优化,因此,只有在0.0001%的程序中才有用,因为人们知道他们在做什么。在所有其他情况下,这是有害的,因为表达式实际上可能改变程序的状态:

    assert(2 == ShroedingersCat.GetNumEars()); 会使程序在调试和发布时做不同的事情。

    我们已经开发了一组断言宏,它将引发异常,并在调试和发布版本中执行该操作。例如, THROW_UNLESS_EQ(a, 20); 将引发一个异常,其中what()消息同时具有文件、行和 实际价值 等等。只有宏才有这种能力。调试程序可能被配置为在特定异常类型的“throw”处中断。

        11
  •  5
  •   Martin    14 年前

    我非常不喜欢断言。但我不会说他们是邪恶的。

    基本上,断言和未检查的异常一样,唯一的异常是断言(通常)不应该为最终产品保留。

    如果您在调试和构建系统时为自己构建了一个安全网,为什么您会拒绝为您的客户、支持服务台或任何可以使用您当前构建的软件的人提供此安全网?只对断言和异常情况使用异常。通过创建一个适当的异常层次结构,您将能够很快地分辨出其中一个。除此之外,断言仍然存在,并且可以在失败时提供有价值的信息,否则将丢失。

    因此,我完全理解go的创建者,通过完全删除断言并强制程序员使用异常来处理这种情况。对此有一个简单的解释,例外只是一个更好的机制,为什么要坚持古老的断言?

        12
  •  3
  •   brendan    15 年前

    简短回答:不,我相信断言是有用的

        13
  •  3
  •   David Stone    12 年前

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

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

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

    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);
    }
    

    这给了我两层支票。任何在运行时确定数据的地方都会得到异常或立即的错误处理。但是,额外的入住 Class::f assert 语句意味着如果某些内部代码调用 班级:: 我还有健康检查。我的内部代码可能没有传递有效的参数(因为我可能 value 从一些复杂的函数系列),所以我喜欢在设置函数中有一个断言来记录,不管谁在调用函数, 价值 不能大于或等于 end .

    这似乎符合我在一些地方读到的内容,即在一个运行良好的程序中断言不可能被违反,而异常情况应该适用于仍然可能出现的异常和错误情况。因为从理论上讲,我正在验证所有输入,所以不可能触发我的断言。如果是,我的程序是错误的。

        14
  •  1
  •   Agnel Kurian    15 年前

    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;
    }
    

    所以…从长远来看…在大局中…我必须同意 断言 可以被滥用。我总是这么做。

        15
  •  1
  •   akuhn    15 年前

    assert 因错误处理而被滥用,因为它的类型较少。

    因此,作为语言设计者,他们更应该看到,可以通过更小的输入来完成正确的错误处理。排除断言,因为您的异常机制是verbose而不是解决方案。哦,等等,Go也没有例外。太糟糕了:)

        16
  •  1
  •   Peter Mortensen    14 年前

    当我看到这一点时,我觉得自己像是在踢作者的头。

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

    异常也更容易与我不喜欢的生产代码混合。断言比 throw new Exception("Some generic msg or 'pretend i am an assert'");

        17
  •  1
  •   Evan Carroll    14 年前

    我对这些答案的问题是,辩护断言没有人明确指出它与常规断言的不同之处。 致命错误 以及为什么断言不能是 例外 . 现在,有了这句话,如果这个例外永远不会被发现呢?这是否使它成为一个命名断言?而且,为什么你会想要在语言中施加一个可以引发一个异常的限制,这个异常是/没有/可以处理的?

        18
  •  1
  •   Joseph Pecoraro    14 年前

    是的,断言是邪恶的。

    它们经常被用于应该使用正确错误处理的地方。从一开始就习惯于写正确的生产质量错误处理!

    通常它们会妨碍编写单元测试(除非您编写了一个与测试工具交互的自定义断言)。这通常是因为在应该使用正确错误处理的地方使用它们。

    大多数情况下,它们是从发布版本中编译出来的,这意味着当您运行实际发布的代码时,它们的“测试”都不可用;考虑到在多线程情况下,最糟糕的问题通常只出现在发布代码中,这可能是很糟糕的。

    有时它们是破坏设计的拐杖;也就是说,代码的设计允许用户以不应该调用的方式调用它,断言“阻止”了这一点。修复设计!

    我在2005年的博客上写了更多这方面的内容,这里是: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html

        19
  •  1
  •   longday    10 年前

    如果您所说的断言意味着程序呕吐然后存在,那么断言可能非常糟糕。这并不是说他们是 总是 使用错误的东西,它们是一个很容易被滥用的结构。他们还有许多更好的选择。像这样的事情是被称为邪恶的好候选人。

    例如,第三方模块(或任何真正的模块)几乎不应该退出调用程序。这并不能让调用程序的程序员控制此时程序应该承担的风险。在许多情况下,数据非常重要,即使保存损坏的数据也比丢失数据要好。断言会强制您丢失数据。

    断言的一些替代方法:

    • 使用调试器,
    • 控制台/数据库/其他日志记录
    • 例外情况
    • 其他类型的错误处理

    一些参考文献:

    即使提倡断言的人也认为他们应该只用于发展和 生产中:

    这个人说,当模块有可能损坏在引发异常后仍然存在的数据时,应该使用断言: http://www.advogato.org/article/949.html . 这当然是一个合理的观点,但是,外部模块应该 从未 规定损坏的数据对调用程序有多重要(通过退出“for”它们)。处理这种情况的正确方法是抛出一个异常,它使程序现在可能处于不一致的状态。而且,由于好的程序主要由模块组成(在主可执行文件中有一些粘合代码),断言几乎总是错误的。

        20
  •  0
  •   Charles Eli Cheese    15 年前

    与其说是邪恶,不如说是适得其反。永久性错误检查和调试之间有一个分离。断言使人们认为所有调试都应该是永久的,并且在经常使用时会导致大量的可读性问题。永久性错误处理应该比需要的地方更好,因为断言会导致自己的错误,这是一个相当可疑的实践。

        21
  •  0
  •   bob    12 年前

    我从不使用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.
    }