c# - 正确使用断言和异常

标签 c# debugging exception assert

我已经阅读了一些内容,试图弄清楚何时适本地使用断言和异常,但我仍然缺少一些全局的东西。可能我只是需要更多的经验,所以我想举一些简单的例子来更好地理解在什么情况下我应该使用什么。

示例 1:让我们从无效值的经典情况开始。例如我有以下类,其中两个字段都必须为正数:

class Rectangle{
    private int height;
    private int length;

    public int Height{
        get => height;
        set{
            //avoid to put negative heights
        }
    }
    //same thing for length
}

请注意,在此示例中我不是在谈论如何处理用户输入,因为我可以为此制作一个简单的控制流。虽然,我正面临这样的想法,即其他地方可能存在一些意外错误,我希望检测到这一点,因为我不想要一个具有无效值的对象。这样我就可以:

  • 使用 Debug.Assert 并在发生这种情况时停止程序,以便我可以在可能的错误出现时更正它们。
  • 抛出一个 ArgumentOutOfRangeException 基本上做同样的事情?这感觉不对,所以只有当我知道我要在某个地方处理它时,我才应该使用它。不过,如果我知道在哪里处理异常,我不应该在问题所在的地方解决问题吗?或者它可能是针对可能发生但您无法在代码中直接控制的事情,比如用户输入(可以毫无异常(exception)地处理,但也许其他事情不能处理)或加载数据?

问题:我是否理解了断言和异常的含义?另外,能否请您举一个例子,说明处理异常是有用的(因为在发生之前您无法控制的事情)?除了我提到的情况之外,我不知道还会发生什么,但我显然仍然缺乏这方面的经验。
稍微扩展一下我的问题:我可以想到抛出异常的各种原因,例如 NullReferenceExceptionIndexOutOfBoundsException 等 IO 异常DirectoryNotFoundExceptionFileNotFoundException 等。虽然,除了简单地停止程序(在这种情况下,应该't an assertion be used?) 或给出问题发生位置的简单消息。我知道这甚至很有用,异常也意味着对“错误”进行分类并提供如何修复它们的线索。不过,一条简单的消息真的对它们有用吗这听起来很可疑,所以我会坚持“我从来没有遇到过合适的情况,‘因为经验’”的口头禅。

示例 2:现在让我们使用第一个示例来讨论用户输入。如我所料,我不会使用异常来检查值是否为正,因为这是一个简单的控制流。但是如果用户输入一个字母会发生什么?我是否应该在这里处理异常(可能是一个简单的 ArgumentException)并在 catch block 中给出消息?或者它也可以通过控制流来避免(检查输入是否为 int 类型,或类似的类型)?

感谢任何能消除我挥之不去的疑虑的人。

最佳答案

Throw an ArgumentOutOfRangeException to basically do the same thing? This feels wrong, so I should use that only if I know I'm going to handle it somewhere. Though, if I know where to handle the exception, shouldn't I fix the problem where it lies?

你的推理在这里很好,但不是完全正确的。你挣扎的原因是因为异常用于 C# 中的四个事物:

  • 愚蠢的异常(exception)。愚蠢的异常类似于“无效参数”当调用者可能知道参数无效时。如果抛出一个愚蠢的异常,那么调用者有一个应该修复的错误。你永远不会在测试用例之外有一个 catch(InvalidArgumentException) 因为它永远不应该在生产中抛出。这些异常的存在是为了帮助您的调用者编写正确的代码,方法是在他们犯错时大声告诉他们。

  • 令人烦恼的异常 是愚蠢的异常,其中调用者不知道参数无效。这些是 API 中的设计缺陷,应该被消除。它们要求您使用 try-catches 包装 API 调用,以捕获看起来应该避免而不是捕获的异常。如果您发现您正在编写的 API 要求调用方将调用包装在 try-catch 中,那么您做错了什么。

  • 致命异常 是线程中止、内存不足等异常。发生了可怕的事情,该过程无法继续。捕捉这些没有什么意义,因为您无法改善这种情况,而且可能会使情况变得更糟。

  • 外生异常是诸如“网络电缆被拔掉”之类的事情。您希望网络电缆已插入;不是,也没有办法早点去查是不是,因为查的时间和使用的时间不一样;电缆可以在这两次之间拔掉。你必须捕获这些。

既然您知道了四种异常是什么,您就可以了解异常和断言之间的区别了。

断言是在逻辑上必须始终为真的东西,如果不是,那么您就有一个应该修复的错误。您永远不会断言网络电缆已插入。您永远不会断言调用者提供的值不为空。 永远不应该有导致断言触发的测试用例;如果存在,则测试用例发现了错误。

您断言在就地排序算法运行后,非空数组中的最小元素位于开头。应该没有办法可以是假的,如果有,你有一个错误。所以断言这个事实。

相比之下,throw 是一个语句,每个语句都应该有一个测试用例来执行它。 “当有缺陷的调用者传递 null 时,此 API 抛出”是其契约的一部分,并且该契约应该是可测试的。如果您发现自己编写的 throw 语句没有可能的测试用例来验证它们是否抛出,请考虑将其更改为断言。

最后,永远不要传递无效的参数然后捕获一个愚蠢的异常。如果您正在处理用户输入,那么 UI 层应该验证输入在句法上是否有效,即数字应该是数字。 UI 层不应将可能未经审查的用户代码传递给更深层次的 API,然后处理由此产生的异常。

关于c# - 正确使用断言和异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43588874/

相关文章:

Java I/O 延迟在文件系统中创建文件

java - 我如何从容器管理的事务提交中捕获异常?

c# - 通过继承扩展枚举

ios - Xcode 9 - 框架断点

linux - 如何让 gdb 从已安装的库中加载符号?

Visual Studio 的 Python 工具在子进程中设置断点

c# - 将函数传递给 linq 查询

c# - 使用 Html Agility Pack 获取特定表

c# - ASP.NET MVC + LINQ 异常

c++ - 为什么未处理的异常会导致段错误?