Welcome to MSDN Blogs Sign in | Join | Help

Syndication

Tags

    No tags have been created or used yet.
Assert Abuse

I see this all the time: 

HRESULT Foo::get_Bar(Bar** ppBar)
{
    ASSERT(ppBar != NULL);
    *ppBar = m_pBar;
    return S_OK;
}

Eh, what's the point of that ASSERT there?  Without it, the subsequent assignment to *ppBar will cause an exception which will almost invariably pop you right into the debugger at the exact point of failure.  It seems redundant. The only use is that you don't see the need for real error handling, but the dialog that the OS throws up for Access Violation is just too scary for users to see (and somehow, the ASSERT box isn't).

You could say it has some value as self-documenting code.  But I think it's already clear that if you wanted to get a Bar object, you had better give get_Bar a place to put it.

Eh, but that's a mild form of abuse; it just bugs me because it's a sign of mechanical thinking, a ritual done without a purpose.  But nothing bad happens; at worst you've just added an extra line of clutter. What really sucks is THIS nonsense, also far too common:

HRESULT hr = ReadInitializationFile( );
ASSERT(SUCCEEDED(hr));

Can ReadInitializationFile fail?  Sure it can!  It could be malformed or nonexistent. What happens if this is the case?  Well, some hapless developer gets popped into the debugger, they investigate the problem for the better part of an hour, and at the end of it, they make no change to the code -- there's no bug there, just a malformed INI file.  They learn to shrug their shoulders and hit F5 or whatever key it is to continue execution.  Five minutes later, BAM!, there goes another assertion ... no worry, it's just that bogus one again.  Ignore it ...

Get it?  You've done your part to train a whole generation of programmers to ignore assertions.  Worse yet, you're not even trying to handle the failure in a graceful way.

Just for review: an assert should always indicate a programming error.  Assert is not a logging facility.   If what you need is a logging facility, there are far better alternatives.  An assert should not be thrown for conditions which, however unusual or exceptional, your program handles correctly (or should handle correctly).  Instead, an assert should always be a precondition, postcondition, or invariant of the code which should always be true, no matter what data is thrown at it for input, or whatever unusual (valid) code paths taken to get there.  For example:

// Finds all the prime factors for the given input. Assumes
// the input has already been error-checked (ie, it's greater than one).

vector<int> FindPrimeFactors(int n) 
{ 
    assert(n > 1); 
    vector<int> ans; 

    // Many lines of code later ... 

    // Verify that the factors multiply up to the original value.
    assert(accumulate(ans.begin( ), ans.end( ), 1, multiplies<int>( )) == n); 
    return ans; 
}

Published Wednesday, October 04, 2006 7:38 PM by cashto

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: Assert Abuse @ Thursday, November 02, 2006 6:51 PM

I'm going to have to disagree with your dislike of

"HRESULT Foo::get_Bar(Bar** ppBar)

{

   ASSERT(ppBar != NULL);

   *ppBar = m_pBar;

   return S_OK;

}"

In the school of programming by contract, upon the entrance to each function the first thing you should check is that the contract is held.  In this case one would assume ppBar != NULL is part of the contract.  This behavior is particularly useful when someone else may be using your code but does not necessarily have access to the source and you are in a managed language (you could do something handy lie print the call stack) upon a contract violation and a little error, in addition the throwing an exception.

Programming by contract also encourages that you do rep. invariant checks at the begging and end of every function.  This generally doesn't sit well with people at first, but it is in the same vein as unit testing.  Checking to make sure your class's rep. invariant holds after each function, whether it was a mutator or not, is a great way to catch errors before they become a problem. It also is a great way to limit the scope of their propagation.

And finally, hi cashto!

Outlook Mobile Bloggers

# re: Assert Abuse @ Thursday, November 02, 2006 9:10 PM

Don't get me wrong -- I like DBC a lot; in fact in the example of "good" asserts, you'll notice I asserted a precondition.

The point I'm trying to make has to do with the mechanical application of "best practices" without really understanding what you're trying to accomplish.  Like I said, this example is relatively minor and I would have never brought it up unless I thought that it was symptomatic of a certain "program by ritual" mindset.  You may have heard to it as "cargo cult programming" ...

What does DBC accomplish?  Two things: it clarifies the function preconditions and ensures fast failure when they're violated.  How does it help in the ASSERT(p) case?  Well, it doesn't.  It's already more than clear that p is has to be non-null, and if it isn't, an exception is thrown, which is a pretty loud way of failing.

Are you a bad programmer if you write "ASSERT(p)"?  No, you might have done it by force of habit, and that's fine.  I'm more worried about the guy who looks at the code without the assert and says, "you should put an assert there".  He's the one running on automatic, either not understanding what DBC is all about, or at least not bothering to understand why he does what he does.

cashto

Leave a Comment

(required) 
required 
(required) 
Page view tracker