I wonder if it\'s a good habit to use NSAssert all over the place? What would be the benefit of doing that? In which situations is it a good idea to use it?
The major warning are side effects. If you write:
NSAssert([self.navigationController popViewControllerAnimated:YES]!=nil,@"Something fails");
popViewControllerAnimated:
will be executed in the debug version, but not in the release version that strips NSAssert()
. This means that your release version will behave different to the debug version.
This problem disappears if you are careful enough:
UIViewController* vc = [self.navigationController popViewControllerAnimated:YES];
NSAssert(vc!=nil,@"Something fails");
Debugging. Whenever you write code, you're almost always making assumptions. Assumptions about the state of the environment, the values of your parameters, your local variables and fields, etc. Oftentimes, these assumptions are just wrong (an old collegue gave me a good maxim, that "Assumption is the mother of all fsckups").
Assertions exist to validate your assumptions, at the point you make them. You have a method
void foo(int x)
{
…
}
that you know and have documented only works for x > 5? Assert it!
Assertions live alongside unit testing and formal methods as part of good coding practice. Whilst some might think comprehensive unit tests ensure assertions are redundant, it's not the case. For example, you may have an assumption to a method that, say, employees are always over 16 and under 100 years old - but that the code, at the moment, doesn't require this. Unit tests that pass those parameters will succeed, but later when you need to use your assumption, you'll have code everywhere that passed the tests, but is wrong.
Broad use of NSAssert()
will not turn ObjC into Eiffel, but it's still a fairly good practice as long as you keep in mind how it's actually implemented and what it's doing. Things to keep in mind about NSAssert()
:
Xcode does not turn off NSAssert()
in Release mode by default. You have to remember to add NS_BLOCK_ASSERTIONS
to GCC_PREPROCESSOR_DEFINITIONS
in you xcconfig. (You are using xcconfigs, right?) A common issue is to assert non-nil in cases where nil will quietly work; this can mean field crashes for things that could have gracefully recovered. This is unrelated to the NDEBUG
macro used by assert()
, and you have to remember to define both if your code includes both types of assertions.
If you compile out NSAssert()
in Release mode, then you get no indication where a problem happened when customers send you their logs. I personally wrap NSAssert()
in my own macros that always log, even in Release mode.
NSAssert()
often forces duplicated logic. Consider the case of testing a C++ pointer for NULL. You use NSAssert()
, but you still need to use a simple if()
test as well to avoid crashing in the field. This kind of duplicated code can become the source of bugs, and I have seen code that fails due to assertions that are no longer valid. (Luckily this is generally in Debug mode!) I have debated a lot how to create a macro that would combine the assertion and if()
, but it's hard to do without being fragile or making the code hard to understand.
Because of the last issue, I typically put "NSAssert(NO, ...)
" in an else{}
clause rather than performing assertion at the top of the method. This is not ideal because it moves the contract away from the method signature (thus reducing its documentary benefit), but it is the most effective approach I've found in practice.
I highly recommend this article
http://www.mikeash.com/pyblog/friday-qa-2013-05-03-proper-use-of-asserts.html
And as the article states it is often good to use a assert-mechanism which is not NSAssert. For example:
https://github.com/hfossli/AGAssert