Distinguish between checks and assertions
[info]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.

Disable assertions and logging in your Release build
[info]chanson
Typically, when you create a build of your software to release, you'll apply a whole bunch of additional optimizations to that version that you won't apply to the debugging builds. You'll strip debugging symbols — or, nowadays, build them to a separate DWARF with dSYM file — and crank up the compiler optimization and instruction scheduling settings that would normally make debugging hard.

One optimization that's often overlooked, though, is to turn off assertions. A lot of developers will use assertion macros as sort of a "continuous unit testing" technique within their code during development. They'll assert that methods' preconditions hold, that the postconditions of various operations continue to hold, that changes within methods produce expected results, and so on.

Standard C provides the assert(3) macro to perform a simple form of verification. By default, it just writes the failure information — including the failing file and line — and then calls abort(3); most environments also provide a way to extend its functionality by using assert as a wrapper for a sub-macro or other function.

Similarly, Cocoa's Foundation framework provides a number of NSAssert and NSParameterAssert macros that you can use in your code. (The primary difference is in the message of the exception they generate.) These normally throw an NSInternalInconsistencyException, but their behavior is extensible by subclassing NSAssertionHandler.

The thing is, you probably don't want the released versions of your applications terminating immediately or throwing exception in unexpected places — even though you probably do want them to do so during development. (After all, that's part of how you ensure they won't happen in production, right?) So you need to disable them in your release build.

Fortunately, Xcode makes this really easy. You just need to add two entries to the Preprocessor Macros build setting in your Xcode project, typically at the project level (since you'll want it to apply to all targets in your project). To turn off Standard C assertions, you just need to specify NDEBUG while for turning off Foundation assertions, just specify NS_BLOCK_ASSERTIONS. If you do this, you can put assertions literally everywhere in your code but be confident you'll only ever hit them during development.

Logging is in a similar boat. The release build of an application should, in general, never log in normal use. However, it can be extremely useful when things do go wrong to allow your users to enable logging by changing a hidden (or not-so-hidden) preference within your application. Ideally, you could even control the logging for different aspects of your application and different levels of severity, so enabling logging for one piece of functionality doesn't affect others, and so you can avoid spewing more log information than necessary.

Unfortunately, there's no built-in facility in Foundation for this sort of fine-grained logging. There's really just NSLog which, though useful during development, is both unconditional and doesn't have either aspects or levels. However, there's the Open Source (BSD licensed) Log4Cocoa project which aims to provide most of this functionality. It's currently based on the same design as Log4J, and it hasn't had a lot of work since its introduction, but it's a good starting point for working sophisticated logging into your own applications and it would be great to see developers pick it up again.

Bob Frank and Wolf Rentzsch presented on both of them for Uniforum a few years back, and made a PDF of their presentation available. Just be sure to tune the logging that actually gets compiled into your release builds by leveraging the Preprocessor Macros build setting...

Update (April 30, 2007): I've followed this up with another post on the importance of distinguishing between assertions and checks in your code. One of the main reasons I find that people don't want to turn off assertions before shipping is that they're using assertions as checks; if you distinguish between them, then this won't be a problem.