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》
- 大胆的断言,断言永远也不嫌多
- 断言不会替代异常,异常涵盖了代码所要求的内容,断言涵盖了它所假设的事物
- 一个写得很好的断言不仅可以告诉你发生了什么事情和发生在哪里,还可以告诉你原因
- 异常消息通常是含糊不清的,要求你向后遍历代码以重新创建导致错误的上下文。断言可以保留发生错误时程序的状态
- 断言也可以作为文档,它告诉其他开发人员你的代码所依赖的隐含假设
- 断言失败时出现的对话框使您可以将调试器附加到进程,因此您可以在堆栈周围戳一下,就好像在其中放置了一个断点一样
断言和异常的区别
- 断言不会替代异常,异常涵盖了代码所要求的内容,断言涵盖了它所假设的事物
- 从结果来看,捕获到程序中的异常,通常先做一些处理(例如记日志),然后选择抛出或者忽略,可能会在后续版本修复发生异常的原因
- 而断言失败时,处于开发调试阶段,断言失败的代码会在开发阶段修复
初次使用断言肯定有人疑问,使用断言的时候完全可以用异常去代替,例如。
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();
}
}
断言的优点
- 不影响性能
- 把假设传达给读者
- 检查代码中的错误
其他
- 发生异常时,堆栈的行号定位并不会因为使用了断言而不准确
- 断言失败并不会被异常捕获
小结
断言最终都是捕获不可能发生的错误,都将等价为 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
浙公网安备 33010602011771号