Debug.Assert与特定的抛出异常

时间:2020-03-05 18:52:56  来源:igfitidea点击:

我刚刚开始浏览John Robbins的"调试MS .Net 2.0应用程序",并因他对Debug.Assert(...)的传播而感到困惑。

他指出,实施良好的断言存储了某种错误情况的状态,例如:

Debug.Assert(i > 3, "i > 3", "This means I got a bad parameter");

现在,就我个人而言,他如此热衷于在没有实际的"业务逻辑"评论的情况下喜欢重新测试,这似乎使我感到疯狂,也许"由于杂乱无章的搅扰过程,我永远不会发生i <= 3"。

因此,我认为我将Asserts看作是一种低级的"让我们保护自己的假设"之类的东西...假设有人认为这只是一种测试,我们只需要在调试中进行即可,也就是说,我们可以保护自己免受同事和他人的攻击。未来的程序员,并希望他们实际进行测试。

但是我没有得到的是,他接着说,除了正常的错误处理外,还应该使用断言。现在我设想的是这样的:

Debug.Assert(i > 3, "i must be greater than 3 because of the flibbity widgit status");
if (i <= 3)
{
    throw new ArgumentOutOfRangeException("i", "i must be > 3 because... i=" + i.ToString());
}

通过错误条件测试的Debug.Assert重复,我获得了什么?如果我们谈论非常重要的计算的仅调试双重检查,我想我会明白的。

double interestAmount = loan.GetInterest();
Debug.Assert(debugInterestDoubleCheck(loan) == interestAmount, "Mismatch on interest calc");

...但是对于参数测试,我确实不知道它是否值得检查(在DEBUG和Release版本中)...还是不值得。我想念什么?

解决方案

回答

IMO这只是在浪费开发时间。正确实施的异常可以使我们清楚地了解所发生的情况。我看到太多应用程序显示模糊的"断言失败:i <10"错误。我认为断言是一个临时解决方案。我认为程序的最终版本中不应包含任何断言。在我的实践中,我使用断言进行快速而肮脏的检查。最终版本的代码应考虑到错误情况并采取相应措施。如果发生不好的事情,我们有两种选择:处理还是保留。如果传入了错误的参数,函数应该抛出一个带有有意义描述的异常。在验证逻辑的重复中,我没有发现任何意义。

回答

断言不用于参数检查。应该始终进行参数检查(并精确地根据文档和/或者规范中指定的准备工作),并在必要时抛出" ArgumentOutOfRangeException"。

断言用于测试"不可能"的情况,即我们(在程序逻辑中)假设的情况是正确的。这些断言可以告诉我们这些假设是否由于任何原因而被打破。

希望这可以帮助!

回答

我使用显式检查,这些检查会在公共方法和受保护方法上引发异常,并在私有方法上引发断言。

通常,显式检查会防止私有方法始终看到不正确的值。所以实际上,断言是在检查应该不可能的条件。如果断言确实触发了,它会告诉我该类的公共例程之一中包含的验证逻辑存在缺陷。

回答

有一个通信方面可以断言vs引发异常。

假设我们有一个具有Name属性和ToString方法的User类。

如果ToString是这样实现的:

public string ToString()
{
     Debug.Assert(Name != null);
     return Name;
}

它说Name绝不能为null,如果是,则User类中存在错误。

如果ToString是这样实现的:

public string ToString()
{
     if ( Name == null )
     {
          throw new InvalidOperationException("Name is null");
     }

     return Name;
}

它说,如果Name为null,则调用者未正确使用ToString,因此应在调用之前进行检查。

两者的实现

public string ToString()
{
     Debug.Assert(Name != null);
     if ( Name == null )
     {
          throw new InvalidOperationException("Name is null");
     }

     return Name;
}

表示如果Name为null,则会在User类中出现错误,但是我们还是想处理它。 (用户无需在呼叫前检查姓名。)我认为这是Robbins建议的安全性。

回答

可以捕获并吞下异常,从而使错误对于测试是不可见的。使用Debug.Assert不会发生这种情况。

没有人应该拥有捕获所有异常的捕获处理程序,但是人们还是会这样做,有时这是不可避免的。如果代码是从COM调用的,则interop层将捕获所有异常并将其转换为COM错误代码,这意味着我们将看不到未处理的异常。断言不会受此困扰。

同样,如果无法处理异常,则更好的做法是进行小型转储。 VB比Cis更强大的地方是,可以在异常运行时使用异常过滤器捕捉小型转储,并保留其余的异常处理。 Gregg Miskelly的有关异常过滤器注入的博客文章提供了一种使用c#进行此操作的有用方法。

关于资产的另一注记...在单元测试代码中的错误情况时,它们的完整性差。值得一个包装器来关闭单元测试的断言。

回答

这是2美分。

我认为最好的方法是同时使用断言和异常。两种方法的主要区别,恕我直言,如果可以很容易地从应用程序文本中删除Assert语句(定义,条件属性...),而引发的异常(通常)则取决于条件代码,而后者很难删除(带预处理程序的multine部分)。

每个应用程序异常应得到正确处理,而断言只能在算法开发和测试期间得到满足。

如果将空对象引用作为常规参数传递,并且使用此值,则会得到空指针异常。确实:为什么要写断言?在这种情况下,这是浪费时间。
但是在类例程中使用的私有类成员呢?如果将这些值设置在某个位置,则最好使用断言检查是否设置了空值。这仅仅是因为当我们使用该成员时,会得到一个空指针异常,但是我们不知道该值是如何设置的。这会导致重新启动程序,从而破坏了用于设置私有成员的所有入口点。

异常更有用,但是(imho)它们的管理工作可能非常繁重,并且有可能使用过多的异常。并且它们需要额外的检查,可能不希望对代码进行优化。
就个人而言,我仅在代码需要深度捕获控制(在调用堆栈中catch语句非常低)或者函数参数未在代码中进行硬编码时才使用异常。

回答

在针对调试问题提供断言与断言方面的指导时,我已经考虑了很久很艰辛。

我们应该能够使用错误的输入,错误的状态,无效的操作顺序以及任何其他可能的错误条件来测试类,并且断言永远都不应跳闸。每个断言都在检查某事物应始终为真,而与输入或者执行的计算无关。

我得出的好的经验法则是:

  • 断言不能替代功能强大且功能独立于配置的健壮代码。它们是互补的。
  • 即使在输入无效值或者测试错误情况时,也不应在单元测试运行期间跳断断言。该代码应处理这些条件而不会发生断言。
  • 如果断言跳闸(在单元测试中或者在测试过程中),则该类已被窃听。

对于所有其他错误-通常是由于环境(网络连接丢失)或者滥用(呼叫者传递了一个空值)而引起的-使用硬检查和异常会更好,也更容易理解。如果发生异常,则呼叫者知道很可能是他们的错。如果发生断言,则调用者将知道断言所在的代码中可能存在错误。

关于重复:我同意。我不明白为什么要使用Debug.Assert和异常检查来复制验证。它不仅给代码增加了一些噪音,并使谁在过错的问题上更加混乱,而且是一种重复。