dotnet 中的断言

工作了几年,发现 dotnet 开发者几乎很少使用断言,似乎这个古老又神秘的术语逐渐消失在 dotnet 圈子,有人认为断言没什么用,但搜索下大爹微软的代码会发现,断言几乎无处不在。

使用断言的两种场景

1、当条件断点使用

foreach(var user in users)
{
    Debug.Assert(user.name == "Bulma");

    //  其他代码
}

断言从表现上来说,只影响 debug 环境,当断言失败时,程序中断运行,就像执行到调试断点,此时可以观察代码和变量来达到调试程序的目的,这种情况调试完程序,一般会删除断言。

2、断言

User GetUser(int id)
{
    Debug.Assert(_dbContext != null);

    return _dbContext.Users.Find(id);
}

绝大多数情况,我们说到断言都是指对代码提出假设,比如在 efcore 的一个仓储实现中,查询数据前,我们断言 dbContext 不为 null。

本文研究的都是第二种场景下的 Debug.Assert 方法。

最佳实践

摘自 John Robbins 《Debugging Microsoft .NET 2.0 Applications》

  1. 大胆的断言,断言永远也不嫌多
  2. 断言不会替代异常,异常涵盖了代码所要求的内容,断言涵盖了它所假设的事物
  3. 一个写得很好的断言不仅可以告诉你发生了什么事情和发生在哪里,还可以告诉你原因
  4. 异常消息通常是含糊不清的,要求你向后遍历代码以重新创建导致错误的上下文。断言可以保留发生错误时程序的状态
  5. 断言也可以作为文档,它告诉其他开发人员你的代码所依赖的隐含假设
  6. 断言失败时出现的对话框使您可以将调试器附加到进程,因此您可以在堆栈周围戳一下,就好像在其中放置了一个断点一样

断言和异常的区别

  1. 断言不会替代异常,异常涵盖了代码所要求的内容,断言涵盖了它所假设的事物
  2. 从结果来看,捕获到程序中的异常,通常先做一些处理(例如记日志),然后选择抛出或者忽略,可能会在后续版本修复发生异常的原因
  3. 而断言失败时,处于开发调试阶段,断言失败的代码会在开发阶段修复

初次使用断言肯定有人疑问,使用断言的时候完全可以用异常去代替,例如。

if (dbContext == null)
{
    throw new Exception();
}

并且异常同时作用于 debug 和 release 版本,这样的话断言还有存在的意义么。

断言最大作用并不是报出错误,而是提示开发者一些假设,这点和 TDD 的思想有点不谋而合,TDD 要求先编写测试代码再编写功能代码。而断言呢?在开发阶段,很多功能都没有完整,比如说我们在 A 类中包含了 B 类,但是 B 类是否初始化或者如何初始化还未知,但 B 类一定要在调用 A.Foo 时已经初始化好的,这就是一个假设,这个时候就可以留个断言。

class A
{
    private B b;

    public void Foo()
    {
        Debug.Assert(b != null);

        b.Foo();
    }
}

断言的优点

  1. 不影响性能
  2. 把假设传达给读者
  3. 检查代码中的错误

其他

  1. 发生异常时,堆栈的行号定位并不会因为使用了断言而不准确
  2. 断言失败并不会被异常捕获

小结

断言最终都是捕获不可能发生的错误,都将等价为 Debug.Assert(true) ,并且能够删除,但是又不必删除,因为断言可以把代码中的假设传递给读者。

断言适用于开发者自己能够完全掌控的代码,例如私有方法,类库。所有含有外部输入的代码,例如接口参数,业务规则这些都不适合使用断言。

https://stackoverflow.com/questions/129120/when-should-i-use-debug-assert
https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.debug.assert?view=net-5.0

posted @ 2021-05-15 20:06  菲利  阅读(114)  评论(0)    收藏  举报