Defensive Programming Without Examples

As mentioned, defensive programming is about small things. Some of the effects of defensive programming are rather subtle. For example, group related code with spaces. See the following code.

int number=0;
int factorial=0;
Console.WriteLine("Enter number:");
string snumber=Console.ReadLine();
number=int.Parse(snumber);
for(factorial=1;(number-1)>0;--number) {
   factorial*=number;
}
Console.WriteLine("Factorial is {0}.", factorial);

In the following code, spaces have been added. The resulting code is more readable. This change is both simple and subtle. Admittedly, the difference between the two versions is not dramatic. However, together all the details of defensive programming will dramatically improve the quality of your program.

int number=0;
int factorial=0;
Console.WriteLine("Enter number:");
string snumber=Console.ReadLine();
number=int.Parse(snumber);
for(factorial=1;(number-1)>0;--number) {
factorial*=number;
}

Console.WriteLine("Factorial is {0}.", factorial);

The following is a list of other points that should be considered for defensive programming. Most of this reflects common sense programming. Nonetheless, each item is worth mentioning. You might want to create a "to do" list, which is checked during code development.

  • At the start of a function, check parameters for correctness. Confirm that parameters fall within an acceptable range of values. Remember the conventional programming adage: "garbage in, garbage out."

  • After a series of related expressions, check the correctness of affected variables. A series of expressions can alter the state of the application. Confirm that the resulting state is correct.

  • When possible, resolve problems with error handling and not exception handling.

  • When possible, functions should return something—not void. Functions that return nothing cannot be verified with exception handling.

  • Always check the return value of functions. Regardless of how unlikely, do not assume that a function will not fail. Improper assumptions are a primary contributor to errors at run time.

  • When porting code, convert pointer algorithms to references. Pointers interfere with normal garbage collection. Defining an unsafe block and keeping the pointer algorithm may be quicker. However, spending time to remove the pointers will make the application safer.

  • Do not write unsafe code. Operations that require an unsafe block are inherently dangerous and should be avoided.

  • Do not use literals. Repeated use of a literal cannot be optimized. As shown earlier in this chapter, literals are not readable code. Use a const variable instead. Const variables are named, while literals are not. This makes the code more readable.

  • Except when switching on an enumeration, always add the default statement to a switch block. The worst case scenario is that the default case is not used, which is better than the alterative.

  • Do not catch the general exception type. Catching System.Exception masks the specific exception and origin of a problem. By catching any exception, you cannot respond to a specific exception.

  • Do not special case exceptions. To "special case exceptions" means to catch System.Exception and, in the exception handler, handle specific exceptions. The problem is that other exceptions are filtered and then not handled with unpredictable results.

  • Avoid defining or using object types. Using object types may unintentionally cause boxing or downcasting to a specific type. Boxing and downcasting adversely affect the performance of your application.

  • Repetitive code is the breeding ground of future problems. Refactor code into reusable blocks or functions.

  • Always used generic collections instead of standard collections. Standard collections are collections of object types, which cause boxing. Boxing is expensive.

  • Use arrays when possible and not collections. Arrays may be old-fashioned when compared with vectors, link lists, dictionaries, and so on. However, arrays are simple and typically quicker than a collection. Less complexity means fewer problems.

  • Do not use a native application programming interface (API) for functionality that exists in the .NET Framework Class Library (FCL).

  • The for statement is preferred to other types of loops. For loops are structured and enforce programmer discipline. That structure prevents inadvertent errors. While and similar loops are flexible, but that openness can foster unattended errors.

  • Unused objects should be set to null. You can then reliably check the status of the object. This also helps with garbage collection.

Defensive Programming with Examples

Avoid stupid complexity. Every team has a developer who specializes in writing code that is unreadable. If it looks like a hack, it probably should be rewritten. This was mentioned earlier, but it is worth repeating. Needlessly complicated code is more prone to have bugs, is harder to maintain, and is most often unreadable. Importantly, complex code does not generally run any quicker than the simpler version. Look at this code. It calculates a factorial within a single line of source code.

for(result=factorial;--factorial-1;result*=factorial);

The following example is simpler. It is several source lines, but it is more readable and just as effective.

for(result=1;number<factorial+1;++number)
{
    result=result*number;
}

When you have the option, always use a block and not a single statement. This prevents a common mistake of adding a statement later that should be part of a block but is not. Here is an error waiting to happen:

for(int var=0;var<10;++var)
    ++locala;

This easily becomes the following code, which is an error. The increment of localb, which should be controlled by the for statement, is outside the for loop.

for(int var=0;var<10;++var)
    ++locala;
    ++localb;

This simple change would have avoided the problem.

for(int var=0;var<10;++var)
{
    ++locala;
}

Decompose complex expressions. Separate a complex expression into individual expressions. If there is a problem in the following code, can you identify where?

double var=(((product+(product*priceDiscount))*(1+taxes))+(freight*units))*(1+regionCost);

This is more readable, and errors, if any, are more transparent.

double var=0 ;
var=product+(product*priceDiscount);
var=var*(1+taxes);
var=var+(freight*units);
var=var*(1+regionCost);

Parentheses add clarity to code. Look at the following equation. I often teach programming classes. In some of those classes, students are asked to manually calculate the result to the following equation. There are four common answers, in order of popularity: 110, 100, 101, and 11.

int i=10*10+10/10;

The correct answer is 101. The problem is that not everyone knows the order of precedence for operators. Parentheses can add clarity even when not changing the result. I am sure most everyone could obtain the correct answer from the following equation. The only difference is the added parentheses.

int i=(10*10)+(10/10);

Adhere to the guidelines of the Common Language Specification (CLS). This is especially important if you are writing code to be used by other developers. Case sensitive names would be an example of this. In the CLS, there is a specification about avoiding case sensitive names. The reason is that some .NET languages are case insensitive. The following code works perfectly in C#. This code is admittedly contrived but does convey the problem. Notice that there are separate funca and FuncA functions. The names are case sensitive. However, in a case insensitive language, these names would be ambiguous. For example, Visual Basic .NET is a case insensitive language.

public class XClass{
    public void funca(){
        // code
    }
    public void FuncA(){
        // code
    }
}

The preceding code is published as a library. It can then be referenced from another project, such as a Visual Basic .NET project. In that project, you could create an instance of the XClass type. However, neither FuncA method would be visible. Because the functions are ambiguous in Visual Basic .NET, both functions are not available. Your telephone is ringing because that Visual Basic developer is calling you at this very moment. For them, this is a bug—a bug that could have been avoided by following the guidelines of the CLS.

Many types expose properties that define the extents of that type. For primitive types, these are the MinValue and MaxValue properties. Use these properties in error checking to protect from overflows and underflows. This is less expensive than handling an exception. Look at the following code. The IntegerMultiply function calculates the product of two variables. The result is passed out as an integer variable, which is a parameter of the function call. The Int. MaxValue property checks for an overflow in the calculation before returning the result.

public static bool IntegerMultiply(int val1, int val2, out int result) {
    bool resp = int.MaxValue > (((long)val1)*val2);
    if (resp) {
        result = val1 * val2;
    }
    else{
        result = 0;
    }
    return resp;
}

Loops should be refactored. Expensive operations must be removed from the loop. Actions, such as unnecessary input/output, can throttle the performance of an application. Unnecessary boxing in a loop is another example of something that should be removed. This can fragment the heap, exert memory pressure, and cause premature memory collection, which is expensive. For boxing, box the value above the loop or create a wrapper class, which is a reference type. Look at the following code. In the loop, boxing occurs at the assignment to reftype. This means boxing will occur 1,000 times.

int number = 5;
object reftype;
for (int a=0; a < 1000; ++a ) {
     reftype = number;

     // do something with referencetype
}

The following code is modified to avoid the unnecessary boxing. The number variable is boxed before the loop. Therefore, boxing occurs only once instead of 1,000 times. This is considerably more efficient.

int number = 5;
object value = number;
object reftype;
for (int a=0; a < 1000; ++a ){
     reftype = value;

     // do something with referencetype
}

For defensive programming, enumerations are better than open integers. Assigning an invalid value to an enumeration is automatically detected at compile time and not run time. You do not have to write the logic to detect the invalid value. It is automatic. Filtering invalid integral values occurs at run time and relies on code logic and programming discipline. This code uses an integral value in the switch block. The default statement protects from invalid values. However, the default statement is executed at run time.

switch (value) {
    case 0:
        break;
    case 1:
        break;
    case 2:
        break;
    default:
}

This code uses an enumeration. Notice that the default statement is omitted. Why? Because an enumeration represents a discrete set of values, the default statement is not needed.

An invalid value is trapped at compile time. This is another example of employing defensie programming.

static void Main(string[] args) {
     classgrade grade=classgrade.fail;
     switch (grade){
         case classgrade.fail:
             break;
         case classgrade.pass:
             break;
         case classgrade.incomplete:
             break;
     }
   }

Avoid needless recursion. Yes, it is fun. However, recursion is expensive, which is amplified for small functions. Internally, each function has a function call site, prolog, and epilog. This infrastructure adds overhead to the function. For short functions, the execution of this infrastructure may exceed the code of the actual function. Finally, recursive functions are inherently more complex, which can lead to inadvertent errors. The following is a recursive function that calculates a factorial.

static public long Factorial(long val1) {
    if (val1 > 1) {
        val1 *= Factorial(--val1);
    }
    return val1;
}

This code also calculates a factorial. Instead of using a recursive function, a simple for loop is used. Both approaches were benchmarked. The for loop is about 50 percent quicker.

for (factorial = currentvalue; --currentvalue-1 > 0; factorial *= i) ;
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.170.65