Symbian OS Explained - Effective C++ Programming For Smartphones (2005) (779885), страница 55
Текст из файла (страница 55)
So, for example, you’dadd the following enumeration to CTestClass, so as not to pollute theglobal namespace:enum TTestClassPanic{EInvalidData,// =0EUninitializedValue // =1...};Then define a panic function, either as a member of the class or as a staticfunction within the file containing the implementation of the class:−−−ASSERT DEBUG−257static void Panic(TInt aCategory){_LIT(KTestClassPanic, "CTestClass-Panic");User::Panic(KTestClassPanic, aCategory);}You could then write the assertion in TestValue() as follows:void CTestClass::TestValue(TInt aValue){__ASSERT_DEBUG((aValue> =0), Panic(EInvalidTestValueInput));...}The advantage of using an identifiable panic descriptor and enumerated values for different assertion conditions is traceability, both foryourself and clients of your code, when an assertion fails and a panicoccurs.
This is particularly useful for others using your libraries, sincethey may not have access to your code in its entirety, but merely to theheader files. If your panic string is clear and unique, they should be ableto locate the appropriate class and use the panic category enumeration tofind the associated failure, which you will have named and documentedclearly to explain why the assertion failed.There may be cases where there’s nothing more a client programmercan do other than report the bug to you, the author of the code;alternatively, the problem could be down to their misuse of your library,which they’ll be able to correct.
I’ll discuss the pros and cons of usingassertions to protect your code against badly-programmed calling codelater in this chapter.If you don’t want or need an extensive set of enumerated panic values,and you don’t expect external callers to need to trace a panic, you mayconsider using a more lightweight and anonymous assertion.
A goodexample of this is to test the internal state of an object, which couldnot possibly be modified by an external caller, and thus should alwaysbe valid unless you have a bug in your code. Assertions can be addedearly in the development process, but left in the code, in debug builds, tovalidate the code as it is maintained and refactored. In these cases, youmay consider using the ASSERT macro, defined in e32def.h as follows:#define ASSERT(x) __ASSERT_DEBUG(x, User::Invariant())I like this macro because it doesn’t need you to provide a paniccategory or descriptor. If condition x is false, in debug builds only, itcalls User::Invariant() which itself panics with category USER andreason 0. The macro can be used as follows:258BUG DETECTION USING ASSERTIONSASSERT(iClanger>0);As an alternative to using ASSERT to test the internal state of yourobject, you may wish to consider using the __TEST_INVARIANT macro,which I discuss in more detail in Chapter 17.An alternative, useful definition of ASSERT which you may see insome Symbian OS code is as follows:#ifdef _DEBUG#ifdef ASSERT#undef ASSERT#endif#define __ASSERT_FILE__(s) _LIT(KPanicFileName,s)#define __ASSERT_PANIC__(l) User::Panic(KPanicFileName().Right(12),l)#define ASSERT(x) { __ASSERT_FILE__(__FILE__);__ASSERT_DEBUG(x, __ASSERT_PANIC__(__LINE__) ); }#endifThis slightly alarming construction is actually quite simple; in debugbuilds, if condition x is false, the code is halted by a panic identifyingthe exact place in code (in the panic descriptor – which contains the last12 characters of the filename) and the panic category (which containsthe line of code at which the assertion failed).
The disadvantage ofusing this construct is that you are coupling the compiled binary directlyto the source file. You cannot later modify your code file, even tomake non-functional changes to comments or white space lines, withoutrecompiling it to update the assertion statements. The resulting binarywill differ from the original, regardless of the nature of the changes.Depending on how you deliver your code, this limitation may prohibityou from using this macro.Let’s move on from how to use the Symbian OS assertion syntax toconsider when you should use assertions and, perhaps more importantly,when you should not.Firstly, don’t put code with side effects into assertion statements. Bythis, I mean code which is evaluated before a condition can be verified.For example:__ASSERT_DEBUG(FunctionReturningTrue(), Panic(EUnexpectedReturnValue));__ASSERT_DEBUG(++index<=KMaxValue, Panic(EInvalidIndex));The reason for this is clear; the code may well behave as you expect indebug mode, but in release builds the assertion statements are removedby the preprocessor, and with them potentially vital steps in your programming logic.
Rather than use the abbreviated cases above, you shouldperform the evaluations first and then pass the returned values into the−−−ASSERT DEBUG−259assertion. You should follow this rule for both __ASSERT_DEBUG and__ASSERT_ALWAYS statements, despite the fact that the latter are compiled into release code, because, while you may initially decide theassertion applies in release builds, this may change during the development or maintenance process. You could be storing up a future bug forthe sake of avoiding an extra line of code.You must also make a clear distinction between programming errors(”bugs”) and exceptional conditions. Examples of bugs might be contradictory assumptions, unexpected design errors or genuine implementationerrors, such as writing off the end of an array or trying to write to a filebefore opening it.
These are persistent, unrecoverable errors which shouldbe detected and corrected in your code at the earliest opportunity. Anexceptional condition is different in that it may legitimately arise, althoughit is rare (hence the term ”exceptional”) and is not consistent with typicalor expected execution.
It is not possible to stop exceptions occurring,so your code should implement a graceful recovery strategy. A goodexample of an exceptional condition that may occur on Symbian OS is anout-of-memory failure, because it is designed to run constantly on deviceswith limited resources for long periods of time without a system reset.To distinguish between bugs and exceptions, you should consider thefollowing question. Can a scenario arise legitimately, and if it can, isthere anything you should or could do to handle it? If your answer is”yes”, you’re looking at an exceptional condition – on Symbian OS, thisis exhibited as a leave (leaving is discussed in Chapter 2).
If the answeris ”no”, you should consider the situation to be caused by a bug whichshould be tracked down and fixed. The rest of this chapter will focus onthe use of assertions to highlight such programming errors.When code encounters a bug, it should be flagged up at the pointat which it occurs so it can be fixed, rather than handled or ignored(which can at best complicate the issue and, at worst, make the bugmore difficult to find or introduce additional defects as you ”code aroundit”).
You could consider assertions as an annoying colleague, leaningover your shoulder pointing out defects for you as your code runs. Theydon’t prevent problems, but make them obvious as they arise so you canfix them.If you add assertion statements liberally as you write code, theyeffectively document assumptions you make about your program logicand may, in addition, flag up unexpected problems.
As you considerwhich assertions to apply, you are actually asking yourself what implicitassumptions apply to the code and how you can test them. By thinkingabout each piece of code you write in this way, you may well discoverother conditions to test or eliminate that would not have been immediatelyobvious. Frequent application of assertions as you code can thus helpyou to pre-empt bugs, as well as catch those already in existence.260BUG DETECTION USING ASSERTIONSAnd there’s more! Another benefit of assertions is the confidence thatyour code is behaving correctly, and that you are not ignoring defectswhich may later manifest themselves where they are hard to track down,for example intermittently or through behavior seemingly unrelated to thecode that contains the error.
Say you write some library code containingno assertions and then create some code to test it, which runs and returnsno errors; are you confident that everything behaves as you expect andthe test code checks every boundary condition? Are you sure? Certain?Adding assertions to test fundamental assumptions in your code willshow you immediately if it is swallowing or masking defects – and itshould give you more confidence that the test results are valid. Sure, youmight hit some unpleasant surprises as the test code runs for the firsttime, but once you’ve ironed out any failures, you can be more confidentabout its overall quality.
What’s more, the addition of assertions alsoprotects your code against any regressions that may be introduced duringmaintenance and refactoring but which would not otherwise be pickedup by your test code. What more could you ask?The cases so far could be considered as ”self defense”, in that I’vediscussed using assertions to catch bugs in your code.
Let’s move on toconsider defensive programming in general. Defensive programming isnot about retorting ”It works OK on my machine” after being informedthat your code doesn’t work as expected. It’s based on defending yourcode against irresponsible use or downright abuse by code that callsit. Defensive code protects functions against invalid input, by inspectingdata passed in and rejecting corrupt or otherwise flawed parameters, suchas strings that are too long or out-of-range numerical values.You’ll need to consider how to handle bad parameters depending onhow your code is called; for example, you may want to assert that thedata is good, terminating with a panic if it is not. Alternatively, you maydecide to continue the flow of execution, so instead of assertions, you’llcheck each incoming parameter (e.g. using if statements) and returnto the caller if invalid data is detected – either with an error value or aleave code.
Another method would be to check incoming data and, ifa parameter is invalid, substitute it with a default parameter or continuewith the closest legal value. What you don’t want to do is ignore invalidinput and carry on regardless, since this could lead to problems lateron, such as data corruption. Whatever method you use to handle illegalinput, it should be consistent throughout your code.Your clients should be testing with debug versions of your librariesand thus you could use __ASSERT_DEBUG statements to alert them ofinvalid input, or other misuse, so they can correct it.−−−ASSERT ALWAYS−261__ASSERT_DEBUG assertions can be added early in the development process to highlight programming errors and can be left in tovalidate the code as it is maintained and refactored – acting as ameans to ”design by contract”.16.2−−−ASSERT− ALWAYSYou still need to be defensive by checking for illegal usage in releasebuilds too, but the case for using __ASSERT_ALWAYS isn’t clear cut.Remember, your assertions will terminate the flow of execution andpanic the library, displaying a nasty ”program closed” dialog to the user,which is generally best avoided where possible.