'Should one leave asserts in production iOS apps?
Common practice might be to put asserts in code to check input parameters, data integrity, and such, during app development.
I test my apps, BUT, given that I'm not Knuth (and he writes $1 checks), and I can't afford to employ a large team of full-time QA people as do some medical and space systems software companies, I assume that all my apps will always have plenty of bugs that have never yet been seen during testing or QA. Assuming otherwise seems quite intellectually dishonest. So after testing an app (and obviously removing all bugs causing any previously seen ASSERT failures) and getting the app ready to ship to Apple, what should be done with all the ASSERT checks in the Release/Distribution build? Leave or no-op?
Here's one rationale for leaving them in: If an app acts wonky for some users, the app might get rated by those users as 1-Star without anyone ever telling the developer why in sufficient detail. But if the app crashes from an ASSERT failure, the app might still get rated 1-Star, but the developer could potentially get some crash dumps, indirectly via iTunes and iTunes Connect if enough users opts in, to figure out what is going wrong. And if the app gets rejected by Apple due to a brand new ASSERT crash, that will prevent a bad version of the app from ever getting onto one's customer's devices.
Solution 1:[1]
Leave them in for exactly the reasons you specify, but also because in certain cases they act as comments (especially where types are concerned in Objective-C). And do not worry about the performance hit unless it becomes a problem or you know you're in a performance critical situation and a particular assert is going to be run hundreds or thousands of times on the main run-loop.
Can't resist mentioning this article on asserts vs. NSAssert.
Personally, I start to remove the ones that I've put in for debugging purposes, but if you use asserts to check data integrity, parameters, resource dependencies and other related things -- arguably, you could throw Exceptions yourself instead, which might be wiser -- then I would leave them in.
Note: A further point is that just removing asserts is utterly stupid, since your app will either crash or be in an inconsistent state, both of which are worse than crashing in a way that you can recognize from the crash logs (so leave the asserts in). Replace asserts with if
statements, on the other hand, could be a good thing.
Solution 2:[2]
My recommendation: You should leave them ON by default. I say: "fail hard, fail early" -- and keep your bugfixes at a higher priority than features.
However, choice is also good -- I don't think one size fits all programs. For this reason, I use multiple types of assertions. Some will be live in release, some will not. I write a lot of error detection, and I also write a lot of performance critical programs. I can't leave a ton of diagnostics and sanity checks in hot paths of release builds.
Unfortunately, it cannot be an afterthought (unless maybe you are prepared to prioritize quality and testing for an open-ended amount of time). If you think about it, the single/traditional approach also cannot be an afterthought. With either model, it is best decide whether assertions or which assertions will be enabled in release before writing your program.
So the basic general form of a dual assert model might look like:
#include <assert.h>
/*
MONDebugAssert assertion is active in debug and disabled in release.
Recommendation: Always define NDEBUG (or not) in your build settings,
and nowhere else.
*/
#if defined(NDEBUG)
#define MONDebugAssert(e) ((void)0)
#else
#define MONDebugAssert(e) \
(__builtin_expect(!(e), 0) ? __assert(#e, __FILE__, __LINE__) : (void)0)
#endif
/* MONAssert assertion is active at all times, including release builds. */
#define MONAssert(e) \
(__builtin_expect(!(e), 0) ? __assert(#e, __FILE__, __LINE__) : (void)0)
where __assert
is the platform assertion handler.
Then in use:
MONDebugAssert(0); // << will fail in debug, and not in release.
MONAssert(0); // << will fail in any case
Of course, it is easy enough to adapt for your needs, or create variants where self
is assumed to be in the scope (like NSAssert
).
Solution 3:[3]
There always tends to be something better to do in production code then fail an assert, but sometimes the trade off is less cut and dried.
A good time to assert: when continuing on will destroy user data ("there is a known good save file already, and I've detected damaged data structures, if I write over the good file with what I have I'll destroy it"). Obviously the "best" option is to not damage the data in the first place. Second best is to detect damaged data during the save and save to a new file (it might be loadable, or maybe what is in it is valuable enough to salvage via heroic means).
Another good time to assert: when you know continuing on will crash (passing NULL to many plain C functions, about to divide by zero...). The assert will carry more useful information. Of corse even better is not getting into that state. Second best is aborting the operation, not the program ("I can't print" is better then "I threw away your unsaved data, and by the way you can't print").
You can pretty much always break things down like that. However error recovery is pretty complex, and handing some errors will double or worse the size of your code for something that may never happen...and then you have to figure out how to test it, and...
...so it is a trade off. What else in your program will be better if you skimp on error recovery? Will it be enough better to make up for the extra crashes?
Solution 4:[4]
My NSAssert
failures indicate something went horribly wrong and proceeding further would result in undefined behaviour and data corruption. I can't imagine why people parrot disabling them in release builds.
If you can recover from it, you should be using an NSError
instead.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Dan Rosenstark |
Solution 3 | |
Solution 4 | Monstieur |