Summary
Motley: Too many assertions make debugging very annoying!
Maven: Lots of assertions are great, as long as they validate the right assumptions. If an assertion fires it is likely manifested by a bug, so get to the root cause and fix it. Best practices for assertions include using them early and often, avoiding executable code within an assertion, and documenting the reason for an assertion.
______________________________
[Context: Motley is having some frustrations while debugging, particularly with components developed by other teams within the company]
Motley: I need to have a talk with the team. Our code has too many debug assertions. They are always firing and it is making debugging very annoying. I constantly have to dismiss the assertion dialog, and when I am executing the same path over and over to debug, it becomes really, really annoying.
Maven: Ah, assertions - one of my favorite topics. I am a huge advocate of defensive programming, and assertions are a great tool. As we have talked about in the past, I like to develop as if the debugger isn't even there. Assertions are mandatory to uphold that way of thinking.
Motley: I still think you're nuts about developing without a debugger. Anyway, assertions have their use, but you can have too many. We do. In this case the large number of assertions is preventing me from getting my work done.
Maven: I am not buying what you are selling. The number of assertions is not the problem. The fact that the assertions are firing on a regular basis IS the problem. In previous teams I have been on, an assertion that is firing that should NOT be firing is considered a priority 1 bug. Think of an assertion as an exception. It is considered a blocking bug from the debugging perspective as you are forcing other developers to continue execution every time they hit it. In addition, assertions validate assumptions, so obviously some assumption is off, which may affect a retail build as well.
Motley: That's true, I guess. I still think we have too many. We probably have one in every 20 lines of source code. And if someone is going to file a blocking bug on me every time they hit my assertion, then I am not sure I want any more than that in the code.
Maven: I would argue that one in 20 may not be enough! Debug assertions are put in place to ensure that you are maintaining a valid state and that your assumptions hold true. Having one in 10 lines of source code may still not be enough. You only see assertions in debug builds, and if the assertions are coded properly, you should never know they are there unless there is a legitimate bug.
Your argument around avoiding blocking bugs is bogus. Code your assertions properly. Give some thought to the conditions. Be thankful if an assertion fires as it is likely exposing a bug in the code. A co-worker gave some great advice around assertions: "If you are assuming anything without verifying it in either the code or an assert, you’re walking a tightrope without a safety net."
Motley: I did learn a little trick for temporarily getting around someone else's assertions when I am debugging without having to rebuild. If there are just a couple that are bothering me, I can disassemble the code around the assertion, look for Int3 which is a debug break, and replace it with no-op byte codes.
Maven: That is very inventive, but make sure you follow up on why the assertion fired in the first place and file a bug as appropriate.
Motley: Another thing to note is that all of this assumes you are running a debug build and not a retail build.
Maven: Absolutely. All of your verification as a developer should be done on debug builds so that you can see the assertions pop up. If you are going to run a retail build but compile only your components in debug, then you may miss some assumptions that were made around your code. You should compile in retail before checking in, but try to do developer validation on a debug build running under the debugger.
Motley: There are ways to misuse assertions too. Regardless of the volume of assertions in source code, here is a list of some stuff I have seen lately in code reviews:
Maven: Good one! Assertions protect against conditions that should never happen. They are typically used to check internal state of your classes and invariants (refer to our discussion on design by contract). You still need a construct like exceptions to flag errors at run-time in retail builds. This is extremely important in public methods. In private methods you could get away with just assertions if the method is otherwise protected by public accessors.
Motley: Here's another one I saw last week:
Debug.Assert(i++ < 100, "Bounds of 'I' exceeded 100");
Maven: Another good one! If that is taking place in a loop, at retail the loop index will not be incremented. Never put executable code inside a debug assertion. Actually, this is a bit of an argument to also do some testing in retail builds as well. To refine my statement above that developer validation should take place on debug builds only, I'd like to resubmit that you should at least run your unit tests on a retail build as well before check-in. You taught me something Mot!
Motley: You act like that isn't a frequent occurrence. Without me around you would probably be on the fast track to nowhere at this company.
Maven: Yeah… right. But I'll humor you. To finish up, here are a few best practices around assertions:
Motley: You always take a simple concept and blow it all up. I didn't realize there was quite that much thinking behind assertions. They seem like a fairly basic language construct at the high level.
Maven: Yeah, as with many computer science, there may be more than meets the eye. That's what keeps our jobs interesting!
Motley: And overly complicated to the point of making frequent errors producing buggy code and-
Maven: Don't be so negative!
Note: Special thanks to the development leads of the mobile phone and shell teams at Microsoft for their indirect contributions to this blog entry.
Maven's Pointer: Think about assertions even at design-time. A design is typically full of assumptions. Document those assumptions at design-time and translate them to source code when implementation begins.
Maven's Resources:
I don't even use asserts (and even when I was a C++ dev, I didn't). I just couldn't get my head around coding defensively in a debug environment but not also in release. I guess I could conceivably use them to assert conditions that are completely determined within a file (i.e. the condition is not affected by another developer's inputs) but for me, defensive coding should also protect the release build (and I also know my unit tests are not comprehensive enough asserts to catch everything, leading to the potential for a logic gap in the release build)
Just make sure to only assert things that are essential. If you assert things that just happen to be that way then a simple change can break dozens of non-essential assertions.