manicwave

Surf the wave

Defensive Programming and the Role of Assertions

Permalink

In Mike Ash's excellent series "Friday Q&A;", he recently wrote about defensive programming.

I've been tightening the screws on my first commercial Mac desktop app and reviewing my code for potential errors is front and center. It's fair to say that all developers should heed Mike's advice. I think its even more important (and fodder for a subsequent post) that indie developers heed this advice. Programming solo in the echo chamber has its share of challenges - someone to look over your shoulder occasionally is not a bad thing.

One thing that has bitten me a few times as I've reswizzled my UI and cleaned up ivars is missing IB connections. In Uli Kusterer excellent article on Defensive coding in Objective-C there is an excellent point about using NSAssert in your -awakeFromNib method to verify IB connections.

We're getting there. The issue is that I've been disappointed with the behavior of NSAssert in my work to date. I will say up front that I may be missing something with NSAssert, but when I'm writing code and using assertions - I want a strong indication that something's amiss. Pouring through console logs to find a missing IB connection doesn't do it for me.

Using the following in my awakeFromNib

-(void) awakeFromNib
{
    NSAssert(button,@"IB connection for button is missing");
    NSLog(@"I'm the next line, but you'll never see me");
}

results in

*** Assertion failure in -[VerifyIBConnectionAppDelegate awakeFromNib], ..../VerifyIBConnectionAppDelegate.m:24
2009-10-30 10:20:45.824 VerifyIBConnection[35323:903] An uncaught exception was raised
2009-10-30 10:20:45.824 VerifyIBConnection[35323:903] IB connection for button is missing
2009-10-30 10:20:45.850 VerifyIBConnection[35323:903] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'IB connection for button is missing'

This is great. The app is terminated, giving me strong feedback that something is amiss.

If however I move the assertions to -applicationDidFinishLaunching, I get

*** Assertion failure in -[VerifyIBConnectionAppDelegate applicationDidFinishLaunching:], .../VerifyIBConnectionAppDelegate.m:35
2009-10-30 10:23:28.746 VerifyIBConnection[35429:903] IB connection for button is missing

and - the app is still running. The same assertions, in the same class, called at different points in the lifecycle have dramatically different results. So now, in addition to coding defensively, I also need to ponder where I'm asserting what - or else.

Vincent Gable has a nice post on NSAssert considered harmful which brings out this and several other points.

So while you and I decide whether to use NSAssert or the more dramatic assert, I put together the following macro to reduce the verbosity of coding such assertions.

// NSAssert version
#define IBVERIFY(condition) NSAssert((condition), @"IB Connection not established for %s", #condition)
// assert version
#define IBVERIFY(condition) assert((condition)!=nil && "IB Connection not established for:" #condition)

The NSAssert version behaves as above. The assert version results in

Assertion failed: ((button)!=nil && "IB Connection not established for:" "button"), function -[VerifyIBConnectionAppDelegate awakeFromNib], file .../VerifyIBConnectionAppDelegate.m, line 25.
Program received signal:  “SIGABRT”.  

Outside of the gratuitous additional double quotes (which I'm sure someone will point out how to eliminate) - this version has the comforting side effect of killing my app - wherever it's invoked from. I take comfort in getting that kind of direct feedback.

Should my opinion of NSAssert change, IBVERIFY can be easily retargeted to use such.