适当使用断言和例外

Har*_*nak 1 c# debugging assert exception

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

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

class Rectangle{
    private int height;
    private int length;

    public int Height{
        get => height;
        set{
            //avoid to put negative heights
        }
    }
    //same thing for length
}
Run Code Online (Sandbox Code Playgroud)

让我说一下,我不是在讨论如何在这个例子中处理用户输入,因为我可以为此做一个简单的控制流程.虽然,我面临的想法是,在其他地方可能会出现一些意外错误,我希望能够检测到这一点,因为我不想要一个带有invald值的对象.所以我可以:

  • Debug.Assert如果发生这种情况,请使用并停止程序,这样我就可以纠正可能出现的错误.
  • 扔一个ArgumentOutOfRangeException基本上做同样的事情?这感觉不对,所以只有当我知道我要在某个地方处理它时我才应该使用它.虽然,如果我知道在哪里处理异常,我不应该解决它所在的问题吗?或者它可能意味着可能发生的事情,但你不能直接在你的代码中控制,比如用户输入(可以处理,没有例外,但可能是别的东西不能)或加载数据?

问题:我是否明白了断言和例外的含义?另外,请举例说明处理异常是否有用(因为之前你无法控制的东西)?除了我提到的情况之外,我无法弄清楚还有什么可以发生,但我显然仍然缺乏经验.
为了扩展我的问题:我可以想出为什么可以抛出异常的各种原因,比如a NullReferenceException,a IndexOutOfBoundsException,IO异常,DirectoryNotFoundException或者FileNotFoundException等等.虽然,我无法弄清楚处理它们变得有用的情况,分开从简单地停止程序(在这种情况下,不应该使用断言?)或给出一个简单的消息,问题发生在哪里.我知道即使这是有用的,例外也意味着对"错误"进行分类并提供如何解决它们的线索.虽然,这是一个简单的消息,它们真的对它们有用吗?这听起来很可疑,所以我会坚持"我从来没有遇到过适当的情况,"经验的原因"口头禅.

示例2:现在让我们使用第一个示例来讨论用户输入.正如我所预料的那样,我不会仅仅使用例外来检查值是否为正,因为这是一个简单的控制流程.但是如果用户输入一个字母会发生什么?我应该在这里处理一个例外(可能很简单ArgumentException)并在catch块中给出一条消息吗?或者也可以通过控制流程来检查(检查输入是否类型int,或类似的东西)?

感谢任何能够消除我挥之不去的怀疑的人.

Eri*_*ert 10

抛出ArgumentOutOfRangeException基本上做同样的事情?这感觉不对,所以只有当我知道我要在某个地方处理它时我才应该使用它.虽然,如果我知道在哪里处理异常,我不应该解决它所在的问题吗?

你的推理在这里相当不错,但并不完全正确.你正在努力的原因是因为C#中的件事情使用了异常:

  • 愚蠢的例外.当调用者知道参数无效时,骨头异常就像"无效参数" .如果抛出了一个骨头异常,那么调用者就会有一个应该修复的bug.你永远不会有一个catch(InvalidArgumentException)测试用例,因为它永远不会被投入生产.存在这些例外是为了帮助您的呼叫者在他们犯错时通过非常大声地告诉他们来编写正确的代码.

  • 烦恼的异常是骨头异常,其中调用者无法知道参数无效.这些是API中的设计缺陷,应予以消除.它们要求您使用try-catches包装API调用以捕获看起来像应该避免的异常,而不是捕获.如果你发现你正在编写需要调用者在try-catch中包装调用的API,那么你做错了.

  • 致命异常是线程中止,内存不足等异常.发生了一件可怕的事情,这个过程无法继续.抓住这些因素并没有什么意义,因为你无法改善这种状况,你可能会让情况变得更糟.

  • 外部异常是"网络电缆被拔掉"之类的东西.您希望插入网络电缆; 它不是,并且你没办法检查它是否是,因为检查时间和使用时间是不同的时间; 这两次之间可以拔掉电缆.你必须抓住这些.

现在你知道了四种异常是什么,你可以看到异常和断言之间的区别.

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

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

一个throw相反的声明,以及每个语句应该具有其行使的测试用例."当错误的调用者传递null时,此API抛出"是其合同的一部分,并且该合同应该是可测试的.如果您发现编写的throw语句没有可能的测试用例来验证它们是否抛出,请考虑将其更改为断言.

最后,永远不要传递无效的参数然后捕获一个愚蠢的异常.如果您正在处理用户输入,那么UI层应该验证输入在语法上是否有效,即数字是预期数字的数字.UI层不应该将可能不受欢迎的用户代码传递给更深层的API,然后处理生成的异常.