I recently found out that there are two types of assertions we can use: there is the popular unit test Assert and then there is the “normal” Assert which is used in debug/production code.
Whenever I heard the term Assertion, I immediately thought about unit testing, checking the test’s condition to determine whether the unit test passed or not.
The “normal” assertion was kind of new to me. I knew the concept, but I didn’t imagine it could be used in everyday debug/production code. However, this is a pretty cool feature (at first glance at least :)). I don’t know why isn’t it more popular or why most people don’t use it. I haven’t seen any assertions in the debug/production code I worked with in my (limited) roughly 5 years experience in software development.
As I’ve said, we can use assertions in both debug and production code.
By debug code I am referring to the not released code, which is still under development.
By production code I am referring to a code which has been released, is live and used on different systems in production.
We have two classes which can be used for the above mentioned cases, both classes are under the System.Diagnostics namespace.
Debug.Assert(bool) – used to check different conditions. If the condition is true, nothing special happens, otherwise the code execution is stopped and an Assertion Failed error message is usually displayed (for GUI applications only). Once the error message is displayed, we have three options:
- abort – which causes the code to execution to stop;
- retry – which will allow the developer to get into debug mode right on that assert;
- ignore – which ignores the failed assertion and continues with the code execution.
The Assert method has two more overloads: Debug.Assert(bool, string) and Debug.Assert(bool, string, string). The string parameters can be filled in to display one or two messages when the assertion fails in the Assertion Failed error message.
Debug.Assert will only be executed when the code is compiled under Debug configuration. Since it won’t be included in the Release, it can be used without any restrictions while developing to catch odd cases which may be not covered by the unit tests. It’s perfect for the cases when the code crashes randomly because the developer can jump into debug mode and identify what’s going on.
Trace.Assert(bool), Trace.Assert(bool, string), Trace.Assert(bool, string, string) is the same as Debug.Assert the only difference being that Trace.Assert is used for Release builds (code compiled under Release configuration). The Trace.Assert also works in debug configuration, but it was designed to be used in Release builds.
Assertions vs. Exception Throwing
You might think why on earth would I complicate things by using these assertions when I could be throwing exceptions in the wired cases. To understand why using assertions is better, you have to know the difference between assertions and exceptions.
Assertions stop the code execution when the assert condition fails.
Exceptions can be thrown and caught, allowing the code to continue execution.
Exceptions were designed to be caught and handled. That’s why you catch an exception, to handle it and allow the code to continue its execution. Assertions on the other hand are designed to validate that the received data is valid so the whole program does not enter a corrupted state. Usually, when the data you receive is corrupt, the whole program can be corrupted as well and this is a case we want to avoid at all costs. Breaking the code execution the moment our data is corrupt would be the best approach in preserving our program’s state corruption free.
You want to use Assertions to check for corrupted data or for weird cases which shouldn’t happen (situations which cannot arise during the software’s normal operations) and for which there is nothing to be done in order for the program to continue its execution (ex: division by zero).
You want to throw and catch exceptions for the cases where you can actually do something about the exception (situations which arise during the software’s normal operations) and the code execution may have a chance to continue its execution (for example if a null reference is encountered, you could instantiate that object).
Like a fellow on stackoverflow said:
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.
Both Debug and Trace classes also expose other methods such as Write, WriteIf, WriteLine, WriteLineIf which can be used to log certain errors using TraceListeners, but this is another discussion for a future post.
A simmilar concept which will have a dedicated post in the near future is Code Contracts and asserting pre and postconditions.