20

I used to work for a company where some of the lead architect/developers had mandated on various projects that assertions were not to be used, and they would routinely be removed from code and replaced with exceptions.

I feel they are extremely important in writing correct code. Can anyone suggest how such a mandate could be justified? If so, what's wrong with assertions?

Sam
  • 7,252
  • 16
  • 46
  • 65
Jesse Pepper
  • 3,225
  • 28
  • 48
  • I don't like how the question is asked as i see it, he says "I know best, if you don't aggree i will hunt you down" – Filip Ekberg Jan 07 '09 at 06:52
  • Well it is clearly subjective. In my opinion it is also borderline argumentative, but I would wait and see what discussion develops. – EBGreen Jan 07 '09 at 06:53
  • 1
    I'm happy to edit it... I really would like to hear good reasoning. I'm not out to create a fight. Sorry if it sounds aggressive. probably repressed feelings from having been forced to work that way. – Jesse Pepper Jan 07 '09 at 07:09
  • @EBGreen Why is it subjective? – Jesse Pepper Jan 07 '09 at 07:15
  • 3
    The question should not be "are assertions good?", but rather, "when are assertions good?" – titaniumdecoy Jan 07 '09 at 08:22
  • yes, or are assertions always bad? shall I rename it? – Jesse Pepper Jan 07 '09 at 08:34
  • @Jesse when you are presenting a question as an always or nothing proposition it will almost always be subjective. There will usually be edge cases that do not fit whatever your proposition is. Good or bad is almost always a subjective call. – EBGreen Jan 07 '09 at 13:10
  • 1
    @EbGreen: But I was saying that someone else told me they are always bad, see my rename of the question's title? I wasn't suggesting an all-one-way thing, I was saying, were they right in saying it was all one way, my feeling is that they have their place. That is not subjective. – Jesse Pepper Jan 08 '09 at 03:47

11 Answers11

20

We use a modified version of assert, as per JaredPar's comment, that acts like a contract. This version is compiled into the release code so there is a small size overhead, but disabled unless a diagnostics switch is set, such that performance overhead is minimized. Our assert handler in this instance can be set to disabled, silent mode (e.g. log to file), or noisy mode (e.g. display on screen with abort / ignore, where abort throws an exception).

We used automated regression testing as part of our pre-release testing, and asserts are hugely important here as they allow us to find potential internal errors that cannot be picked up at a GUI level, and may not be initially fatal at a user level. With automation, we can run the tests both with and without diagnostics, with little overhead other than the execution time, so we can also determine if the asserts are having any other side effects.

One thing to be careful of with asserts is side effects. For example, you might see something like assert(MyDatabasesIsOk()), which inadvertently corrects errors in the database. This is a bug, as asserts should never change the state of the running application.

SmacL
  • 22,555
  • 12
  • 95
  • 149
  • 2
    This sounds like a really nice strategy. It's funny when you're looking for people who think asserts are bad, you never hear from them. – Jesse Pepper Jan 07 '09 at 08:23
  • 2
    +1 for avoiding side effects. I've seen people write code where they think 'assert' means "make it valid" rather than "is it valid". Obviously the code then fails if the assert is compiled out! – Paul Stephenson Jan 07 '09 at 08:27
6

The only really negative thing I can say about assertions is they don't run in retail code. In our team we tend to avoid assertions because of this. Instead we use contracts, which are assertions that run in both retail and debug.

The only time we use assertions now is if one of the following are true.

  1. The assertion code has a noticable performance impact
  2. The particular condition is not fatal
  3. Occasionally there is a piece of code that may or may not be dead. We will add an assertion that essentially says "how did you get here." Not firing does not mean the code is indeed dead but if QA emails me and says "what does this assertion mean," we now have a repro to get to a particular piece of code (it's immediately documented of course).
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • Exactly my thinking... see my comment on Chris' answer – Jesse Pepper Jan 07 '09 at 06:48
  • About this point, see this answer and my update in the end pointing to a related question: http://stackoverflow.com/questions/117171/design-by-contract-tests-by-assert-or-by-exception#397873 – Daniel Daranas Jan 07 '09 at 07:03
  • 1
    Design by contract and usage of assertions is about proving your state is correct at all times in a precise mathematical way. Once proven correct, you need not prove it all the time at runtime which may slow the program. This can be a good thing (tm) – Greg Domjan Jan 07 '09 at 07:29
  • 1
    @Greg - only true if your testing goes through every possible state the application can be in which IMHO is highly unlikely. – SmacL Jan 07 '09 at 13:59
  • @smacl - in mathematics we use proof by induction and the like to avoid brute force testing of every possible state (which may indeed be impossible) to prove correctness. This can be applied to software algorithms in many circumstances too. – Daniel Paull Aug 03 '09 at 14:47
  • @Daniel, proof by induction is all very well, but most seasoned software QA people will tell you that 100% test coverage is very rarely if ever achieved in mosts systems tests, remembering that test coverage != code coverage while testing. Given this is true, and most applications are delivered with some bugs, contracts are a great help in analysing root cause of the bug. Does anyone here seriously believe they deliver 100% bug free code all of the time? – SmacL Jan 29 '10 at 08:57
  • @Shane - I'm happy to claim that the bug rates in my team are lower than most (note quite zero though) and that formal proofs and testing that is guided by such proofs are a part of our success. We also use Monte Carlo tests to exercise the code in unusual ways. Interestingly, Monte Carlo testing often shows up problems that you might not consider bugs, such as a pathological case where performance is unacceptable. Without programming by contract and liberal use of assertions, I truly believe that we would be *much* worse off. – Daniel Paull Jan 29 '10 at 12:02
5

assertions and exceptions are used for two different things.

Assertions are used for states that should never happen. For example, a signalton pointer should never be null and this error should be picked up during development using an assert. Handling it with an exception is alot more work for nothing.

On the other hand exceptions are used for rare states that could happen in the normal running of an application. For example using fopen and it returns a null pointer. It could happen but most times it will return a valid pointer.

Using assertions is nether wrong nor right but it comes down to personal preference as at the end of the day it is a tool to make programing easier and can be replaced by other tools.

Lodle
  • 31,277
  • 19
  • 64
  • 91
4

It depends on the criticality of your system: assertions are a failfast strategy, while exceptions can be used when the system can perform some kind of recovery.

For instance, I won't use assertions in a banking application or a telecommunication system : I'd throw an exception, that will be catched upper in the call stack. There, resources can be cleaned, and the next call/transaction can be processed ; only one will be lost.

philant
  • 34,748
  • 11
  • 69
  • 112
  • 1
    An issue arises as to whether the bad data arose from user/programmer supplying incorrect values (recoverable) or rogue-pointer trashing memory (unrecoverable). In the latter case, the train is off the track. Exceptions just take you further off track. Sometimes failfast and restarting is best. – Mr.Ree Jan 07 '09 at 17:40
  • Agreed, however besides NULL and mis-aligned pointers, and fence checking there is no definitive way to detect a corrupted pointer. – philant Jan 08 '09 at 08:44
3

Assertions are an excellent thing, but not to be confused with parameter/return value checking. You use them in situations that you don't believe will occur, not in situations that you expect could occur.

My favourite place to use them is in code blocks that really shouldn't be reached - such as a default case in switch-statement over an enum that has a case for every possible enum value.

It's relatively common that you might extend the enum with new values, but don't update all switch-statements involving the enum, you'll want to know that as soon as possible. Failing hard and fast is the best you can wish for in such circumstances.

Granted, in those places you usually want something that breaks in production builds as well. But the principle of abort()ing under such conditions is highly recommended. A good stack trace in the debugger gives you the information to fix your bug faster than guessing around.

1

Is it true that an assertion exists in the debug build, but not in the release build?

If you want to verify/assert something, don't you want to do that in the release build as well as in the debug build?

ChrisW
  • 54,973
  • 13
  • 116
  • 224
  • Well, that's a convention. It's quite easy to make them appear in the Release build too though. And I think it's often a good idea. However, it can be useful to have Debug-Only assertions that do time consuming checks, like verify a list is valid every time a list operation occurs. – Jesse Pepper Jan 07 '09 at 06:43
  • I like to release the version of the software which the developers actually tested; so I tend to delete the 'debug' build from the project/makefile, so that there's only one version that can be built (and tested, and debugged, and released). – ChrisW Jan 07 '09 at 06:47
  • Unit tests should be automated and run on both debug and release builds. Testing by a test-team should be done using the release build. – Jesse Pepper Jan 07 '09 at 06:48
  • Chris that is such a bad idea. The debug build is there because its normally 100x easier to debug due to the lack of optimizations. – Lodle Jan 07 '09 at 06:48
  • Another thing is that you may, later, want to be able to debug whatever you released: if you can't debug it, can you afford to release it? – ChrisW Jan 07 '09 at 06:55
  • Again, see http://stackoverflow.com/questions/117171/design-by-contract-tests-by-assert-or-by-exception#397873 and http://stackoverflow.com/questions/17732/when-should-assertions-stay-in-production-code. – Daniel Daranas Jan 07 '09 at 08:13
  • @Chris, I agree, and also use the same version for debug and release. The 10%-20% performance gains from the optimizer tend to be very minor when compared to manual optimizations at algorithm level. A single build removes so many potential bugs. – SmacL Jan 07 '09 at 08:22
  • @Chris "Another thing is that you may, later, want to be able to debug whatever you released: if you can't debug it, can you afford to release it?" - Nothing stops you from installing also the debug version in a subfolder, and switching to it on site when some problem arises. – Daniel Daranas Jan 07 '09 at 08:24
  • @Daniel, good links. Maybe you should add them as a seperate post so they appear in the body of the content, or if you have the rep, edit them into the original question. – SmacL Jan 07 '09 at 08:25
  • @Daniel, what if the bug doesn't appear in the debug build but only the release version? For example, uninitialised variables and buffer overflows only exposed when the code is optimised. Worse still side effects caused by mis-use of asserts. – SmacL Jan 07 '09 at 08:34
  • @Chris, I think you have the making of another good question and lively discussion here ;) – SmacL Jan 07 '09 at 08:34
  • @smacl, ok I made a separate topic of it: see http://stackoverflow.com/questions/420343/separate-debug-and-release-builds – ChrisW Jan 07 '09 at 13:43
1

The only guess is that because an exception is often non-fatal that it makes for a codebase that does not die in some odd state. The counter-point is that the fatality of an assertions points right to where the problem is, thus easy to debug.

Personally I prefer to take the risk of an assertion as I feel that it leads to more predictable code that is easier to debug.

notbenh
  • 197
  • 6
1

Assertions can be left on simply by not defining NDEBUG, so that's not really an issue.

The real problem is that assertions call abort(), which instantly stops the program. This can cause problems if there is critical cleanup your program must do before it quits. Exceptions have the advantage that destructors are properly called, even if the exception is never caught.

As a result, in a case where cleanup really matters, exceptions are more appropriate. Otherwise, assertions are just fine.

Henk
  • 3,193
  • 2
  • 17
  • 12
1

We use assertions to document assumptions.

We ensure in code review that no application logic is performed in the asserts, so it is quite safe to turn them off just shortly before release.

jamesh
  • 19,863
  • 14
  • 56
  • 96
0

One reason to veto assert() is that it's possible to write code that works correctly when NDEBUG is defined, but fails when NDEBUG is not defined. Or vice versa.

It's a trap that good programmers shouldn't fall into very often, but sometimes the causes can be very subtle. For example, the code in the assert() might nudge memory assignments or code positions in the executable such that a segmentation fault that would happen, does not (or vice versa).

Depending on the skill level of your team, it can be a good idea to steer them away from risky areas.

slim
  • 40,215
  • 13
  • 94
  • 127
  • If the code's that fussy, it's even more likely to work or not depending on whether it's a debug or optimized build, or whether it's working on a test or realistic data set. Assertions are way down on the list here. – David Thornley Feb 26 '09 at 15:20
  • +1 for avoiding any unnecessary differences between retail and debug builds. See my answer above on how we avoid this potential pit fall. – SmacL Jan 29 '10 at 08:47
0

Note, throwing an exception in a destructor is undefined behaviour.

John W
  • 426
  • 1
  • 5
  • 10