How-To Write Bug Free and Defensive Code

Posted by JD 12/23/2011 at 03:00

Sometimes when I look at other people’s code, I cringe. They do not write defensive code, code that prevents common mistakes and helps us find programming errors sooner than later.

  • Some of these recommendations will slow down your code.
  • Some will look funny.
  • Others were hard learned.
  • You will still make mistakes and you will still introduce bugs, but at least these will not be trivial in their nature.

Reading Best Practice guides for your language of choice is always a good idea too. Many programming languages have tools that help recommend best practices or point out common mistakes – things that are often coded improperly for the intended use. Lint and Perl::Critic are examples.

Look for design patterns and follow them where reasonable. Design patterns will help you avoid mistakes made by others over the years. Learn from mistakes that others have made.

Initialize All Variables to Known Values, Always.

int c;  // This is wrong.
int c=0; // This is correct.  

Variables that are not initialized are usually given random values from whatever the memory had previously. A few languages will automatically initialize all program memory to zeros, but this is compiler and language specific. Finding improperly set or initialized variable bugs is hard. Why risk it?

At a recent PerlMonger meeting, one of the extreme experts had a bug in his code because something wasn’t initialized. This guy is extremely well know as a perl expert, yet he made this same mistake just like someone who started programming last week might. Some programmers will balk at this suggestion and say it wastes CPU. Compared to programmer time, CPU and RAM is cheap.

It is easy to initialize all variables. Accept that you are not smarter than everyone else and do it.

Set Pointers to Known Values Always

Known values are either a good location in memory or NULL if a pointer isn’t currently being used.

 char *str = NULL; // this is correct.

free(str);
str = NULL;

This prevents accidental use of free’d memory. Your program will crash with a null-pointer exception while still in development. This is part of defensive programming. I’d rather have a program crash during development phases than in front of an end-user. Wild pointers are extremely difficult to debug and find. Proper initialization and management of pointers is really simple if you always kknow where the pointer is pointing.

Perform Comparisons with the Static Variable on the Left-Hand Side

This prevents accidental assignment of a value into a variable. It sounds really easy, but this bug happens all the time.

if ( 3.14 == pi )

Simple. Effective. No mistaken accidental assignments.

Avoid Long Blocks of Code

This seems like a waste, especially if you are just doing straight-line coding. Still, the best practice is to keep every function or OO method less than 20 lines of code. Doing this will ensure a human can understand what the code intends. Breaking up a 50 line module into 5 functions of 10 lines each seems like a complete waste of time, until you do it and uncover 5 bugs. Long functions introduce errors and make the code look sloppy.

When I worked on CMMI level 5 systems – ZERO bugs were acceptable – we used statistics to help determine the risk of modifying any module. We learned that certain modules were likely to hide existing bugs and that touching any line of code in those modules would
a) introduce a new, critical bug or
b) uncover an old, hidden bug.
The most likely module with this issue was the ascent executive flight control module. It was large, complex and ugly. Touching anything in that code was likely to get a critical DR, discrepency report, assigned to you. Nobody wanted to touch that module. It didn’t matter if you were new to the team or had been programming there for 15 yrs. A bug was likely.

The same reasoning works for your code too. Complex code will have bugs. The easiest way to keep your code simple is to limit the length.

Every small function can be understood easily and well-tested. A well-tested function will handle bad inputs better. It also means that fewer bugs per function/method will occur. That’s an ideal situation. Simple code. Well tested code.

Validate All Inputs to All Functions

When you pass parameters into any function, any bad values should be rejected immediately. That means your code needs to validate all inputs in some way for reasonableness.

With object oriented code, you can validate inputs at the class level. I code a Valitate() method for every class which performs validation of all data members in the class. The Set() methods for data should be your gateway and prevent bad data from being stored. With class data, by definition all the data has been validated for reasonable values. Defensive code.

Use Version Control, Always

Not using version control shows immaturity in a coder. Everyone makes mistakes, version control lets you roll back those mistakes.

Backup the version control DB at least daily. Data corruption of a VCS database sucks. Trust me.

Storing the versions on another machine is better than keeping everything local, but it is not a backup.

Test Early and Often, Automatically

Make testing part of your process. Write test cases constantly. Some people will say to create the test cases before you write the code. I guess if you know exactly what the fuction will be, then that makes sense. Often, I write functions without a clear idea of the required output when I begin. I also refactor functions and split a long function into multiple, smaller functions. Each of these new functions need to be tested approprately.

Automatic testing is important too. We all know about the MVC model. If you place too much code into the view or gui area, then automatic testing becomes more difficult. Try to keep the code in the view to a minimum to help with automatic testing of modules, functions and methods. Testing GUIs usually means that some tool needs to be used. If you can validate that the underlying app code works as desired, that means GUI testing won’t be as complex.

Test Good and Bad Inputs

When you write your test cases, pay specific attention to boundary values for inputs. Your code and tests should handle those cleanly.

Any input from a user must be validated. Do not trust the users. Expect wild values and incorrect data types. Test for these.

Port Early and Often

If you are planning to run the code on more than 1 platform or OS version, port the code to all the other platforms as early in the development cycle as possible. Run the test cases on all platforms too. This shouldn’t be a surprise recommendation to anyone. Finding and fixing a small issue just after it was introduced is much easier than coming back a week or a month later on a different platform and discovering it again. Compilers do not behave the same on all platforms. Sometimes even core language constructs don’t work as expected so a complete redesign is needed. Knowing that before you’ve built 10 other classes on top of the main class is important.

Daily porting and daily test validation is best. I actually prefer that porting and validation be performed on all platforms prior to checking the code into the version control system. However, another best practice is to always pull code from the VCS to perform compiles on other systems. Conflicting requirements mean that the lesser evil of the choices need to be decided. Consistency is important.

Enforce Coding Style

Use a tool to force all the code for a project to look the same. Perl::Tidy, indent or indent++ are such tools. Customize the configuration to the liking of the team and stick with it. Code needs to look the same regardless of who develops it. Consistently formated code means dumb mistakes aren’t hidden.

Determine Coding Standards and Follow Them

Your team should work to create coding standards for the team. These are useful to prevent the most common mistakes and to increase the total knowledge of the entire team. Code standards are an efficient way to train new programmers in the way that your team works. Standards about formating, names variables, classes, methods, static functions, initialization, debugging methods are examples. The team needs to accept input from pretty much everyone, but decide to try something for a period and then decide whether it will be a standard going forward.

  • Variable naming conventions
  • Variable and class Capitalization
  • Tags like :TODO:, :BUG:, :COMPLEXE: are examples.
  • Exception style
  • Return values or Return Codes
  • New ideas from the team members

Summary

Writing good code isn’t something you learn overnight. It takes time, usually years of practice. Writing defensive code is a skill that must be learned too, but in the end, it makes you a more efficient developer.