SWEN 6301 Software Construction - Ahmed Tamrawi · The single most important reason to create a...
Transcript of SWEN 6301 Software Construction - Ahmed Tamrawi · The single most important reason to create a...
SWEN 6301 Software ConstructionLecture 4: Creating High-Quality Code
Ahmed Tamrawi
Copyright notice: 1- care has been taken to use only those web images deemed by the instructor to be in the public domain. If you see a copyrighted image on any slide and are the copyright owner, please contact the instructor. It will be removed.2- part of the slides are adopted from Mustafa Misir’s lecture notes on Modern Software Development Technology course..
Design in Construction
Creating High-Quality CodeWorking Classes High Quality Routines Defensive Programming
What is a routine?
A routine is an individual method or procedure invocable for a single purpose. Examples include a function in C++, a method in Java, a
function or sub procedure in Microsoft Visual Basic
What is a high-quality routine? That’s a harder question.
What is a high-quality routine?The routine has a bad name. HandleStuff() tells you nothing about what the routine does.
The routine isn’t documented.
The routine has a bad layout. The physical organization of the code on the page gives few hints about its logical organization.
The routine’s input variable, inputRec, is changed. If it’s an input variable, its value should not be modified
The routine reads and writes global variables—it reads from corpExpense and writes to profit . It should communicate with other routines more directly than by reading and writing global variables.
The routine doesn’t have a single purpose. It initializes some variables, writes to a database, does some calculations—none of which seem to be related to each other in any way. A routine should have a single, clearly defined purpose.
The routine doesn’t defend itself against bad data. If crntQtr equals 0 , the expression ytdRevenue * 4.0 / (double) crntQtr causes a divide-by-zero error.
The routine uses several magic numbers: 100 , 4.0, 12 , 2 , and 3 .
Some of the routine’s parameters are unused: screenX and screenY are not referenced within the routine.
One of the routine’s parameters is passed incorrectly: prevColor is labeled as a reference parameter (&) even though it isn’t assigned a value within the routine.
The routine has too many parameters. The upper limit for an understandable number of parameters is about 7
The routine’s parameters are poorly ordered and are not documented
Valid Reasons to Create a RoutineReduce Complexity
The single most important reason to create a routine is to reduce a program’s complexity. Create a routine to hide information so that you won’t need to think about it.
Other reasons to create routines: minimizing code size and improving maintainability and correctness
But without the abstractive power of routines, complex programs would be impossible to manage.
An indication that a routine needs to be broken out is loop deep nesting or a conditional
Valid Reasons to Create a RoutineIntroduce an intermediate, understandable abstractionPutting a section of code into a well-named routine is one of the best ways to document its purpose
leafName = GetLeafName( node )
Valid Reasons to Create a RoutineAvoid duplicate code
Undoubtedly the most popular reason for creating a routine is to avoid duplicate code.
Valid Reasons to Create a RoutineHide Sequences
It’s a good idea to hide the order in which events happen to be processed
For example, a sequence might be found when you have two lines of code that read the top of a stack and decrement a stackTopvariable.
Put those two lines of code into a PopStack() routine to hide the assumption about the order in which the two operations must be performed
Hiding that assumption will be better than baking it into code from one end of the system to the other.
Valid Reasons to Create a RoutineHide Pointer Operations
Pointer operations tend to be hard to read and error prone. By isolating them in routines, you can concentrate on the intent of the operation rather than on the mechanics of pointer manipulation
if the operations are done in only one place, you can be more certain that the code is correct. If you find a better data type than pointers, you can change the program without traumatizing the code that would have used the pointers.
Valid Reasons to Create a RoutineImprove portability
Use of routines isolates nonportable capabilities, explicitly identifyingand isolating future portability work.
Nonportable capabilities include nonstandard language features, hardware dependencies, operating-system dependencies, and so on.
Valid Reasons to Create a RoutineSimplify Complicated Boolean Tests
Understanding complicated boolean tests in detail is rarely necessary for understanding program flow.
Putting such a test into a function makes the code more readable because (1) the details of the test are out of the way and (2) a descriptive function name summarizes the purpose of the test.
Giving the test a function of its own emphasizes its significance. It encourages extra effort to make the details of the test readable inside its function.
Valid Reasons to Create a RoutineImprove Performance
You can optimize the code in one place instead of in several places.
Centralizing code into a routine means that a single optimization benefits all the code that uses that routine, whether it uses it directly or indirectly.
Having code in one place makes it practical to recode the routine with a more efficient algorithm or in a faster, more efficient language.
Operations That Seem Too Simple to Put Into Routines
Constructing a whole routine to contain two or three lines of code might seem like overkill, but experience shows how helpful a good
small routine can be.Small routines offer several advantages. One is that they improve readability.
Operations That Seem Too Simple to Put Into Routines
If that original line of code had still been in a dozen places, the test would have been repeated a dozen times, for a total of 36 new lines of code. A simple routine reduced the 36 new lines to 3.
Design at the Routine LevelCohesion Couplinghow closely the operations
in a routine are relatedthe relationships between
functions
Design at the Routine Level: CohesionSome programmers prefer the term “strength”; how strongly related are the operations in a routine
Cosine() CosineAndTan()A function like Cosine() is perfectly cohesive because the whole routine is dedicated to
performing one function.
A function like CosineAndTan() has lower cohesion because it tries to do more than one thing. The
goal is to have each routine do one thing well and not do anything else.
One study of 450 routines found that 50 percent of the highly cohesive routines were fault free, whereas only 18 percent of routines with low cohesion were fault free (Card, Church, and Agresti1986)
Another study of a different 450 routines (which is just an unusual coincidence) found that routines with the highest coupling-to-cohesion ratios had 7 times as many errors as those with the lowest coupling-to-cohesion ratios and were 20 times as costly to fix (Selby and Basili1991)
Design at the Routine Level: Desired CohesionFunctional Cohesion
Functional cohesion is the strongest and best kind of cohesion, occurring when a routine performs one and only one operation
• Compute Cosine of Angle• Verify Alphabetic Syntax• Read Transaction Record• Determine Customer Mortgage Repayment• Compute Point of Impact of Missile• Calculate Net Employee Salary• Assign Seat to Airline Customer
Design at the Routine Level: Acceptable CohesionSequential Cohesion
Sequential cohesion exists when a routine contains operations that must be performed in a specific order, that share data from step to step, and that don’t make up a complete function when done together.
For example, given a birth date, calculates an employee’s age and time to retirement.If the routine calculates the age and then uses that result to calculate the employee’s time to retirement, it has sequential cohesion.
Design at the Routine Level: Acceptable CohesionCommunicational Cohesion
Communicational cohesion occurs when operations in a routine make use of the same data and aren’t related in any other way.
For example, suppose you wrote a function to query a database to get the name and office number for an employee in your company.It may make sense for your application, but the only common point between the two operations is that the data comes from the same employee record.
Design at the Routine Level: Acceptable CohesionTemporal Cohesion
Temporal cohesion occurs when operations are combined into a routine because they are all done at the same time.
Some programmers consider temporal cohesion to be unacceptable because it’s sometimes associated with bad programming practices such as having a mixture of dissimilar code in a Startup() routine.
To avoid this problem, think of temporal routines as organizers of other events.
have the temporally cohesive routine call other routines to perform specific activities rather than performing the operations directly itself. But this raises the issue of choosing a name that describes the routine at the right level of abstraction
It will be clear that the point of the routine is to orchestrate activities rather than to do them directly.
Design at the Routine Level: Unacceptable CohesionProcedural Cohesion
Procedural cohesion occurs when operations in a routine are done in a specified order.
The routine has procedural cohesion because it puts a set of operations in a specified order and the operations don’t need to be combined for any other reason.
• Clean Utensils from Previous Meal• Prepare Chicken for Roasting• Make Phone Call• Take Shower• Chop Vegetables• Set Table
To achieve better cohesion, put the separate operations into their
own routines.
Design at the Routine Level: Unacceptable CohesionLogical Cohesion
Logical cohesion occurs when several operations are stuffed into the same routine and one of the operations is selected by a control flag that’s passed in.
The control flow or “logic” of the routine is the only thing that ties the operations together—they’re all in a big if statement or case statement together.
Design at the Routine Level: Unacceptable CohesionLogical Cohesion
Logical cohesion occurs when several operations are stuffed into the same routine and one of the operations is selected by a control flag that’s passed in.
It’s usually all right, to create a logically cohesive routine if its code consists solely of a series of if or case statements and calls to other routines.
if the routine’s only function is to dispatch commands and it doesn’t do any of the processing itself, that’s usually a good design.
The technical term for this kind of routine is “event handler” An event handler is often used in interactive environments such as the Windows
and Linux GUI environments.
Design at the Routine Level: Unacceptable CohesionCoincidental Cohesion
Coincidental cohesion occurs when the operations in a routine have no discernible relationship to each other
It’s hard to convert coincidental cohesion to any better kind of cohesion—you usually need to do a deeper redesign and reimplementation
• Fix Car• Bake Cake• Walk Dog• Fill our Astronaut-Application Form• Get out of Bed• Go the the Movies
Design at the Routine Level: Bad CouplingTight Coupling
Large dependence on the structure of one module by another.
Design at the Routine Level: Good CouplingLoose Coupling
Modules with loose coupling are more independent and easier to maintain
Design at the Routine Level: Worst CouplingContent Coupling
A module changes another module’s data
Design at the Routine Level: Not Worst CouplingCommon Coupling
This occurs when all modules reference the same global data structure
Design at the Routine Level: Not Worst CouplingExternal Coupling
Modules communicate through an external medium, such as files
Design at the Routine Level: Acceptable CouplingControl Coupling
Two modules exhibit control coupling if one (``module A'') passes to the other (``module B'') a piece of information that is intended to control the internal logic of the other.
Design at the Routine Level: Acceptable CouplingStamp Coupling
Two modules (``A'' and ``B'') exhibit stamp coupling if one passes directly to the other a ``composite'' piece of data-that is, a piece of data with meaningful internal structure -such as a record (or structure), array, or
(pointer to) a list or tree.
Design at the Routine Level: Ideal Coupling
Modules A and B have the lowest possible level of coupling -no coupling at all -if they have no direct communication and are also not ``tied together'' by shared access to the same global data area or external device.it implies that A and B be implemented, tested, and maintained (almost) completely independently; neither will affect
the behavior of the other
Good Routine NamesA good name for a routine clearly describes everything the routine does
Describe everything the routine does Avoid meaningless, vague, or wishy washy verbs
Don’t differentiate routine names solely by number Make names of routines as long as necessary
To name a function, use a description of the return value To name a procedure, use a strong verb followed by an object
Use opposites preciselyEstablish conventions for common operations
describe all the outputs and side effects. If a routine computes report totals and opens an output file, ComputeReportTotals() is not an adequate name for the routine. ComputeReportTotalsAndOpenOutputFile() is an adequate name but is too long and silly.
Some verbs are elastic, stretched to cover just about any meaning. Routine names like HandleCalculation() , PerformServices() , OutputUser() , ProcessInput() , and DealWithOutput() don’t tell you what the routines do.
describe all the outputs and side effects. If a routine computes report totals and opens an output file, ComputeReportTotals() is not an adequate name for the routine. ComputeReportTotalsAndOpenOutputFile() is an adequate name but is too long and silly.
A procedure with functional cohesion usually performs an operation on an object. The name should reflect what the procedure does, and an operation on an object implies a verb-plus object name.
In some systems, it’s important to distinguish among different kinds of operations. A naming convention is often the easiest and most reliable way of indicating these distinctions
How Long Can a Routine Be?
The theoretical best maximum length is often described as one screen or one or two pages of program listing, approximately 50 to 150 lines. In this spirit, IBM once limited routines to 50 lines, and TRW limited them to two pages (McCabe 1976)
A large percentage of routines in object-oriented programs will be accessor routines, which will be very short. From time to time, a complex algorithm will lead to a longer routine, and in those circumstances, the routine should be
allowed to grow organically up to 100–200 lines (A line is a non comment, nonblank line of source code).
How to Use Routine Parameters?
One often-cited study by Basiliand Perricone (1984) found that 39 percent of all errors were internal interface errors—errors in communication between routines.
Interfaces between routines are some of the most error-prone areas of a program
How to Use Routine Parameters?Put parameters in input-modify-output order
Instead of ordering parameters randomly or alphabetically, list the parameters that are input-only first, input-and-output second, and output-only third
How to Use Routine Parameters?If several routines use similar parameters, put the similar parameters in a consistent order
The order of routine parameters can be a mnemonic, and inconsistent order can make parameters hard to remember.
How to Use Routine Parameters?Use all the parameters
If you pass a parameter to a routine, use it. If you aren’t using it, remove the parameter from the routine interface.
Unused parameters are correlated with an increased error rate. In one study, 46 percent of routines with no unused variables had no errors, and only 17 to 29 percent of routines with more than one unreferenced variable had no errors (Card, Church, and Agresti1986).
How to Use Routine Parameters?Put status or error variables last
By convention, status variables and variables that indicate an error has occurred go last in the parameter list. They are
incidental to the main purpose of the routine, and they are output-only parameters, so it’s a sensible convention.
How to Use Routine Parameters?Don’t use routine parameters as working variables
It’s dangerous to use the parameters passed to a routine as working variables. Use local variables instead.
How to Use Routine Parameters?Document interface assumptions about parameters
If you assume the data being passed to your routine has certain characteristics, document the assumptions as you make them. Even better than commenting your assumptions, use assertions to put them into code
Whether parameters are input-only, modified, or
output-only
Units of numeric parameters (inches,
feet, meters, and so on)
Meanings of status codes and error values if enumerated
types aren’t used
Ranges of expected values
Specific values that should never appear
How to Use Routine Parameters?Limit the number of a routine’s parameters to about seven
Seven is a magic number for people’s comprehension
If you find yourself consistently passing more than a few arguments, the coupling among your routines is too tight. Design the routine or group of routines to reduce the
coupling. If you are passing the same data to many different routines, group the routines into a class and treat the frequently used data as class data.
How to Use Routine Parameters?Make sure actual parameters match formal parameters
Formal parameters, also known as “dummy parameters,” are the variables declared in a routine definition. Actual parameters are the variables, constants, or expressions used in the actual routine calls.
A common mistake is to put the wrong type of variable in a routine call
The most important reason for creating a routine is to improve the intellectual manageability of a program, and you can create a routine for
many other good reasons. Saving space is a minor reason; improved readability, reliability, and modifiability are better reasons.
Sometimes the operation that most benefits from being put into a routine of its own is a simple one.
You can classify routines into various kinds of cohesion, but you can make most routines functionally cohesive, which is best.
The name of a routine is an indication of its quality. If the name is bad and it’s accurate, the routine might be poorly designed. If the name is bad and
it’s inaccurate, it’s not telling you what the program does. Either way, a bad name means that the program needs to be changed.
Defensive ProgrammingThe idea is based on defensive driving. In defensive driving, you adopt the mind-set that you’re never sure what
the other drivers are going to do. That way, you make sure that if they do something dangerous you won’t be hurt. You take responsibility for protecting yourself even when it might be the other driver’s fault.
Defensive Programming
Part of the Interstate-90 floating bridge in Seattle sank during a storm because the flotation tanks were left uncovered, they filled with water, and
the bridge became too heavy to float. During construction, protecting yourself against the small stuff matters more than you might think.
Protecting Your Program from Invalid InputsIn school you might have heard the expression, “Garbage in, garbage out.” That expression is essentially
software development’s version of caveat emptor: let the user beware.
For production software, garbage in, garbage out isn’t good enough. A good program never puts
out garbage, regardless of what it takes in.
Check the values of all data from external sources
Check the values of all routine input parameters
Decide how to handle bad inputs
AssertionsAn assertion is code that’s used during development—usually a routine or macro—that allows a
program to check itself as it runs
When an assertion is true, that means everything is operating as expected. When it’s false, that means it has
detected an unexpected error in the code.
Assertions are especially useful in large, complicated programs and in high-reliability programs. They enable programmers to more quickly
flush out mismatched interface assumptions, errors that creep in when code is modified, and so on.
AssertionsAn assertion is code that’s used during development—usually a routine or macro—that allows a
program to check itself as it runs
An assertion usually takes two arguments: a Boolean expression that describes the assumption that’s supposed to be true, and a message to display if it isn’t.
AssertionsAn assertion is code that’s used during development—usually a routine or macro—that allows a
program to check itself as it runs
You use assertions primarily for debugging and identifying logic errors in an application. They are comment-like code
You must explicitly enable assertions when executing a program, because they reduce performance and are unnecessary for the program’s user.
Users should not encounter any Assertion Errors through normal execution of a properly written program. Such errors should only indicate bugs in the
implementation. E.g., Debug mode vs. Release mode
AssertionsAn assertion is code that’s used during development—usually a routine or macro—that allows a
program to check itself as it runs
• That an input parameter’s value falls within its expected range (or an output parameter’s value does)
• That a file or stream is open (or closed) when a routine begins executing (or when it ends executing)
• That a file or stream is at the beginning (or end) when a routine begins executing (or when it ends executing)
• That a file or stream is open for read-only, write-only, or both read and write• That the value of an input-only variable is not changed by a routine• That a pointer is non-null• That an array or other container passed into a routine can contain at least X number of
data elements• That a table has been initialized to contain real values• That a container is empty (or full) when a routine begins executing (or when it finishes)• That the results from a highly optimized, complicated routine match the results from a
slower but clearly written routine
Assertions: Guidelines for Using AssertionsUse error-handling code for conditions you expect to occur;
use assertions for conditions that should never occur
Assertions check for conditions that should never occur. Error-handling code checks for off-nominal circumstances that might not occur very often, but that have been anticipated by the programmer who wrote the code and that need to be handled by the production code. Error handling typically checks for bad
input data; assertions check for bugs in the code.
Assertions: Guidelines for Using AssertionsAvoid putting executable code into assertions
Putting code into an assertion raises the possibility that the compiler will eliminate the code when you turn off the assertions.
Assertions: Guidelines for Using AssertionsDo not use assertions for argument checking in public
methodsArgument checking is typically part of the published specifications (or contract) of a method, and
these specifications must be obeyed whether assertions are enabled or disabled
Erroneous arguments should result in an appropriate runtime exception (such as IllegalArgumentException,
IndexOutOfBoundsException, or NullPointerException)
Assertions: Guidelines for Using AssertionsUse Assertions for Internal Invariants
An invariant is a condition that can be relied upon to be true during execution of a program, or during some portion of it. It is a logical assertion that is held to always be true during a certain
phase of execution. For example, a loop invariant is a condition that is true at the beginning and end of every execution of a loop.
Assertions: Guidelines for Using AssertionsUse Assertions for Internal Invariants
An invariant is a condition that can be relied upon to be true during execution of a program, or during some portion of it. It is a logical assertion that is held to always be true during a certain
phase of execution. For example, a loop invariant is a condition that is true at the beginning and end of every execution of a loop.
Assumption: the suit variable will have one of only four values. To test this assumption, you should add the following default case:
Assertions: Guidelines for Using AssertionsUse Assertions for Control Flow Invariantsplace an assertion at any location you assume will not be reached
Assertions: Guidelines for Using AssertionsUse assertions to document and verify preconditions and postconditions
Preconditions are the properties that the client code of a routine or class promises will be true
before it calls the routine or instantiates the object. Preconditions are the client code’s
obligations to the code it calls.
Postconditions are the properties that the routine or class promises will be true when it concludes executing. Postconditions are the
routine’s or class’s obligations to the code that uses it.
If the variables latitude, longitude, and elevation were coming from an external source, invalid values should be checked and handled by error-handling code rather than by assertions.
Assertions: Guidelines for Using AssertionsFor highly robust code, assert and then handle the error anyway
Error-Handling TechniquesReturn a neutral value
Sometimes the best response to bad data is to continue operating and simply return a value that’s known to be harmless.
• A numeric computation might return 0.• A string operation might return an empty string, or a pointer operation might
return an empty pointer.• A drawing routine that gets a bad input value for color in a video game might
use the default background or foreground color.
Error-Handling TechniquesSubstitute the next piece of valid data
When processing a stream of data, some circumstances call for simply returning the next valid data.
If you’re reading records from a database and encounter a corrupted record, you might simply continue reading until you find a valid record.
If you’re taking readings from a thermometer 100 times per second and you don’t get a valid reading one time, you might simply wait another 1/100th of a second and take the next reading.
Error-Handling TechniquesSubstitute the closest legal value
In some cases, you might choose to return the closest legal value. This is often a reasonable approach when taking readings from a calibrated instrument
The thermometer might be calibrated between 0 and 100 degrees Celsius, for example. If you detect a reading less than 0, you can substitute 0, which is the closest legal value.
Cars use this approach to error handling whenever going back. Since a speedometer doesn’t show negative speeds, when it simply shows a speed of 0—the closest legal value.
Error-Handling TechniquesLog a warning message to a file
When bad data is detected, you might choose to log a warning message to a file and then continue on.
This approach can be used in conjunction with other techniques like substituting the closest legal value or substituting the next piece of valid data.
If you use a log, consider whether you can safely make it publicly available or whether you need to encrypt it or protect it some other way.
Error-Handling TechniquesReturn an Error Code
You could decide that only certain parts of a system will handle errors. Other parts will not handle errors locally; they will simply report that an error has
been detected and trust that some other routine higher up in the calling hierarchy will handle the error.
■ Set the value of a status variable■ Return status as the function’s return value■ Throw an exception by using the language’s built-in exception mechanism
Call an error-processing routine/objectCentralize error handling in a global error-handling routine or error-handling object.
Error-Handling TechniquesDisplay an error message wherever the error is encounteredThis approach minimizes error-handling overhead; however, it does have the potential to spread
user interface messages through the entire application-how to separate UI. Tight coupling
Beware of telling a potential attacker of the system too much. Attackers sometimes use error messages to
discover how to attack a system.
Error-Handling TechniquesShutdown
Some systems shut down whenever they detect an error. This approach is useful in safety-critical applications.
Error-Handling Techniques: Correctness vs. Robustness
Correctness means never returning an inaccurate result; returning no result is better than returning an inaccurate result.
Robustness means always trying to do something that will allow the software to keep operating, even if that leads to results that are inaccurate sometimes.
Safety-critical applications tend to favor correctness to robustness. It is better to return no
result than to return a wrong result. e.g. the radiation machine
Consumer applications tend to favor robustness to correctness. Any result whatsoever is usually
better than the software shutting down.
ExceptionsAn exception is an event, which occurs during the execution of a program, that
disrupts the normal flow of the program's instructions.
If code in one routine encounters an unexpected condition that it doesn’t know how to handle, it throws an exception, essentially throwing up its hands and yelling, “I don’t know what to do about this—I sure hope
somebody else knows how to handle it!”Code that has no sense of the context of an error can return control to other parts of the system that might have a better ability to interpret the error and do something useful about it.
ExceptionsAn exception is an event, which occurs during the execution of a program, that
disrupts the normal flow of the program's instructions.
Exceptions
The benefit of exceptions is their ability to signal error conditions in such a way that they cannot be ignored (Meyers 1996)
Use exceptions to notify other parts of the program about errors that should not be ignored
Other approaches to handling errors create the possibility that an error condition can propagate through a code base
undetected. Exceptions eliminate that possibility.
Exceptions
Exceptions should be reserved for conditions that are truly exceptional—in other words, for conditions that cannot be addressed by other coding practices
Throw an exception only for conditions that are truly exceptional
Exceptions represent a tradeoff between a powerful way to handle unexpected conditions on the one hand and increased
complexity on the other.
ExceptionsDon’t use an exception to pass the buck
If an error condition can be handled locally, handle it locally. Don’t throw an uncaught exception in a section of code if you
can handle the error locally.
The rules for how exceptions are processed become very complicated very quickly when exceptions are thrown in constructors and destructors.
Avoid throwing exceptions in constructors and destructors unless you catch them in the same place
Exceptions
A routine should present a consistent abstraction in its interface, and so should a class. The exceptions thrown are part of the routine interface, just like specific data types are.
Throw exceptions at the right level of abstraction
Exceptions
Be sure the message contains the information needed to understand why the exception was thrown.
Include in the exception message all information that led to the exception
If the exception was thrown because of an array index error, be sure the exception message includes the upper and lower array
limits and the value of the illegal index.
ExceptionsAvoid empty catch blocks
Either the code within the try block is wrong because it raises an exception for no reason, or the code within the catch block is wrong because it doesn’t handle a valid exception.
Know the exceptions your library code throwsIf you’re working in a language that doesn’t require a routine or class to define the exceptions
it throws, be sure you know what exceptions are thrown by any library code you use.
ExceptionsConsider building a centralized exception reporter
Exceptions provide the means to separate the details of what to do when something out of the ordinary. Error detection, reporting, and handling often lead to confusing spaghetti code
ExceptionsConsider building a centralized exception reporter
Exceptions provide the means to separate the details of what to do when something out of the ordinary. Error detection, reporting, and handling often lead to confusing spaghetti code
Barricade Your Program to Contain the Damage Caused by ErrorsBarricades are a damage-containment strategy. The reason is similar to that for having isolated compartments in
the hull of a ship.
Debugging AidsDon’t Automatically Apply Production Constraints to the Development Version
A common programmer blind spot is the assumption that limitations of the production software apply to the development version
Be willing to trade speed and resource usage during development in exchange for built-in tools that can make
development go more smoothly.
Introduce Debugging Aids EarlyThe earlier you introduce debugging aids, the more they’ll help
Debugging Aids
Use Offensive ProgrammingExceptional cases should be handled in a way that makes them obvious during development and
recoverable when production code is running
• Make sure assert/abort the program. Don’t allow programmers to get into the habit of
just hitting the Enter key to bypass a known problem. Make the problem painful enough
that it will be fixed.
• Completely fill any memory allocated so that you can detect memory allocation errors.
• Completely fill any files or streams allocated to flush out any file-format errors.
• Be sure the code in each case statement’s default or else clause fails hard (aborts the
program) or is otherwise impossible to overlook.
• Fill an object with junk data just before it’s deleted.
• Set up the program to e-mail error log files to yourself so that you can see the kinds of
errors that are occurring in the released software, if that’s appropriate for the kind of
software you’re developing.
Debugging AidsPlan to Remove Debugging Aids
If you’re writing code for your own use, it might be fine to leave all the debugging code in the program. If you’re writing code for commercial use, the performance penalty in size and speed can be prohibitive.
Use version-control tools and build tools like ant and make
Use a built-in preprocessor
Write your own preprocessor Use debugging stubs
Determining How Much Defensive Programming toLeave in Production Code
Leave in code that checks for important errors
Leave in code that helps the program crash gracefully
Log errors for your technical support personnel
Make sure that error messages you leave in are friendly
Being Defensive About Defensive Programming
Think about where you need to be defensive, and set your defensive programming priorities accordingly
Production code should handle errors in a more sophisticated way than “garbage in, garbage out.”
Defensive-programming techniques make errors easier to find, easier to fix, and less damaging to production code.
Assertions can help detect errors early, especially in large systems, high-reliability systems, and fast-changing code bases.
The decision about how to handle bad inputs is a key error-handling decision and a key high-level design decision.
Exceptions provide a means of handling errors that operates in a different dimension from the normal flow of the code. They are a valuable addition to the programmer’s intellectual toolbox when
used with care, and they should be weighed against other error-processing techniques
Constraints that apply to the production system do not necessarily apply to the development version. You can use that to your advantage, adding code to the development version that helps to
flush out errors quickly.