Quantcast
Channel: Mobile Development Tips » crash
Viewing all articles
Browse latest Browse all 2

To use or not to use: Assertions

$
0
0

Difficulty: easy moderate challenging Too many times I found out that programmers rarely and somewhat incorrectly use assertions. Hopefully this quick guide will help you understand how to avoid being one of those. As a rule of thumb, a programmer needs to put guards around his code to take into account inappropriate input and act accordingly. Sometimes, he might run into corrupted data from the network, invalid input from your user, or even getting his objects into unintended states. All of these should be somehow guarded against. He can use if statements accompanied with return, exit, pop-up alerts, throwing exception, or working around those issues. But, sometimes, these guards can be redundant, excessive, and not likely to happen in real life. For instance, imagine that you write a calcSumOfNumber: andNumber: function that checks for user input and calls another internal function internalCalcSumOfNumber: andNumber: to calculate using the data:

- (NSString*) calcSumOfNumber:(NSString*)num1 andNumber:(NSString*)num2
{
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber* convNum1 = [formatter numberFromString:num1];
NSNumber* convNum2 = [formatter numberFromString:num2];
[formatter release];
NSString* res;
if ((convNum1==nil) || (convNum2==nil)) {
res=nil;
} else {
NSNumber* resNum=[self internalCalcSumOfNumber:convNum1 andNumber:convNum2];
res=[resNum stringValue];
}
return(res);
}
- (NSNumber*) internalCalcSumOfNumber:(NSNumber*)num1 andNumber:(NSNumber*)num2
{
int int1=[num1 intValue];
int int2=[num2 intValue];
NSNumber* sum=[NSNumber numberWithInt:(int1+int2)];
return(sum);
}

Of course, the outer function should check the user input. But should the inner function check the data as well? If you modify the inner function to guard check, your code looks a bit messy and wastes some valuable CPU time:

- (NSNumber*) internalCalcSumOfNumber:(NSNumber*)num1 andNumber:(NSNumber*)num2
{
if ((!num1) || (!num2)) {
return(nil);
}
int int1=[num1 intValue];
int int2=[num2 intValue];
NSNumber* sum=[NSNumber numberWithInt:(int1+int2)];
return(sum);
}

Or, should you leave the inner function as it is, without any guards, but then whoever reads and later uses the function will not know your assumptions. For instance, what’s going to happen if one of the two parameters is nil? Will the result then be something valid? Actually, both options are not so good. The key here is to use asserts. Asserts are if conditionals that are only enabled during development and debugging. So, in this case, it’s wise to put the conditions in an assert statement:

- (NSNumber*) internalCalcSumOfNumber:(NSNumber*)num1 andNumber:(NSNumber*)num2
{
NSAssert(num1, @"First parameter is missing");
NSAssert(num1, @"Second parameter is missing");
int int1=[num1 intValue];
int int2=[num2 intValue];
NSNumber* sum=[NSNumber numberWithInt:(int1+int2)];
return(sum);
}

During development, the assert statements will be checked and upon failing, will crash the app, allowing you to fix your code up as needed. But once the development phase finishes, these asserts are simply ignored and not compiled into the executable. This, then, makes your code run faster in production. Left in your source, these asserts also help you describe the entailed logic of your code. By the time of releasing your app, hopefully, you have fixed all the bugs, so that these asserts are not needed any more. Keep in mind though, that in case your program still contains bugs while in production – when assertions are disabled and the program “skips over” the assertions – these bugs will probably end up crashing your app at an unrelated line of code. This will make your debugging a bit harder; every rose has its thorn. Remember that using if statements as guards and acting accordingly without assertions is still imperative, because in many cases you do need to confirm user input, validate data received from the network, verify data in files, etc. and deal with it. In our example, the outer function ought to contain the following lines as they are, without using assertions:

if ((convNum1==nil) || (convNum2==nil)) {
res=nil;
}

To understand how the compiler knows whether to put in or take out the asserts’ code, let’s take a closer look at the implementation of NSAssert (modified a bit for brevity):

#define NSAssert(condition, desc)           \
_NSAssertBody((condition), (desc), 0, 0, 0, 0, 0)
... 
#if !defined(NS_BLOCK_ASSERTIONS)
#define _NSAssertBody(condition, desc, arg1, arg2, arg3, arg4, arg5)    \
if (!(condition)) {             \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd object:self file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ description:(desc), (arg1), (arg2), (arg3), (arg4), (arg5)];    \
}                       \
#endif

It’s easy to see that if you have defined a NS_BLOCK_ASSERTIONS then the assert will simply be ignored. And if you haven’t, then it will check for its parameter’s literal. To allow asserts to behave appropriately, Xcode automatically put NS_BLOCK_ASSERTIONS=1 as a precompiled header in your Release target. Then all you need to do is use your Debug target while coding and debugging and the Release target when you are deploying for production.


Filed under: General Programming Tagged: assert, crash, debug, exception, precompiled header, target, throw, validation

Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images