The Art of Defensive Programming

Arunarka Mukhopadhyay
3 min readSep 8, 2023

--

Programming, at its core, is about creating solutions that not only solve the task at hand but also stand the test of time, unpredictable user input, and unforeseen circumstances. Defensive programming is a coding practice where the developer anticipates possible problems before they occur, ensuring robustness and stability. This article delves into this art and illustrates ways to make your code more resilient and foolproof.

1. Anticipating Bad Input

Regardless of how clear and comprehensive your user instructions are, you’ll always have instances where users enter unexpected or wrong data. Defensively written code must anticipate and handle these scenarios.

Tips for guarding against bad input:

  • Input Validation: Every time you accept input from a user, validate it. This includes checking for null values, ensuring the correct data type, and verifying the data’s range.
  • Regular Expressions: Use regex to ensure that strings match a specific pattern, especially when dealing with emails, phone numbers, and passwords.
  • Whitelisting vs. Blacklisting: It’s easier and safer to specify what’s allowed (whitelisting) than to list all potential threats (blacklisting).

2. Graceful Failure

Even the most diligently written programs can encounter unexpected issues. Defensive programming acknowledges this and ensures that when a failure occurs, it’s handled gracefully.

Guidelines for graceful failure:

  • Exception Handling: Always have exception handlers to manage unexpected errors. This will allow the program to decide the next steps instead of just crashing.
  • Custom Error Messages: Offer user-friendly error messages that provide guidance on what went wrong and possibly how to correct it. Avoid exposing raw error messages that could reveal sensitive system information.
  • Fallback Procedures: For critical operations, have backup procedures in place. For example, if a database connection fails, you might queue the data for later processing or store it locally.

3. Code Robustness through Self-checks

A defensively written program continually verifies its own health.

Implementing code self-checks:

  • Assertions: Use assertions to validate the program’s state. They’re useful during development, making it clear when and where an assumption was violated.
  • Logging: Regularly log critical parts of your system. This way, if something goes wrong, you can trace back the events leading up to the failure.

4. Avoid Hard Crashes

Crashing is a severe response to an error. Sometimes, it’s unavoidable, but your program should always attempt to handle the error and proceed or fail elegantly.

Strategies to prevent hard crashes:

  • Timeouts: If your code calls external services or waits for resources, always use timeouts. This ensures your code doesn’t hang indefinitely.
  • Resource Existence Checks: Before accessing files, databases, or other resources, verify their existence and availability.
  • Memory Management: Keep a vigilant watch on memory usage, ensuring there are no memory leaks which can lead to system slowdowns or crashes over time.

5. Keep It Simple and Transparent

Complexity is the enemy of defensive programming. The more complex your code, the harder it becomes to anticipate all possible issues.

  • Code Reviews: Regularly review your code. Fresh eyes often spot potential problems that the original developer missed.
  • Documentation: Keep your codebase well-documented. Explain why certain defensive measures are in place. This makes it easier for other developers to maintain and expand upon your code.

In conclusion, defensive programming is more than just a technique; it’s a mindset. By anticipating potential issues, validating input, ensuring graceful failures, and maintaining simplicity, you safeguard your application against numerous pitfalls. It might require more effort initially, but the stability, security, and robustness benefits it offers make it a practice every developer should embrace.

--

--