Previous Entry Add to Memories Share Next Entry
Distinguish between checks and assertions
latest
chanson
In my previous post, I suggested turning off assertions for your Release build. However, if you want to be able to do so, you'll need to be careful to distinguish between assertions that can be safely turned off, and those that can't.

For example, say you've implemented your own ordered-set class, MyOrderedSet. Obviously it will have an interface similar to both the NSSet class and the NSArray class in Cocoa's Foundation framework. As part of making it work like the Foundation collections, you'll probably want to do things like make it throw an NSRangeException if the caller requests the object at an index that's at or beyond the collection's count.

You might be tempted to implement this using a form of assertion, since by default assertions in Cocoa will throw an exception. But remember, this is part of the API contract of your class, not just an internal detail that should never go wrong! Thus if you do implement this using an assertion, and you disable assertions in your Release build, your Release build will wind up implementing your API incorrectly.

The solution is to distinguish in your code between assertions that verify state that should never be incorrect, and checks that ensure preconditions that are specified as part of an API contract are valid. In the example above, you could easily use a check to cause a range exception to be thrown, and this would still be compiled into your Release build.

The Foundation framework in Cocoa doesn't have any pre-defined checks, but they're not hard to develop on your own, especially with C99 variadic macros. You'll probably want to use a syntax similar assertions in creating your check macro, for example, MyCheck(condition, exceptionName, format, ...) and you'll probably want it to just throw the named exception with the given reason. If you take the time to make your checks easy to use, you'll definitely use them more, and if you make them use exceptions, you'll make it easy to ensure your application's state is rolled back in a sane fashion.

The behavior of your checks can even be specified as part of your unit tests. (It's 2007, you are writing unit tests, right?) You can write tests that mimic the misuses of your API that your checks are designed to trap, and then note that these misuses cause the appropriate exception to be thrown via OCUnit's STAssertThrowsSpecificNamed assertion macro.

Thanks for getting me thinking, Chris... I hadn't realized the perils (http://wincent.com/a/about/wincent/weblog/archives/2007/05/assertions.php) of relying on NSParameterAssert to enforce my API contract.