Failing Fast

August 19, 2013

Failing fast is the decision to abort a running application when the application encounters a bad state. The decision, however, to abort a running application is often difficult. This post does not attempt to convince you to adopt a fail fast policy. Instead this post shows a way of using simple macros to make it easy to abort an iOS app with crash report logging.

Assert Expression

The BTSAssert macro aborts the app if the given expression evaluates to false. The macro performs the following:

  • evaluates the expression
    • if true, nothing happens
    • if false
      • log a message
      • abort the app using abort()

This code is always on for debug and release versions. Why? Because bugs still exist in production/ release code, so we still want to fail-fast. The runtime cost is minimal.

#define BTSAssert(expression, ...) \
    do { \
        if (!(expression)) { \
            NSString *__BTSAssert_log_string = [NSString stringWithFormat:@"Assertion failure: %s. %@", #expression, [NSString stringWithFormat:@"" __VA_ARGS__]]; \
            __BTS_LOG(@"%@", __BTSAssert_log_string); \
            abort(); \
        } \
    } while(0)

Example Usages:

BTSAssert(someCounter >= 5);
BTSAssert(someCounter >= 5, @"Counter is wrong.");
BTSAssert(someCounter >= 5, @"Counter is wrong: %d", someCounter);

Force Abort

The BTSFail macro aborts the app. The macro performs the following:

  • log a message
  • abort the app using abort()

This code is always on for debug and release versions. Why? Because bugs still exist in production/ release code, so we still want to fail-fast. The runtime cost is minimal.

#define BTSFail(...) \
    do { \
        NSString *__BTSFail_log_string = [NSString stringWithFormat:@"%@", [NSString stringWithFormat:@"" __VA_ARGS__]]; \
        __BTS_LOG(@"%@", __BTSFail_log_string); \
        abort(); \
    } while(0)

Example Usages:

BTSFail();
BTSFail(@"Counter is wrong.");
BTSFail(@"Counter is wrong: %d", someCounter);

Intro To Crashlytics

Crashlytics is a very simple crash reporting tool for iOS and Android. The __BTS_LOG macro used in BTSAssert and BTSFail enables logging to the Crashlytics API for release builds. Logging to Crashlytics, in my opinion, only makes sense for release builds. There are numerous reasons why. One reason is because there is no need to link Crashlytics with OCUnit test targets. Debug builds log to NSLog.

Crash Report Logging

#ifdef DEBUG
#define __BTS_LOG(__FORMAT__, ...) NSLog((__FORMAT__), ##__VA_ARGS__);
#else
#define __BTS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#endif

The CLSLog is part of the Crashlytics logging API. Crashlytics submits log statements with an iOS crash report. This means that Crashlytics combines a symbolicated crash report with the programmer provided abort message. Very nice!

Summary

  1. You should adopt a fail fast policy.
  2. You should write unit tests.
  3. You should try Crashlytics.