Defensive Programming Techniques Explained with Examples

What is Defensive Programming

Defensive Programming is the development of computer software putting in mind all the unforeseen circumstances that could raise problematic issues. That allows the software to behave correctly despite the input provided. This development technique is mainly utilized when coding software that should be highly available, safe, and secure from malicious attempts. Defensive Programming techniques help improve Software and Source code through:

  • Improving General Quality: Completely minimizes the number of bugs and problems that could arise with the code.
  • Developing Comprehensible Code: Source code written with defensive coding techniques is easy to read and understand. That makes it to be easily approved in a  code audit.
  • Developing software that will provide a correct output despite the input given.

 

Categories of Defensive Programming

There are two main categories of defensive programming:

Advertisement
  • Secure Programming
  • Offensive Programming

 

Secure Programming

Secure programming is a category of Defensive programming that emphasizes the development of highly secure programs. In such a case, the availability and safety of the software are not a priority. The software can fail in certain ways as long as it is secure from any malicious exploitation attempts. Like any defensive program, minimizing the number of bugs and any arising problems should be considered, but reducing the likelihood of the program failing is not a major concern compared to reducing the attack surface. The developer should have a mindset that the program can be misused or tricked into revealing bugs or security flaws that can be exploited.

Let's take a look at the piece of code below:

int firstSample(char *input) {
char str[1000];

//The rest of the code
strcpy(str, input);//Copy iput
//The rest of the code

}

In the above program, the developers set the user input to 1000 characters. If it was a username field, they did not imagine a user can enter more than 1000 characters. However, this is a bug that can be exploited using buffer overflow exploitation attacks. A reliable solution to this would be to use the code below:

Defensive Programming Explained with Examples

 

Offensive Programming

Normally, defensive programming emphasizes total Fault Tolerance. A situation where the software will continue working normally in the event of failure or an error arising within its components. Offensive Programming, however, takes a different approach on that.  According to offensive programming, the errors that should be handled defensively should come from outside the applications, such as user input. Errors arising from within the program itself should be exempted from total Fault Tolerance. There are two methodologies applied in Offensive Programming:

  • Trusting internal data validity
  • Trusting Software components

 

Let's take a look at the two methodologies using code samples:

Trusting internal data validity

Overly Defensive Programming

Advertisement
const char* ledLight_colorname(enum led_light_color c) {
switch (c) {
case LEDLIGHT_WHITE: return "white";
case LEDLIGHT_BLUE: return "blue";
case LEDLIGHT_PINK: return "pink";
}
return "black"; // handled as a dead LED light.
}

Offensive Programming Solution of the above code.

const char* ledLight_colorname(enum led_light_color c) {
switch (c) {
case LEDLIGHT_WHITE: return "white";
case LEDLIGHT_BLUE: return "blue";
case LEDLIGHT_PINK: return "pink";
}
assert(0); // Assert that this section is unreachable.
}

 

Trusting Software Components

Overly Defensive Programming

Defensive Programming Explained with Examples

Offensive Programming Solution of the above code.

Defensive Programming Explained with Examples

 

Defensive Programming Techniques

Up to this point, I believe you now have a good understanding of defensive programming. Let's dive in and look at three techniques you can use to write defensive source code.

 

Guard Clauses — Checking Pre-Conditions

The use of pre-conditions is the most common and widely utilized technique in Defensive Programming. These one-liner statements are used to validate your input, ensuring that the methods and functions continue to execute only when the correct input is provided. Let's take a look at the example below:

public Author? GetAuthorOrDefaultgyId(string id) {

if (string. IsNullOrEmpty(id)) return null;
// rest of the implementation...

}

In the above code, we check whether we are provided with a non-null or an empty value. This check is important in situations where proceeding with non-null or empty values could result in incorrect output.

When the method takes multiple arguments or one argument calls for multiple guard clauses, we will need to refactor to another guard clause approach. Instead of writing multiple checks, we can have one object that holds all our properties and the IsValid() method. Take a look at the piece of code below.

public Author GetAuthurBySecification(AuthorSpecification speolfIcation){
     if (! specification.IsValid()) return null;
// rest of the implementation
}

In the above code, the IsValid() function validates if every property meets the required requirements.

Advertisement

 

Assertions

Assertions are another important concept in Defensive Programing. They are used to confirm that the assumption made on the program's execution flow is correct by adding assert statements validated at runtime. In real-life software development, assertions come in handy when working with third-party modules and libraries you import into your project. You cannot fully trust these libraries, and it's important to check and confirm whether the output they give falls within the expected range.

Let's take a look at the example below:

public OpertionStatus SaveAuthor(Author author) { 
    if (author is null) return OpertionStatos.Failed;
    if (string.IsNullOrEmpty(anthcr.name)) return OpertionStatusSailed;
    OpertionStatus status = database.Save(author); 
    if (status = OpertionSfatus.Failed) {
        // log statements and whatever else you'd want to do return OpertionStafus.Failed;
        
    }
return OpertionStatus. Succeeded;

In the above code, we are anticipating any errors that may arise. After invoking the database.save() method, we have used a if statement to assert what happened and act accordingly.

 

Nulls

One of the many causes of bugs in Object Oriented Programing is Nulls. That is mainly due to the inability to distinguish between nullable and non-nullable reference types. To avoid these null bugs, most developers write multiple null checks in almost every method and constructor (Defensive Coding). See the sample code below.

private String getEmployeeStreet(Long employeeId) {
    Employee employee = findEmployeeById(employeeId); 
    if (employee != null) { 
          Address address = employee.getAddress();
          if (address != null) {
              String street = address.getStreet(); 
              if (street != null) { 
                   return street; 
              }
          }
       }
        return EMPTY; 56
}

Even though these checks can be essential, having so many of them can cause errors to slip through the programmer's eyes. To avoid using these many validations, there are various methods that you can apply.

One, you can decide to use a language that does not support any nulls at all. They include Haskell, Ruby, Smalltalk, and many more. Therefore, the problem of null references doesn't arise at all. Another related solution would be to use a language that does have null but supports null tracking to avoid any null references. One of such languages is Spec#.

If you are developing software using popular OOP languages like Java, you can define a special Struct that distinguishes nullable and non-nullable reference types. Additionally, you can also import the Fody.NullGuard library, which automatically checks for all input parameters that weren't marked with the struct.

 

Conclusion

Defensive programming can be tough to write source code, but it results in high-quality foolproof code. Without Defensive programming, your code will still run normally. However, it can easily break or give incorrect output depending on the condition or user input.

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment