103

I've read plenty of articles (and a couple of other similar questions that were posted on StackOverflow) about how and when to use assertions, and I understood them well. But still, I don't understand what kind of motivation should drive me to use Debug.Assert instead of throwing a plain exception. What I mean is, in .NET the default response to a failed assertion is to "stop the world" and display a message box to the user. Though this kind of behavior could be modified, I find it highly annoying and redundant to do that, while I could instead, just throw a suitable exception. This way, I could easily write the error to the application's log just before I throw the exception, and plus, my application doesn't necessarily freeze.

So, why should I, if at all, use Debug.Assert instead of a plain exception? Placing an assertion where it shouldn't be could just cause all kinds of "unwanted behavior", so in my point of view, I really don't gain anything by using an assertion instead of throwing an exception. Do you agree with me, or am I missing something here?

Note: I fully understand what the difference is "in theory" (Debug vs Release, usage patterns etc.), but as I see it, I would be better off throwing an exception instead of performing an assert. Since if a bug is discovered on a production release, I still would want the "assertion" to fail (after all, the "overhead" is ridiculously small), so I'll be better off throwing an exception instead.


Edit: The way I see it, if an assert failed, that means that the application entered some kind of corrupted, unexpected state. So why would I want to continue execution? It doesn't matter if the application runs on a debug or release version. The same goes for both

Mustafa Özçetin
  • 1,893
  • 1
  • 14
  • 16
  • 1
    For things that you are saying "if a bug is discovered on a production release, I still would like that the "assertion" would fail" about, exceptions are what you should use – Tom Neyland Sep 23 '09 at 18:01
  • 2
    Performance is the only reason. Null checking everything all the time can decrease speed, though it may be completely unnoticeable. This is mainly for cases that should never happen, eg you know you've null checked it already in a previous function, there is no point wasting cycles checking it again. The debug.assert effectively acts like a last chance unit test to inform you. – rollsch Jan 25 '17 at 05:31

8 Answers8

198

Though I agree that your reasoning is plausible -- that is, if an assertion is violated unexpectedly, it makes sense to halt execution by throwing -- I personally would not use exceptions in the place of assertions. Here's why:

As others have said, assertions should document situations that are impossible, in such a manner that if the allegedly impossible situation comes to pass, the developer is informed. Exceptions, by contrast, provide a control flow mechanism for exceptional, unlikely, or erroneous situations, but not impossible situations. For me, the key difference is this:

  • It should ALWAYS be possible to produce a test case which exercises a given throw statement. If it is not possible to produce such a test case then you have a code path in your program which never executes, and it should be removed as dead code.

  • It should NEVER be possible to produce a test case which causes an assertion to fire. If an assertion fires, either the code is wrong or the assertion is wrong; either way, something needs to change in the code.

That's why I would not replace an assertion with an exception. If the assertion cannot actually fire, then replacing it with an exception means you have an untestable code path in your program. I dislike untestable code paths.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 18
    The problem with assertions is they are not there in the production build. Failing an assumed condition means your program has entered undefined behavior land, in which case a responsible program must halt execution as soon as possible (unwinding the stack is also somewhat dangerous, depending on how rigorous you want to get). Yes, assertions should usually be _impossible_ to fire, but you don't know what's possible when things go out in the wild. What you thought of as _impossible_ might happen in production, and a responsible program should detect violated assumptions and act promptly. – kizzx2 Dec 05 '10 at 13:42
  • 3
    @kizzx2: OK, so how many impossible exceptions per line of production code do you write? – Eric Lippert Dec 05 '10 at 15:58
  • @kizzx2: not sure how it can be done in C# but in other languages you can make an assert throw an exception instead of being removed in your production code (I will not argue whether or not you should do it). – João Portela Jan 12 '11 at 11:46
  • 1
    @Eric: A related question. Do you use assertion (`Debug.Assert`) in your code regularly? – Navaneeth K N Mar 23 '11 at 13:53
  • 5
    @kixxx2: This is C#, so you *can* keep assertions in production code by using Trace.Assert. You can even use the app.config file to re-direct production assertions to a text file rather than being rude to the end-user. – HTTP 410 Nov 23 '11 at 14:55
  • +1 This makes the most sense. So you wouldn't use an Assert for example to test that parameters are not null, you'd still use exceptions for that unless it were supposed to not be possible for them to be null. (unless your IoC container is fulfilling the requests or something). – Jon Upchurch Nov 05 '14 at 19:54
  • 1
    I just had a good real world situation where it would be good to assert, I had a `myTask.ContinueWith(OnError, TaskContinuationOptions.OnlyOnFaulted)` then in `OnError` I had `private void OnError(Task task) { Debug.Assert(task.Execption != null);` because it was impossible for `OnError` to ever have a task passed in that had a null exception I did not need to check and throw, but I still wanted to let intellisense know it does not need to warn me about a possible NullRefrenceException. – Scott Chamberlain Dec 06 '14 at 17:37
  • Why would you use trace.assert in production code? Theres no penalty for throwing an exception instead, an exception that your program could log and handle more gracefully. – rollsch Jan 25 '17 at 05:33
  • 1
    @rolls I would use an Assert *precisely* because exceptions can be caught. If the error is a logical / implementation bug then what happens if that exception accidentally / incorrectly gets swallowed somewhere, perhaps by a caller outside your own code. Asserts can't be swallowed on accident, making them more reliable. Especially if you have a type that can throw during type initialization. That exception will be swallowed and rethrown on later access. What if you never end up accessing it? Sure then you have a poor test, but that is not the point. Why risk never seeing it for no benefit..? – AnorZaken Jun 06 '17 at 18:18
  • 4
    @AnorZaken: Your point illustrates a design flaw in exceptions. As I've noted elsewhere, exceptions are (1) fatal disasters, (2) boneheaded mistakes that should never happen, (3) design failures where an exception is used to signal a non-exceptional condition, or (4) unexpected exogenous conditions. Why are these four completely different things all represented by exceptions? If I had my druthers, boneheaded "null was dereferenced" exceptions would not be catchable *at all*. It's *never* right, and it should *terminate your program before it does more harm*. They should be more like asserts. – Eric Lippert Jun 06 '17 at 19:48
  • what about fixing user input mistakes? for example you expect that input is always less than `128`, but then if input is `200`, should I silently change that to `127` or should I throw exception? or should I write assert statement for it? (assuming in my case changing `200` to `127` will solve problem since its range value.) – M.kazem Akhgary Oct 05 '17 at 17:08
  • @M.kazemAkhgary Nothing of the above. You should inform the user of the wrong value and let him enter a correct one. – Anton Shepelev Jan 27 '18 at 21:52
  • @M.kazemAkhgary it depends if you want robustness, or correctness. You can't have both. – David Klempfner May 01 '18 at 04:59
  • @EricLippert if assertions are for situations which are impossible, then isn't it redundant, because by definition, it will never run? People are saying Debug.Assert is good because it's removed in prod, but if for some reason Assert.Debug(true) happens, wouldn't you want to know about it in prod as well? – David Klempfner May 01 '18 at 05:09
  • 2
    @Backwards_Dave: *false* assertions are bad, not true ones. Assertions enable you to run expensive verification checks that you would not want to run in production. And if an assertion is violated in production, what should the end user do about it? – Eric Lippert May 01 '18 at 14:49
  • @EricLippert Sorry, meant to write Assert.Debug(false). You asked what the end user should do about it. Couldn't the end user do the same thing they'd do if they found an Exception? – David Klempfner May 02 '18 at 04:06
  • 1
    @Backwards_Dave: False assertions cannot be caught, they cannot be handled, by default they create a UI element, and as they indicate a situation that should be *impossible*, the program state now cannot be predicted at all. They're not exceptions, so don't treat them as exceptions. – Eric Lippert May 02 '18 at 04:42
  • 4
    @EricLippert So in a theoretical programming language where every programmer's mistake will result in a compile-time error, there is no need for asserts? Say, in Rust no one needs to assert anything related to memory management because the compiler will ensure certain situations in question are actually impossible? And in C#, no one needs to assert for correct types so long they use polymorphism and generics as intended, hence it's type-safe? So one could say asserts is a tool to guard a programmer against hard to reason situations the programming language in use can't protect them from? – Bruno Zell Mar 03 '20 at 19:46
  • 4
    @BrunoZell: That's a good summary, yes. In C# you might write an assertion that a string variable is not null, but you never write an assertion that a string variable is not, say, a boxed integer; why would you? The program would have either not compiled, or an exception would have been thrown on the assignment attempt. Assertions express invariants that are *not* enforced by the language. – Eric Lippert Mar 03 '20 at 22:25
21

Assertions are used to check the programmer's understanding of the world. An assertion should fail only if the programmer has done something wrong. For example, never use an assertion to check user input.

Asserts test for conditions that "cannot happen". Exceptions are for conditions that "should not happen but do".

Assertions are useful because at build time (or even run time) you can change their behavior. For example, often in release builds, the asserts aren't even checked, because they introduce unneeded overhead. This is also something to be wary of: your tests may not even be executed.

If you use exceptions instead of asserts, you lose some value:

  1. The code is more verbose, since testing and throwing an exception is at least two lines, while an assert is only one.

  2. Your test and throw code will always run, while asserts can be compiled away.

  3. You lose some communication with other developers, because asserts have a different meaning than product code that checks and throws. If you are really testing a programming assertion, use an assert.

More here: http://nedbatchelder.com/text/assert.html

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • 1
    If it "cannot happen", then why write an assertion. Isn't that redundant? If it can actually happen but shouldn't, then isn't this the same as "should not happen but do" which is for exceptions? – David Klempfner May 01 '18 at 05:10
  • 4
    "Cannot happen" is in quotes for a reason: it can only happen if the programmer has done something wrong in another part of the program. The assert is a check against programmer mistakes. – Ned Batchelder May 01 '18 at 09:07
  • 1
    @NedBatchelder The term _programmer_ is a little ambiguous when you develop a library though. Is it right that those "impossible situations" _should_ be impossible by the library user, but could be possible when the library author made a mistake? – Bruno Zell Mar 03 '20 at 19:39
14

EDIT: In response to the edit/note you made in your post: It sounds like using exceptions are the right thing to use over using assertions for the type of things you are trying to accomplish. I think the mental stumbling block you are hitting is that you are considering exceptions and assertions to fulfill the same purpose, and so you are trying to figure out which one would be 'right' to use. While there may be some overlap in how assertions and exceptions can be used, don't confuse that for them being different solutions to the same problem- they aren't. Assertions and Exceptions each have their own purpose, strengths, and weaknesses.

I was going to type up an answer in my own words but this does the concept better justice than I would have:

C# Station: Assertions

The use of assert statements can be an effective way to catch program logic errors at runtime, and yet they are easily filtered out of production code. Once development is complete, the runtime cost of these redundant tests for coding errors can be eliminated simply by defining the preprocessor symbol NDEBUG [which disables all assertions] during compilation. Be sure, however, to remember that code placed in the assert itself will be omitted in the production version.

An assertion is best used to test a condition only when all of the following hold:

* the condition should never be false if the code is correct,
* the condition is not so trivial so as to obviously be always true, and
* the condition is in some sense internal to a body of software.

Assertions should almost never be used to detect situations that arise during software's normal operation. For example, usually assertions should not be used to check for errors in a user's input. It may, however, make sense to use assertions to verify that a caller has already checked a user's input.

Basically, use exceptions for things that need to be caught/dealt with in a production application, use assertions to perform logical checks that will be useful for development but turned off in production.

Tom Neyland
  • 6,860
  • 2
  • 36
  • 52
  • I realize all of that. But the thing is, the same statement you marked as bold goes to exceptions as well. So the way I see it, instead of an assertion, I could just throw an exception (since if the "situation that never should occur" does happen on a deployed version, I would still want to be informed of it [plus, the application may enter a corrupted state, so I an exception is suitable, I may not want to continue the normal execution flow) –  Sep 23 '09 at 18:05
  • 1
    Assertions should be used on invariants; exceptions should be used when, say, something should not be null, but it will be (like a parameter to a method). – Ed S. Sep 23 '09 at 18:07
  • I guess it all comes down to how defensively you want to code. – Ned Batchelder Sep 23 '09 at 18:08
  • I agree, for what it appears that you need, exceptions are the way to go. You said you would like: Failures detected in production, the ability to log information about errors, and execution flow control, etc. Those three things make me think that what you need is to do is throw around some exceptions. – Tom Neyland Sep 23 '09 at 18:18
9

I think a (contrived) practical example may help illuminate the difference:

(adapted from MoreLinq's Batch extension)

// 'public facing' method
public int DoSomething(List<string> stuff, object doohickey, int limit) {

    // validate user input and report problems externally with exceptions

    if(stuff == null) throw new ArgumentNullException("stuff");
    if(doohickey == null) throw new ArgumentNullException("doohickey");
    if(limit <= 0) throw new ArgumentOutOfRangeException("limit", limit, "Should be > 0");

    return DoSomethingImpl(stuff, doohickey, limit);
}

// 'developer only' method
private static int DoSomethingImpl(List<string> stuff, object doohickey, int limit) {

    // validate input that should only come from other programming methods
    // which we have control over (e.g. we already validated user input in
    // the calling method above), so anything using this method shouldn't
    // need to report problems externally, and compilation mode can remove
    // this "unnecessary" check from production

    Debug.Assert(stuff != null);
    Debug.Assert(doohickey != null);
    Debug.Assert(limit > 0);

    /* now do the actual work... */
}

So as Eric Lippert et al have said, you only assert stuff that you expect to be correct, just in case you (the developer) accidentally used it wrong somewhere else, so you can fix your code. You basically throw exceptions when you have no control over or cannot anticipate what comes in, e.g. for user input, so that whatever gave it bad data can respond appropriately (e.g. the user).

Community
  • 1
  • 1
drzaus
  • 24,171
  • 16
  • 142
  • 201
  • Aren't your 3 Asserts completely redundant? It's impossible for their parameters to evaluate to false. – David Klempfner May 01 '18 at 05:22
  • 5
    That is the point - the assertions are there to document stuff that's impossible. Why would you do that? Because you might have something like ReSharper that warns you inside the DoSomethingImpl method that "you might be dereferencing null here" and you want to tell it "I know what I'm doing, this can never be null". It's also an indication for some later programmer, who might not immediately realize the connection between DoSomething and DoSomethingImpl, especially if they're hundred of lines apart. – Marcel Popescu May 02 '18 at 16:24
4

Another nugget from Code Complete:

"An assertion is a function or macro that complains loudly if an assumption isn't true. Use assertions to document assumptions made in code and to flush out unexpected conditions. ...

"During development, assertions flush out contradictory assumptions, unexpected conditions, bad values passed to routines, and so on."

He goes on to add some guidelines on what should and should not be asserted.

On the other hand, exceptions:

"Use exception handling to draw attention to unexpected cases. Exceptional cases should be handled in a way that makes them obvious during development and recoverable when production code is running."

If you don't have this book you should buy it.

Andrew Cowenhoven
  • 2,778
  • 22
  • 27
  • 3
    I've read the book, it's excellent. However.. you didn't answer my question :) –  Sep 23 '09 at 18:02
  • You are right I didn't answer it. My answers is no I don't agree with you. Assertions and Exceptions are different animals as covered above and some of the other posted answers here. – Andrew Cowenhoven Sep 23 '09 at 18:14
1

Debug.Assert by default will only work in debug builds, so if you want to catch any sort of bad unexpected behavior in your release builds you'll need to use exceptions or turn the debug constant on in your project properties (which is considered in general not to be a good idea).

Mez
  • 2,817
  • 4
  • 27
  • 29
  • first partial sentence is true, the rest is in general a bad idea: assertions are assumptions and no validation (as stated above), enabling debug in release is really no option. – Marc Wittke Sep 23 '09 at 19:24
0

Use assertions for things which ARE possible but should not happen (if it were impossible, why would you put an assertion?).

Doesn't that sound like a case to use an Exception? Why would you use an assertion instead of an Exception?

Because there should be code that gets called before your assertion that would stop the assertion's parameter being false.

Usually there is no code before your Exception that guarantees that it won't be thrown.

Why is it good that Debug.Assert() is compiled away in prod? If you want to know about it in debug, wouldn't you want to know about it in prod?

You want it only during development, because once you find Debug.Assert(false) situations, you then write code to guarantee that Debug.Assert(false) doesn't happen again. Once development is done, assuming you've found the Debug.Assert(false) situations and fixed them, the Debug.Assert() can be safely compiled away as they are now redundant.

David Klempfner
  • 8,700
  • 20
  • 73
  • 153
0

Suppose you are a member of a fairly large team and there are several people all working on the same general code base, including overlapping on classes. You may create a method that is called by several other methods, and to avoid lock contention you do not add a separate lock to it, but rather "assume" it was previously locked by the calling method with a specific lock. Such as, Debug.Assert(RepositoryLock.IsReadLockHeld || RepositoryLock.IsWriteLockHeld); The other developers might overlook a comment that says the calling method must use the lock, but they cannot ignore this.

daniel
  • 1
  • 1