read

In a followup to yesterday's post, let's revisit the concept of making an object "do too much". To rephrase this, doing too much means an object has too many responsibilities. How many is too many? Some would say more than one (1) responsibility is too many. I tend to agree with that number. And, as I completed my refactoring to rid myself of an if statement, I was informed that I was, in fact, making NSError do too much. I had made NSError responsible for two things, generating a nice error message AND logging it to the console.

NSError is a "data-bearing object". That is, the sole purpose of NSError is to shuttle across classes carrying a payload of information to its ultimate destination. That destination could be a log, the screen as an alert dialog or popup. Ultimately, what matters here is the idea that NSError's sole purpose is to hold onto information pertinent to an error, and allow some class more able to handle that error. What that also implies that the mere act of asking NSError to learn the concept of printing itself out to the console is an extra responsibility.

So, what to do now?

It's worth noting that NSError has properties that return the error description. NSError also implements the -description method which prints out somewhat useful information. But what needs to happen is we need to separate the concept of generating the error message and printing it out to the console (or where ever the message is supposed to end up). So, the category on NSError should be modified like so:

- (NSString *) MR_coreDataErrorDescription;
{
    //fancy 21st century error message generation technology
    return errorDescription;
}

Now, when we want to log this error to the console, we'll need to use syntax like so:

NSLog([error MR_coreDataErrorDescription]);

However, NSLog doesn't take kindly to nil objects, so the case that was quite nice before, namely when error is nil (ie. no error occurred), our app will crash. The suggested approach is to write a wrapper macro for NSLog (or your logger of choice) and have something like:

#define SafeNSLog(msg) do { if (msg) NSLog(msg); } while(0)
SafeNSLog([error MR_coreDataErrorDescription]);

But then our if check returns. I'm not so wild about this myself. What if we tried another way to log this:

[[error MR_coreDataErrorDescription] MR_logToConsole];

With MR_logToConsole defined as a category on NSString like so:

@interface NSString (MagicalRecordErrorHandling)

- (void) MR_logToConsole;
{
    NSLog(self);
}
@end

When the error variable is nil, nil is also the return value which is then sent the MR_logToConsole message. Messaging nil again results in nothing happening. This is ideal on our NSString category that logs the string to the console so that in the MR_logToConsole, self will never be nil, and NSLog will not crash. When the error is not nil, and a string is generated, our generated string will then proceed to log itself to the console.

This final breakdown of statements is nice in that we've separated out the two things that were previously occurring in a single method, the generation of the message we want to log and the actual console logging mechanism itself. We have also not handled the nil case again without resorting to an if statement. However, now you may see the self-logging ability of NSString and wonder if that is necessary or the correct home as well. That may be fodder for a follow up to this follow up.

--

  • Thanks to James Dempsey for pointing out that I was making NSError do too much.
Blog Logo

Saul Mora


Published

Image

Saul Mora

iOS Developer, Engineer, Dad, Human

Back to Overview