【问题标题】:Appropriate usage of assertions and exceptions正确使用断言和异常
【发布时间】:2017-04-24 13:04:20
【问题描述】:

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

示例 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 基本上做同样的事情?这感觉不对,所以只有当我知道我要在某个地方处理它时才应该使用它。不过,如果我知道在哪里处理异常,我不应该解决它所在的问题吗? 或者它可能是针对可能发生的事情,但 您无法直接在代码中进行控制,比如用户输入(可以处理,无一例外,但也许其他东西不能)或加载数据?

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

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

感谢任何能解决我挥之不去的疑问的人。

【问题讨论】:

  • 没有一个正确的答案,这完全取决于您拥有的支持系统来处理您的代码的客户端程序员出错并且不知道为什么。输入无效数据的用户也不例外。
  • 我知道这在很大程度上取决于情况,我只是想弄清楚可能会出现什么样的情况。另外,坚持这个简单的例子,相反,如果我的意思是验证值只是为了检测代码中的错误,我应该使用Debug.Assert。如果我打算实现保存/加载数据并且加载的文件中的某些内容可能会失败,我可以捕获它并使用“数据损坏”消息停止程序。这种推理是否正确,或者我没有得到断言和/或异常的目的?
  • 这是一个不完整的问题。通过鼓励(或强迫)他们输入正确的数据来与用户打交道。当程序可能只是帮助用户时停止程序似乎没有必要
  • 加载的文件更复杂,因为您无法控制源。
  • 这主要是一种意见/情况。如果这是一个除以零事件,我会亲自更改零并通知用户我做了。如果这是一个复杂的损坏,那么告诉他们文件是垃圾。

标签: c# debugging exception assert


【解决方案1】:

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

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

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

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

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

  • 外部异常是诸如“网络电缆被拔下”之类的东西。您希望插入网络电缆;不是,而且你也没有办法早点检查是不是,因为检查的时间和使用的时间是不同的时间;可以在这两次之间拔下电缆。你必须抓住这些。

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

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

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

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

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

【讨论】:

  • 出色的答案,解决了许多疑问。这正是我正在寻找的东西。谢谢!只是对拔出电缆示例的最后一个疑问。我的意思是,我不确定我是否做对了,但我觉得在这种情况下你也不能通过抓住它来做很多事情。或者你可以吗?
【解决方案2】:

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

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

这只是一个例子。因此,如果 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 可能会在您的应用程序的其他地方处理。

【讨论】:

  • “嘿程序员”部分真的很解释。我想我明白了断言的目的:)谢谢。尽管如此,我仍然对处理异常有一些疑问。假设我有一个加载一些数据然后调用 Divide 对该数据进行操作的方法(我使用加载数据或用户输入场景,因为它们是我现在唯一能想到的情况;也许 那就是 我仍然对 处理 异常有疑问的原因)。我可以处理一个异常来给出一条消息“嘿用户,你放了一个错误的文件。”如果出现问题?
  • 首先您应该考虑是否可以处理此异常。例如,如果您将 x / 0 相除,那么也许您可以将某种默认值作为结果。但是,如果您真的不知道在那种“异常”情况下该怎么做,那么您必须通知用户文件已损坏或某事。但是不要被我写的关于设置默认值的内容误导。不要使用异常处理来检查事物。异常很昂贵,应该只在发生错误时使用。
  • 我明白了。谢谢你的解释:)
猜你喜欢
  • 1970-01-01
  • 2021-10-27
  • 2017-12-14
  • 1970-01-01
  • 2012-09-14
  • 2014-06-13
  • 2018-09-26
  • 2020-10-25
  • 2018-08-21
相关资源
最近更新 更多