代码之家  ›  专栏  ›  技术社区  ›  Nicholas Mancuso

何时应该使用Debug.Assert()?

  •  210
  • Nicholas Mancuso  · 技术社区  · 17 年前

    我已经成为一名专业软件工程师大约一年了,毕业时获得了CS学位。我在C++和C中已经知道断言一段时间了,但直到最近才知道它们在C.NET和.NET中都存在。

    我们的生产代码不包含任何断言,我的问题是。。。

    Debug.Assert(val != null);
    

    if ( val == null )
        throw new exception();
    
    20 回复  |  直到 17 年前
        1
  •  254
  •   Rory MacLeod    17 年前

    在里面 Debugging Microsoft .NET 2.0 Applications

    1. 慷慨地断言。你不能有太多的断言。
    2. 断言不能替换异常。例外包括代码所需的内容;断言涵盖了它假定的内容。
    3. 异常消息通常是神秘的,要求您在代码中反向工作以重新创建导致错误的上下文。断言可以保留错误发生时程序的状态。
    4. 断言兼作文档,告诉其他开发人员您的代码所依赖的隐含假设。

    PS:如果您喜欢完整的代码,我建议您继续阅读本书。我买它是为了学习如何使用WinDBG和dump文件,但上半部分有很多技巧,可以帮助你从一开始就避免bug。

        2
  •  90
  •   Anatoliy Nikolaev    12 年前

    Debug.Assert() 代码中任何地方都需要进行健全性检查以确保不变量。当您编译发布版本时(即,否 DEBUG 编译器常量),调用 Debug.Assert() 将被删除,因此不会影响性能。

    Debug.Assert() . 断言只是确保在您仍在开发时一切都如预期的那样。

        3
  •  53
  •   Nicholas Piasecki    15 年前

    if () { throw; } 模式以确保正确调用该方法。我的私人方法倾向于使用 Debug.Assert() .

    我的想法是,对于我的私有方法,我是受控制的,所以如果我开始用不正确的参数调用我自己的私有方法,那么我在某个地方打破了我自己的假设——我不应该进入那种状态。在生产中,这些私有资产在理想情况下应该是不必要的工作,因为我应该保持内部状态的有效性和一致性。与公共方法的参数不同,公共方法可以在运行时被任何人调用:我仍然需要通过抛出异常来强制参数约束。

    此外,如果某些东西在运行时不起作用(网络错误、数据访问错误、从第三方服务检索到的坏数据等),我的私有方法仍然可以抛出异常。我的断言只是为了确保我没有破坏自己对对象状态的内部假设。

        4
  •  52
  •   Community Mohan Dere    6 年前

    从…起 Code Complete

    或宏,允许程序在运行时检查自身。当 如果它为false,则表示它在 密码例如,如果系统假定客户信息 包含记录数小于或等于的断言 到五万。只要记录数小于或等于 50000,断言将保持沉默。如果它遇到超过 程序中的错误。

    断言在大型、复杂的程序和应用程序中特别有用 在高可靠性程序中。它们使程序员能够更快地完成任务 清除不匹配的接口假设,当

    描述假定为真的假设,并向 如果不是,则显示。

    通常,您不希望用户在中看到断言消息 生产代码;断言主要用于开发期间 意外情况、传递给例程的错误值等。 在生产过程中,它们是从代码中编译出来的,因此

        5
  •  45
  •   Justin R.    17 年前

    使用资产检查开发人员假设,使用例外检查环境假设。

        6
  •  32
  •   Michael Freidgeim    13 年前

    如果我是你,我会:

    Debug.Assert(val != null);
    if ( val == null )
        throw new exception();
    

    if ( val == null )
    {
        Debug.Assert(false,"breakpoint if val== null");
        throw new exception();
    }
    
        7
  •  25
  •   to StackOverflow    17 年前

    如果您希望在生产代码(即发布版本)中使用断言,可以使用Trace.Assert而不是Debug.Assert。

    此外,如果应用程序在用户界面模式下运行,则默认情况下会显示断言对话框,这可能会让用户感到不安。

    您可以通过删除DefaultTraceListener来覆盖此行为:查看MSDN中Trace.Listeners的文档。

    总之,

    • 自由地使用Debug.Assert来帮助捕获调试版本中的错误。

    • 如果在用户界面模式下使用Trace.Assert,可能需要删除DefaultTraceListener以避免用户不安。

        8
  •  22
  •   user19113    17 年前

    断言用于捕获程序员(您的)错误,而不是用户错误。只有当用户不可能触发断言时,才应该使用它们。例如,如果您正在编写API,则不应使用断言来检查API用户可以调用的任何方法中的参数是否为null。但是,它可以用在一个私有方法中,而这个私有方法不是作为API的一部分公开的,用来断言代码在不应该传递null参数的情况下从不传递null参数。

    当我不确定时,我通常倾向于例外而不是主张。

        9
  •  15
  •   StuartLC    5 年前

    简言之

    Asserts 您的预期设计。

    • 断言 应仅用于调试和非生产版本。在发布版本中,编译器通常会忽略断言。
    • 可以检查系统控制范围内的错误/意外情况
    • 不是对用户输入或业务规则进行一线验证的机制
    • 应该
    • 失败的断言对您的系统来说应该是致命的-也就是说,与异常不同,不要尝试捕获或处理失败的断言 断言 -您的代码正在意外区域中运行。堆栈跟踪和崩溃转储可用于确定出错的原因。

    断言有巨大的好处:

    • 帮助查找丢失的用户输入验证,或更高级别代码中的上游错误。
    • 代码库中的断言将代码中的假设清楚地传达给读者
    • 将在中的运行时检查断言 Debug 建立。
    • 一旦对代码进行了彻底的测试,将代码重新构建为发行版将消除验证假设的性能开销(但好处是,如果需要,以后的调试构建将始终恢复检查)。

    ... 更多细节

    Debug.Assert 表示由程序控制内的代码块的其余部分假定的关于状态的条件。这可以包括所提供参数的状态、类实例成员的状态,或者方法调用的返回在其约定/设计的范围内。

    什么时候使用 Asserts?

    • 常见的 Assert 检查包括无效假设将导致空对象解引用、零除数、数字或日期算术溢出,以及一般带外/非行为设计(例如,如果使用32位int来模拟人的年龄,则谨慎的做法是 明确肯定 年龄实际上在0到125岁左右-值为-100和10^10不是为这个设计的)。


    在.Net堆栈中, Code Contracts 可以使用 in addition to, or as an alternative to 使用 . 代码契约可以进一步形式化状态检查,并有助于在编译时(或在编译后不久,如果在IDE中作为后台检查运行)检测违反假设的情况。

    可用的合同设计(DBC)检查包括:

    • Contract.Requires
    • Contract.Ensures -合同后条件
    • Invariant -表示关于对象在其生命周期内所有点的状态的假设。
    • Contract.Assumes -在调用非契约修饰方法时安抚静态检查器。
        10
  •  10
  •   Chris Ani    17 年前

    在我的书中几乎没有。 在绝大多数情况下,如果你想检查一切是否正常,如果不正常就扔掉。

    我想可能有一些性能关键的场景,您希望优化您的断言,它们在那里很有用,但是我还没有遇到这样的场景。

        11
  •  7
  •   devlord    17 年前

    根据 IDesign Standard

    using System.Diagnostics;
    
    object GetObject()
    {...}
    
    object someObject = GetObject();
    Debug.Assert(someObject != null);
    

    作为免责声明,我应该提到,我认为实施这个IRL并不实际。但这是他们的标准。

        12
  •  6
  •   Derek Park    17 年前

        13
  •  6
  •   Pang Ajmal PraveeN    5 年前

    所有资产应为可优化的代码,以便:

    Debug.Assert(true);
    

    因为它检查的是你已经假设为真的东西。例如。:

    public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
    {
      if(source != null)
        using(var en = source.GetEnumerator())
          RunThroughEnumerator(en);
    }
    public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
    {
      if(source == null)
        throw new ArgumentNullException("source");
      using(var en = source.GetEnumerator())
      {
        if(!en.MoveNext())
          throw new InvalidOperationException("Empty sequence");
        T ret = en.Current;
        RunThroughEnumerator(en);
        return ret;
      }
    }
    private static void RunThroughEnumerator<T>(IEnumerator<T> en)
    {
      Debug.Assert(en != null);
      while(en.MoveNext());
    }
    

    在第一种情况下,没有问题。

    GetFirstAndConsume 使用null,则返回异常。

    在第三种情况下,这段代码有问题,因为应该已经检查过了 en != null 在它被调用之前,所以它不是真的是一个bug。或者换句话说,它应该是理论上可以优化的代码 Debug.Assert(true) ,sicne 嗯!=无效的 true !

        14
  •  4
  •   Noctis    11 年前

    我想我会再添加四个案例,其中Debug.Assert可能是正确的选择。

    1) . 举个简单的例子:

    然而,实际上引入了一个细微的缺陷,但在测试结果中没有检测到。在这种情况下,被调用方变得不确定,并且 发生 提供预期的结果。或者它产生了一个未被注意到的舍入误差。或者导致了一个在其他地方被同等抵消的错误。或不仅授予请求的访问权限,而且授予不应授予的其他特权。等

    此时,被调用方中包含的Debug.Assert()语句与单元测试驱动的新案例(或边缘案例)结合在一起,可以在测试期间提供非常宝贵的通知,告知原始作者的假设已经无效,并且在没有额外审查的情况下不应发布代码。单元测试的断言是完美的合作伙伴。

    2) 另外,, . 例如:

    如果只能从某个安全入口点访问某个对象,是否应该从每个对象方法对网络权限数据库进行额外查询,以确保调用方具有权限?当然不是。也许理想的解决方案包括缓存或其他一些功能扩展,但设计并不需要它。当对象被附加到不安全的入口点时,Debug.Assert()将立即显示。

    接下来,在某些情况下,您的 . 例如:

    假设它是一个嵌入式实时设备。在遇到格式错误的数据包时抛出异常并重新启动会适得其反。相反,设备可能会受益于尽力而为的操作,甚至在其输出中渲染噪声。它也可能没有人机界面、日志记录设备,甚至在以发布模式部署时根本无法由人进行物理访问,并且最好通过评估相同的输出来提供错误意识。在这种情况下,自由的断言和彻底的发布前测试比异常更有价值。

    最后一点 有些测试是不必要的,只是因为被调用方被认为非常可靠

    如果一个核心 String.Find 操作说明它将返回一个 -1 当未找到搜索条件时,您可以安全地执行一个操作,而不是三个操作。然而,如果它真的返回 -2 ,你可能没有合理的行动方针。用一个单独测试一段时间的计算来代替更简单的计算是没有帮助的 在大多数发布环境中,用测试来确保核心库按预期运行是不合理的。在这种情况下,断言是理想的。

        15
  •  4
  •   Teoman shipahi    10 年前

    引自 The Pragmatic Programmer: From Journeyman to Master

    人们对政府公布的断言普遍存在误解 编写编译器和语言环境的人。就这样

    断言会给代码增加一些开销。因为他们检查东西 密码一旦代码经过测试和发布,它们就不再是 断言是一种调试工具。

    这里有两个明显错误的假设。首先,他们假设 不太可能测试哪怕是极小百分比的排列 您的代码将通过测试(请参阅无情测试)。

    危险的世界。在测试过程中,老鼠可能不会啃穿牙齿 通信电缆,玩游戏的人不会耗尽内存 日志文件无法填满硬盘。这些事情可能发生在 您的程序在生产环境中运行。你的第一行 尝试检测您错过的内容的断言。

    在将程序交付到生产环境时关闭断言是非常困难的 就像穿越一条没有网的高压线,因为你曾经成功过 在实践中跨越 . 有戏剧性的价值,但很难获得生命 保险

    .

        16
  •  2
  •   Thomas Danecker    17 年前

    您应该始终使用第二种方法(抛出异常)。

        17
  •  0
  •   orlando calresian    17 年前

        18
  •  0
  •   AlexDev    10 年前

    我已经阅读了这里的答案,我想我应该添加一个重要的区别。使用资产有两种截然不同的方式。一种是作为一种临时的开发人员快捷方式,“这不应该真的发生,所以如果它确实让我知道,那么我可以决定做什么”,有点像一个条件断点,用于程序能够继续的情况。另一种方法是在代码中加入关于有效程序状态的假设。

    Debug.Assert 在开发过程中,如果不再需要,您可以删除它们。如果您想保留它们或者忘记删除它们,没有问题,因为它们不会在发布编译中产生任何后果。

    但在第二种情况下,断言是代码的一部分。他们断言,你的假设是真实的,并记录下来。在这种情况下,您真的希望将它们留在代码中。如果程序处于无效状态,则不允许继续。如果你负担不起性能冲击,你就不会使用C。一方面,如果调试器发生了,能够附加调试器可能会很有用。另一方面,您不希望堆栈跟踪突然出现在您的用户身上,更重要的是,您不希望他们能够忽略它。此外,如果它在服务中,它将始终被忽略。因此,在生产环境中,正确的行为是抛出异常,并使用程序的正常异常处理,这可能会向用户显示一条漂亮的消息并记录详细信息。

    Trace.Assert 有一个完美的方法来实现这一点。它不会在生产中删除,可以使用app.config使用不同的侦听器进行配置。 因此,对于开发来说,默认处理程序很好,对于生产,您可以创建一个简单的TraceListener,如下面所示,它抛出一个异常并在生产配置文件中激活它。

    using System.Diagnostics;
    
    public class ExceptionTraceListener : DefaultTraceListener
    {
        [DebuggerStepThrough]
        public override void Fail(string message, string detailMessage)
        {
            throw new AssertException(message);
        }
    }
    
    public class AssertException : Exception
    {
        public AssertException(string message) : base(message) { }
    }
    

    <system.diagnostics>
      <trace>
        <listeners>
          <remove name="Default"/>
          <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
        </listeners>
      </trace>
     </system.diagnostics>
    
        19
  •  -1
  •   unexist    17 年前

        20
  •  -2
  •   mattlant    17 年前