适当使用断言和例外

时间:2017-04-24 13:04:20

标签: 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
}

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

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

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

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

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

2 个答案:

答案 0 :(得分:10)

  

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

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

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

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

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

  • 外生异常是“网络电缆已拔下”之类的内容。您希望插入网络电缆;它不是,并且你没办法检查它是否是,因为检查时间和使用时间是不同的时间;这两次之间可以拔掉电缆。你必须抓住这些。

既然您知道这四种异常是什么,那么您可以看到异常和断言之间的区别。

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

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

相比之下,throw是一个声明,每个声明都应该有一个练习它的测试用例。 “当错误的调用者传递null时,此API抛出”是其合同的一部分,并且该合同应该是可测试的。如果您发现您正在编写没有可能的测试用例的throw语句来验证它们是否抛出,请考虑将其更改为断言。

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

答案 1 :(得分:2)

我是这样看的。断言是针对程序员的。例外情况适用于用户。您可以在代码中包含您期望特定值的位置。然后你可以把断言,例如:

public int Age
{
    get { return age; }
    set
    {
        age = value;
        Debug.Assert(age == value);
    }
}

这只是一个例子。所以,如果年龄!=价值,没有例外。但是,#嘿嘿程序员,可能发生了一些奇怪的事情,看看这部分代码"。

当应用程序不知道如何在特定情况下做出反应时,您会使用例外。例如:

public int Divide(int a, int b)
{
    Debug.Assert(b != 0); //this is for you as a programmer, but if something bad happened, user won't see this assert, but application also doesn't know what to do in situation like this, so you will add:

    if(b == 0)
        throw SomeException();
}

SomeException可能会在您的应用程序中的其他位置处理。