我什么时候应该使用 Debug.Assert()?

发布于 2024-07-07 04:34:01 字数 388 浏览 12 评论 0 原文

我已经成为一名专业软件工程师大约一年了,并获得了计算机科学学位。 我在 C++ 和 C 中了解断言已经有一段时间了,但直到最近才知道它们存在于 C# 和 .NET 中。

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

我应该开始在我们的生产代码中使用断言吗? 如果是这样,什么时候使用最合适? 这样做更有意义

    Debug.Assert(val != null, "message");

吗?

    if ( val == null )
        throw new exception("message");

I've been a professional software engineer for about a year now, having graduated with a CS degree. I've known about assertions for a while in C++ and C, but had no idea they existed in C# and .NET at all until recently.

Our production code contains no asserts whatsoever and my question is this...

Should I begin using Asserts in our production code? And if so, When is its use most appropriate? Would it make more sense to do

    Debug.Assert(val != null, "message");

or

    if ( val == null )
        throw new exception("message");

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(20

世界和平 2024-07-14 04:34:01

调试 Microsoft .NET 2.0 应用程序中,John Robbins 有很大一部分内容是关于断言的。 他的主要观点是:

  1. 自由主张。 你永远不能有太多的断言。
  2. 断言不能取代异常。 异常涵盖了您的代码所要求的内容; 断言涵盖了它所假设的事物。
  3. 一个写得好的断言不仅可以告诉您发生了什么以及在哪里发生(例如异常),还可以告诉您原因。
  4. 异常消息通常可能很神秘,需要您逆向执行代码以重新创建导致错误的上下文。 断言可以保留错误发生时程序的状态。
  5. 断言兼作文档,告诉其他开发人员您的代码所依赖的隐含假设。
  6. 断言失败时出现的对话框允许您将调试器附加到进程,这样您就可以浏览堆栈,就像在那里放置了断点一样。

PS:如果您喜欢《Code Complete》,我建议您继续阅读这本书。 我购买它是为了了解如何使用 WinDBG 和转储文件,但前半部分包含了一些技巧,可以帮助您从一开始就避免错误。

In Debugging Microsoft .NET 2.0 Applications John Robbins has a big section on assertions. His main points are:

  1. Assert liberally. You can never have too many assertions.
  2. Assertions don't replace exceptions. Exceptions cover the things your code demands; assertions cover the things it assumes.
  3. A well-written assertion can tell you not just what happened and where (like an exception), but why.
  4. An exception message can often be cryptic, requiring you to work backwards through the code to recreate the context that caused the error. An assertion can preserve the program's state at the time the error occurred.
  5. Assertions double as documentation, telling other developers what implied assumptions your code depends on.
  6. The dialog that appears when an assertion fails lets you attach a debugger to the process, so you can poke around the stack as if you had put a breakpoint there.

PS: If you liked Code Complete, I recommend following it up with this book. I bought it to learn about using WinDBG and dump files, but the first half is packed with tips to help avoid bugs in the first place.

千紇 2024-07-14 04:34:01

将 Debug.Assert() 放置在代码中您想要进行健全性检查以确保不变量的任何位置。 当您编译 Release 版本时(即没有 DEBUG 编译器常量),对 Debug.Assert() 的调用将被删除,以便它们获胜不影响性能。

您仍然应该在调用 Debug.Assert() 之前引发异常。 断言只是确保在您仍在开发时一切都按预期进行。

Put Debug.Assert() everywhere in the code where you want have sanity checks to ensure invariants. When you compile a Release build (i.e., no DEBUG compiler constant), the calls to Debug.Assert() will be removed so they won't affect performance.

You should still throw exceptions before calling Debug.Assert(). The assert just makes sure that everything is as expected while you're still developing.

甜尕妞 2024-07-14 04:34:01

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

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

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

FWIW ... I find that my public methods tend to use the if () { throw; } pattern to ensure that the method is being called correctly. My private methods tend to use Debug.Assert().

The idea is that with my private methods, I'm the one under control, so if I start calling one of my own private methods with parameters that are incorrect, then I've broken my own assumption somewhere--I should have never gotten into that state. In production, these private asserts should ideally be unnecessary work since I am supposed to be keeping my internal state valid and consistent. Contrast with parameters given to public methods, which could be called by anyone at runtime: I still need to enforce parameter constraints there by throwing exceptions.

Additionally, my private methods can still throw exceptions if something doesn't work at runtime (network error, data access error, bad data retrieved from a third party service, etc.). My asserts are just there to make sure that I haven't broken my own internal assumptions about the state of the object.

謸气贵蔟 2024-07-14 04:34:01

来自代码完成 (维基百科):

8 防御性编程

8.2 断言

断言是开发过程中使用的代码——通常是例程
或宏——允许程序在运行时检查自身。 当
断言为真,这意味着一切都按预期运行。
当它为 false 时,意味着它检测到了意外错误
代码。 例如,如果系统假设客户信息
文件永远不会超过 50,000 条记录,程序可能
包含记录数小于或等于的断言
至 50,000。 只要记录条数小于或等于
5万,断言就会无声无息。 如果遇到超过
50,000条记录,然而,它会大声“断言”有一个
程序错误。

断言在大型、复杂的程序和
在高可靠性程序中。 它们使程序员能够更快地
清除不匹配的接口假设和错误
代码修改等等。

断言通常需要两个参数:一个布尔表达式,
描述了应该为真的假设以及一条消息
如果不是则显示。

(…)

通常,您不希望用户看到断言消息
生产代码; 断言主要用于开发期间使用
和维护。 断言通常编译到代码中
开发时间并编译出用于生产的代码。 期间
发展,断言冲走矛盾的假设,
意外情况、传递给例程的错误值等等。
在生产过程中,它们是从代码中编译出来的,以便
断言不会降低系统性能。

From Code Complete (Wikipedia):

8 Defensive Programming

8.2 Assertions

An assertion is code that’s used during development—usually a routine
or macro—that allows a program to check itself as it runs. When an
assertion is true, that means everything is operating as expected.
When it’s false, that means it has detected an unexpected error in the
code. For example, if the system assumes that a customer-information
file will never have more than 50,000 records, the program might
contain an assertion that the number of records is lessthan or equal
to 50,000. As long as the number of records is less than or equal to
50,000, the assertion will be silent. If it encounters more than
50,000 records, however, it will loudly “assert” that there is an
error in the program.

Assertions are especially useful in large, complicated programs and
in high reliability programs. They enable programmers to more quickly
flush out mismatched interface assumptions, errors that creep in when
code is modified, and so on.

An assertion usually takes two arguments: a boolean expression that
describes the assumption that’s supposed to be true and a message to
display if it isn’t.

(…)

Normally, you don’t want users to see assertion messages in
production code; assertions are primarily for use during development
and maintenance. Assertions are normally compiled into the code at
development time and compiled out of the code for production. During
development, assertions flush out contradictory assumptions,
unexpected conditions, bad values passed to routines, and so on.
During production, they are compiled out of the code so that the
assertions don’t degrade system performance.

苦笑流年记忆 2024-07-14 04:34:01

使用断言来检查开发人员的假设,并使用异常来检查环境假设。

Use asserts to check developer assumptions and exceptions to check environmental assumptions.

天邊彩虹 2024-07-14 04:34:01

如果我是你我会这样做:

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

或者避免重复的状况检查

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}

If I were you I would do:

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

Or to avoid repeated condition check

if ( val == null )
{
    Debug.Assert(false,"breakpoint if val== null");
    throw new exception();
}
七七 2024-07-14 04:34:01

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

这当然会增加您的生产可执行文件的开销。

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

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

总之,

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

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

  • 如果您正在测试的条件是您的应用无法处理的,那么您最好抛出异常,以确保执行不会继续。 请注意,用户可以选择忽略断言。

If you want Asserts in your production code (i.e. Release builds), you can use Trace.Assert instead of Debug.Assert.

This of course adds overhead to your production executable.

Also if your application is running in user-interface mode, the Assertion dialog will be displayed by default, which may be a bit disconcerting for your users.

You can override this behaviour by removing the DefaultTraceListener: look at the documentation for Trace.Listeners in MSDN.

In summary,

  • Use Debug.Assert liberally to help catch bugs in Debug builds.

  • If you use Trace.Assert in user-interface mode, you probably want to remove the DefaultTraceListener to avoid disconcerting users.

  • If the condition you're testing is something your app can't handle, you're probably better off throwing an exception, to ensure execution doesn't continue. Be aware that a user can choose to ignore an assertion.

如日中天 2024-07-14 04:34:01

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

当我不确定时,我通常更喜欢例外而不是断言。

Asserts are used to catch programmer (your) error, not user error. They should be used only when there is no chance a user could cause the assert to fire. If you're writing an API, for example, asserts should not be used to check that an argument is not null in any method an API user could call. But it could be used in a private method not exposed as part of your API to assert that YOUR code never passes a null argument when it isn't supposed to.

I usually favour exceptions over asserts when I'm not sure.

最好是你 2024-07-14 04:34:01

简而言之

断言用于保护和检查契约设计约束,即确保代码、对象、变量和参数的状态正在运行< strong>在您预期设计的边界和限制内

  • Asserts 应该仅用于调试和非生产版本。 在发布版本中,编译器通常会忽略断言。
  • 断言可以检查系统控制范围内的错误/意外情况
  • 断言不是用于一线验证用户输入或业务规则的机制
  • 断言 不应该用于检测意外的环境条件(超出代码的控制范围),例如内存不足、网络故障、数据库故障等。虽然很少见,但这些情况是这是预期的(并且您的应用程序代码无法修复硬件故障或资源耗尽等问题)。 通常,会抛出异常 - 然后您的应用程序可以采取纠正措施(例如重试数据库或网络操作,尝试释放缓存内存),或者如果无法处理异常则正常中止。
  • 失败的断言对您的系统来说应该是致命的 - 即与异常不同,不要尝试捕获或处理失败的断言 - 您的代码正在意外的区域运行。 堆栈跟踪和故障转储可用于确定出了什么问题。

断言有巨大的好处:

  • 帮助查找缺少的用户输入验证或更高级别代码中的上游错误。
  • 代码库中的断言清楚地向读者传达了代码中所做的假设。
  • 断言将在运行时在 Debug 版本中进行检查。
  • 一旦代码经过详尽的测试,将代码重建为发布版将消除验证假设的性能开销(但好处是,如果需要,稍后的调试版本将始终恢复检查)。

...更多详细信息

Debug.Assert 表示由程序控制内的代码块的其余部分假定的有关状态的条件。 这可以包括所提供的参数的状态、类实例的成员的状态,或者方法调用的返回是否在其约定/设计的范围内。
通常,断言应该使用所有必要的信息(堆栈跟踪、故障转储等)使线程/进程/程序崩溃,因为它们表明存在未设计的错误或未考虑的条件(即不要尝试捕获或处理断言失败),但有一个可能的例外,即断言本身可能造成比 bug 更大的损害(例如,当飞机潜入潜艇时,空中交通管制员不会想要 YSOD,尽管是否应该部署调试版本还没有实际意义)生产 ...)

什么时候应该使用断言?

  • 在系统、库 API 或服务中的任何点,假设函数的输入或类的状态有效(例如,当验证已已经在系统的表示层中对用户输入进行了处理,业务层和数据层类通常假设已经对输入进行了空检查、范围检查、字符串长度检查等)。
  • 常见的断言检查包括无效假设将导致空对象取消引用、零除数、数字或日期算术溢出以及一般带外/未设计的行为(例如,如果 32 位 int用于对人类年龄进行建模时,明智的做法是断言年龄实际上在 0 到 125 之间左右 --100 和 10^10 的值不是为之设计的)。

.Net 代码契约
在 .Net Stack 中,可以使用代码契约除了使用 Debug.Assert 之外,或者作为其替代方案。 代码契约可以进一步形式化状态检查,并且可以帮助在编译时(或编译后不久,如果在 IDE 中作为后台检查运行)检测违反假设的情况。

可用的契约设计 (DBC) 检查包括:

  • Contract.Requires - 约定的先决条件
  • Contract.Ensures - 约定的后置条件
  • Invariant - 表达关于对象在其生命周期中所有时间点的状态。
  • Contract.Assumes - 在调用非Contract修饰方法时安抚静态检查器。

In Short

Asserts are used for guards and for checking Design by Contract constraints, i.e. to ensure that the state of your code, objects, variables and parameters is operating within the boundaries and limits of your intended design.

  • Asserts should be for Debug and non-Production builds only. Asserts are typically ignored by the compiler in Release builds.
  • Asserts can check for bugs / unexpected conditions which ARE in the control of your system
  • Asserts are NOT a mechanism for first-line validation of user input or business rules
  • Asserts should not be used to detect unexpected environmental conditions (which are outside the control of the code) e.g. out of memory, network failure, database failure, etc. Although rare, these conditions are to be expected (and your app code cannot fix issues like hardware failure or resource exhaustion). Typically, exceptions will be thrown - your application can then either take corrective action (e.g. retry a database or network operation, attempt to free up cached memory), or abort gracefully if the exception cannot be handled.
  • A failed Assertion should be fatal to your system - i.e. unlike an exception, do not try and catch or handle failed Asserts - your code is operating in unexpected territory. Stack Traces and crash dumps can be used to determine what went wrong.

Assertions have enormous benefit:

  • To assist in finding missing validation of user inputs, or upstream bugs in higher level code.
  • Asserts in the code base clearly convey the assumptions made in the code to the reader
  • Assert will be checked at runtime in Debug builds.
  • Once code has been exhaustively tested, rebuilding the code as Release will remove the performance overhead of verifying the assumption (but with the benefit that a later Debug build will always revert the checks, if needed).

... More Detail

Debug.Assert expresses a condition which has been assumed about state by the remainder of the code block within the control of the program. This can include the state of the provided parameters, state of members of a class instance, or that the return from a method call is in its contracted / designed range.
Typically, asserts should crash the thread / process / program with all necessary info (Stack Trace, Crash Dump, etc), as they indicate the presence of a bug or unconsidered condition which has not been designed for (i.e. do not try and catch or handle assertion failures), with one possible exception of when an assertion itself could cause more damage than the bug (e.g. Air Traffic Controllers wouldn't want a YSOD when an aircraft goes submarine, although it is moot whether a debug build should be deployed to production ...)

When should you use Asserts?

  • At any point in a system, or library API, or service where the inputs to a function or state of a class are assumed valid (e.g. when validation has already been done on user input in the presentation tier of a system, the business and data tier classes typically assume that null checks, range checks, string length checks etc on input have been already done).
  • Common Assert checks include where an invalid assumption would result in a null object dereference, a zero divisor, numerical or date arithmetic overflow, and general out of band / not designed for behaviour (e.g. if a 32 bit int was used to model a human's age, it would be prudent to Assert that the age is actually between 0 and 125 or so - values of -100 and 10^10 were not designed for).

.Net Code Contracts
In the .Net Stack, Code Contracts can be used in addition to, or as an alternative to using Debug.Assert. Code Contracts can further formalize state checking, and can assist in detecting violations of assumptions at ~compile time (or shortly thereafter, if run as a background check in an IDE).

Design by Contract (DBC) checks available include:

  • Contract.Requires - Contracted Preconditions
  • Contract.Ensures - Contracted PostConditions
  • Invariant - Expresses an assumption about the state of an object at all points in its lifespan.
  • Contract.Assumes - pacifies the static checker when a call to non-Contract decorated methods is made.
执妄 2024-07-14 04:34:01

我的书中几乎从来没有出现过。
在绝大多数情况下,如果您想检查一切是否正常,则如果不正常则抛出。

我不喜欢的是它使调试构建在功能上与发布构建不同。 如果调试断言失败但功能在发布中有效,那么这有什么意义呢? 当断言者早已离开公司并且没有人知道那部分代码时,情况会更好。 然后你必须花一些时间探索这个问题,看看它是否真的是一个问题。 如果这是一个问题,那么为什么这个人不首先扔呢?

对我来说,这表明通过使用 Debug.Asserts 您将问题推迟给其他人,自己处理问题。 如果事情应该是这样,但事实并非如此,那就抛出。

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

Mostly never in my book.
In the vast majority of occasions if you want to check if everything is sane then throw if it isn't.

What I dislike is the fact that it makes a debug build functionally different to a release build. If a debug assert fails but the functionality works in release then how does that make any sense? It's even better when the asserter has long left the company and no-one knows that part of the code. Then you have to kill some of your time exploring the issue to see if it is really a problem or not. If it is a problem then why isn't the person throwing in the first place?

To me this suggests by using Debug.Asserts you're deferring the problem to someone else, deal with the problem yourself. If something is supposed to be the case and it isn't then throw.

I guess there are possibly performance critical scenarios where you want to optimise away your asserts and they're useful there, however I am yet to encounter such a scenario.

谜兔 2024-07-14 04:34:01

根据 IDesign 标准,您应该

断言每一个假设。 平均而言,每五行就有一个断言。

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

作为免责声明,我应该提到的是,我还没有发现实施这个 IRL 是可行的。 但这是他们的标准。

According to the IDesign Standard, you should

Assert every assumption. On average, every fifth line is an assertion.

using System.Diagnostics;

object GetObject()
{...}

object someObject = GetObject();
Debug.Assert(someObject != null);

As a disclaimer I should mention I have not found it practical to implement this IRL. But this is their standard.

岁月静好 2024-07-14 04:34:01

所有断言都应该是可以优化为的代码:

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());
}

在上面,对于空参数存在三种不同的方法。 第一个接受它是允许的(它只是什么也不做)。 第二个抛出异常供调用代码处理(或不处理,导致错误消息)。 第三种假设它不可能发生,并断言它确实如此。

第一种情况是没有问题的。

在第二种情况下,调用代码存在问题 - 它不应该使用 null 调用 GetFirstAndConsume,因此会返回异常。

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

All asserts should be code that could be optimised to:

Debug.Assert(true);

Because it's checking something that you have already assumed is true. E.g.:

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());
}

In the above, there are three different approaches to null parameters. The first accepts it as allowable (it just does nothing). The second throws an exception for the calling code to handle (or not, resulting in an error message). The third assumes it can't possibly happen, and asserts that it is so.

In the first case, there's no problem.

In the second case, there's a problem with the calling code - it shouldn't have called GetFirstAndConsume with null, so it gets an exception back.

In the third case, there's a problem with this code, because it should already have been checked that en != null before it was ever called, so that it isn't true is a bug. Or in other words, it should be code that could theoretically be optimised to Debug.Assert(true), sicne en != null should always be true!

相对绾红妆 2024-07-14 04:34:01

仅在您希望删除发布版本的检查的情况下才使用断言。 请记住,如果您不在调试模式下编译,您的断言将不会触发。

鉴于您的 check-for-null 示例,如果这是在仅限内部的 API 中,我可能会使用断言。 如果是在公共 API 中,我肯定会使用显式检查和抛出。

Use assertions only in cases where you want the check removed for release builds. Remember, your assertions will not fire if you don't compile in debug mode.

Given your check-for-null example, if this is in an internal-only API, I might use an assertion. If it's in a public API, I would definitely use the explicit check and throw.

狼性发作 2024-07-14 04:34:01

引用自务实的程序员:从熟练工到大师

启用断言

对于断言有一个常见的误解,由
编写编译器和语言环境的人。 它去
像这样的东西:

断言会给代码增加一些开销。 因为他们会检查东西
这永远不会发生,它们只会被错误触发
代码。 一旦代码经过测试并交付,它们就不再是
需要,并且应该关闭以使代码运行得更快。
断言是一种调试工具。

这里有两个明显错误的假设。 首先,他们假设
测试发现所有错误。 事实上,对于任何复杂的程序,你
不太可能测试哪怕一小部分的排列
您的代码将通过(请参阅无情测试)。

其次,乐观主义者忘记了你的程序运行在一个
危险的世界。 在测试过程中,老鼠可能不会咬断
通信电缆,玩游戏的人不会耗尽内存,并且
日志文件不会填满硬盘。 这些事情可能会发生在
您的程序在生产环境中运行。 你的第一行
防御是检查任何可能的错误,第二个是使用
尝试检测您错过的断言。

将程序交付生产时关闭断言是
就像跨越没有网的高空钢丝,因为你曾经做到过
在实践中跨越
。 有戏剧性的价值,但获得生命却很难
保险。

即使确实存在性能问题,也仅关闭那些
真正打动你的断言

Quote Taken from The Pragmatic Programmer: From Journeyman to Master

Leave Assertions Turned On

There is a common misunderstanding about assertions, promulgated by
the people who write compilers and language environments. It goes
something like this:

Assertions add some overhead to code. Because they check for things
that should never happen, they'll get triggered only by a bug in the
code. Once the code has been tested and shipped, they are no longer
needed, and should be turned off to make the code run faster.
Assertions are a debugging facility.

There are two patently wrong assumptions here. First, they assume that
testing finds all the bugs. In reality, for any complex program you
are unlikely to test even a miniscule percentage of the permutations
your code will be put through (see Ruthless Testing).

Second, the optimists are forgetting that your program runs in a
dangerous world. During testing, rats probably won't gnaw through a
communications cable, someone playing a game won't exhaust memory, and
log files won't fill the hard drive. These things might happen when
your program runs in a production environment. Your first line of
defense is checking for any possible error, and your second is using
assertions to try to detect those you've missed.

Turning off assertions when you deliver a program to production is
like crossing a high wire without a net because you once made it
across in practice
. There's dramatic value, but it's hard to get life
insurance.

Even if you do have performance issues, turn off only those
assertions that really hit you
.

这个俗人 2024-07-14 04:34:01

我想我应该再添加四种情况,其中 Debug.Assert 可能是正确的选择。

1) 我在这里没有看到提到的是断言在自动化测试期间可以提供的额外概念覆盖。 举一个简单的例子:

当某个更高级别的调用者被作者修改时,他认为他们已经扩展了代码的范围以处理其他场景,理想情况下(!)他们将编写单元测试来涵盖这种新情况。 那么完全集成的代码可能看起来工作正常。

然而,实际上已经引入了一个微妙的缺陷,但在测试结果中并未检测到。 在这种情况下,被调用者变得不确定,并且只会碰巧提供预期的结果。 或者它可能产生了未被注意到的舍入误差。 或者导致错误在其他地方同样被抵消。 或者不仅授予所请求的访问权限,还授予不应授予的其他权限。 代码

此时,被调用方中包含的 Debug.Assert() 语句与单元测试驱动的新案例(或边缘案例)相结合,可以在测试期间提供宝贵的通知,表明原作者的假设已无效,并且 未经额外审查,不应发布。 断言与单元测试是完美的搭档。

2) 此外,一些测试编写起来很简单,但考虑到最初的假设,成本很高且不必要。 例如:

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

3) 接下来,在某些情况下,您的产品在发布模式下部署时可能无法对其全部或部分操作提供有用的诊断交互。 例如:

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

4) 最后,一些测试是不必要的,只是因为被调用者被认为非常可靠。 在大多数情况下,代码的可重用性越高,就越需要付出更多的努力来确保其可靠性。 因此,对于来自调用者的意外参数,通常使用 Exception,而对于来自被调用者的意外结果,则使用 Assert。 例如:

如果核心 String.Find 操作声明在未找到搜索条件时将返回 -1,则您可以安全地执行一项操作,而不是三。 但是,如果它实际上返回 -2,您可能没有合理的行动方案。 用单独测试 -1 值的计算来替换更简单的计算是没有帮助的,并且在大多数发布环境中用测试来确保核心库按预期运行是不合理的。 在这种情况下断言是理想的。

I thought I would add four more cases, where Debug.Assert can be the right choice.

1) Something I have not seen mentioned here is the additional conceptual coverage Asserts can provide during automated testing. As a simple example:

When some higher-level caller is modified by an author who believes they have expanded the scope of the code to handle additional scenarios, ideally (!) they will write unit tests to cover this new condition. It may then be that the fully integrated code appears to work fine.

However, actually a subtle flaw has been introduced, but not detected in test results. The callee has become non-deterministic in this case, and only happens to provide the expected result. Or perhaps it has yielded a rounding error that was unnoticed. Or caused an error that was offset equally elsewhere. Or granted not only the access requested but additional privileges that should not be granted. Etc.

At this point, the Debug.Assert() statements contained in the callee coupled with the new case (or edge case) driven in by unit tests can provide invaluable notification during test that the original author's assumptions have been invalidated, and the code should not be released without additional review. Asserts with unit tests are the perfect partners.

2) Additionally, some tests are simple to write, but high-cost and unnecessary given the initial assumptions. For example:

If an object can only be accessed from a certain secured entry point, should an additional query be made to a network rights database from every object method to ensure the caller has permissions? Surely not. Perhaps the ideal solution includes caching or some other expansion of features, but the design does not require it. A Debug.Assert() will immediately show when the object has been attached to an insecure entry point.

3) Next, in some cases your product may have no helpful diagnostic interaction for all or part of its operations when deployed in release mode. For example:

Suppose it is an embedded real-time device. Throwing exceptions and restarting when it encounters a malformed packet is counter-productive. Instead the device may benefit from best-effort operation, even to the point of rendering noise in its output. It also may not have a human interface, logging device, or even be physically accessible by human at all when deployed in release mode, and awareness of errors is best provided by assessing the same output. In this case, liberal Assertions and thorough pre-release testing are more valuable than exceptions.

4) Lastly, some tests are unneccessary only because the callee is perceived as extremely reliable. In most cases, the more reusable code is, the more effort has been put into making it reliable. Therefore it is common to Exception for unexpected parameters from callers, but Assert for unexpected results from callees. For example:

If a core String.Find operation states it will return a -1 when the search criteria is not found, you may be able to safely perform one operation rather than three. However, if it actually returned -2, you may have no reasonable course of action. It would be unhelpful to replace the simpler calculation with one that tests separately for a -1 value, and unreasonable in most release environments to litter your code with tests ensuring core libraries are operating as expected. In this case Asserts are ideal.

夜雨飘雪 2024-07-14 04:34:01

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

此外,如果您处于生产状态(并且有发布版本),最好抛出异常(并让应用程序在最坏的情况下崩溃),而不是使用无效值并可能破坏客户的数据(这可能会花费数千美元)美元)。

You should always use the second approach (throwing exceptions).

Also if you're in production (and have a release-build), it's better to throw an exception (and let the app crash in the worst-case) than working with invalid values and maybe destroy your customer's data (which may cost thousand of dollars).

辞别 2024-07-14 04:34:01

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

在第一种情况下,断言甚至不需要出现在最终代码中。 您应该在开发期间使用 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>

I've read the answers here and I thought I should add an important distinction. There are two very different ways in which asserts are used. One is as a temporary developer shortcut for "This shouldn't really happen so if it does let me know so I can decide what to do", sort of like a conditional breakpoint, for cases in which your program is able to continue. The other, is a as a way to put assumptions about valid program states in your code.

In the first case, the assertions don't even need to be in the final code. You should use Debug.Assert during development and you can remove them if/when no longer needed. If you want to leave them or if you forget to remove them no problem, since they won't have any consequence in Release compilations.

But in the second case, the assertions are part of the code. They, well, assert, that your assumptions are true, and also document them. In that case, you really want to leave them in the code. If the program is in an invalid state it should not be allowed to continue. If you couldn't afford the performance hit you wouldn't be using C#. On one hand it might be useful to be able to attach a debugger if it happens. On the other, you don't want the stack trace popping up on your users and perhaps more important you don't want them to be able to ignore it. Besides, if it's in a service it will always be ignored. Therefore in production the correct behavior would be to throw an Exception, and use the normal exception handling of your program, which might show the user a nice message and log the details.

Trace.Assert has the perfect way to achieve this. It won't be removed in production, and can be configured with different listeners using app.config.
So for development the default handler is fine, and for production you can create a simple TraceListener like below which throws an exception and activate it in the production config file.

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) { }
}

And in the production config file:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>
○愚か者の日 2024-07-14 04:34:01

您应该使用 Debug.Assert 来测试程序中的逻辑错误。 编译器只能通知您语法错误。 因此,您应该明确使用 Assert 语句来测试逻辑错误。 比如测试一个销售汽车的程序,只有蓝色的宝马才能享受 15% 的折扣。 编译器无法告诉您程序在执行此操作时逻辑上是否正确,但断言语句可以。

You should use Debug.Assert to test for logical errors in your programs. The complier can only inform you of syntax errors. So you should definetely use Assert statements to test for logical errors. Like say testing a program that sells cars that only BMWs that are blue should get a 15% discount. The complier could tell you nothing about if your program is logically correct in performing this but an assert statement could.

七色彩虹 2024-07-14 04:34:01

我不知道 C# 和 .NET 中的情况如何,但在 C 中,assert() 仅在使用 -DDEBUG 编译时才起作用 - 如果不使用 -DDEBUG 编译,最终用户将永远不会看到assert()。 它仅供开发人员使用。 我经常使用它,有时更容易跟踪错误。

I don't know how it is in C# and .NET, but in C will assert() only work if compiled with -DDEBUG - the enduser will never see an assert() if it's compiled without. It's for developer only. I use it really often, it's sometimes easier to track bugs.

鹤仙姿 2024-07-14 04:34:01

我不会在生产代码中使用它们。 抛出异常,捕获并记录。

在 asp.net 中还需要小心,因为断言可能会显示在控制台上并冻结请求。

I would not use them in production code. Throw exceptions, catch and log.

Also need to be careful in asp.net, as an assert can show up on the console and freeze the request(s).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文