The art of defensive programming

Original author: Diego Mariani
  • Transfer
image

Why can't developers write safe code? We are not talking here again about “clean code” . We are talking about more from a purely practical point of view - the reliability and security of software. Yes, because unsafe software is pretty much useless. Let's see what “unsafe” software means:

  • Flight No. 501 of the Arian-5 rocket of the European Space Agency was terminated 40 seconds after launch (June 4, 1996). An experimental $ 1 billion prototype missile self-destructed due to an error in the on-board control software.
  • A mistake in the program that controlled the Therac-25 radiation therapy unit was the direct cause of the death of at least five patients in the 80s when she asked for excessive doses of x-ray radiation.
  • A software error in the MIM-104 Patriot anti-aircraft missile system , causing his system clock to go one third of a second in a hundred hours, led to his inability to detect and intercept a flying missile. An Iraqi missile hit a military unit in Dahran, Saudi Arabia (February 25, 1991), 28 Americans died .

This should be enough to understand how important it is to write safe and working software, especially for certain applications. But in other cases of use, we must know what our software errors can lead to.

The first acquaintance with defensive programming (design of self-testing and self-correcting programs)


Why do I think defensive (defensive) programming is a good approach to solve these problems in a certain kind of project?
Protect yourself from the impossible, because it will happen.
There are many definitions for the term “defensive (defensive) programming”; they depend on the level of "security" and the level of resources required for your software projects.
Defensive programming is a form of defensive (defensive) design designed to ensure the uninterrupted operation of some software in unforeseen circumstances. Security programming techniques are often used where high availability, security, and reliability ( Wikipedia ) are required .
I personally think that this approach is advisable when there is a large and long-lived project in which many people are involved. It is also suitable, for example, for such an open source project that requires significant and ongoing maintenance.

Let’s take a look at some of my simplified key points aimed at achieving a defensive programming approach.

Never trust user input


Assume always that you can get what you do not expect. This should be your approach as a defensive programming programmer with respect to user input or, in general, everything that enters your system. Because, as mentioned above, the impossible is possible. Try to be as strict as possible. Prove that your input values ​​are what you expect.

image

The best defense is an attack.

Make white lists, not blacklists, for example, when checking for image expansion, do not check for invalid types, but check for valid types, excluding everything else. For example, in the PHP language, you also have many open-source test libraries to simplify your work.

Best defense is attack. Be strict.

Use an abstract database view


The first of the top 10 software vulnerabilities according to OWASP are injections. This means that someone (or many people) has not yet used secure tools to access their databases. Use packages and libraries for abstract database representations. In PHP, you can use PDO (portable distributed object) to provide basic protection against injection .

Don't reinvent the wheel


You do not use a framework (or microframework)? Well, then you like to do extra work without any need, congratulations! This applies not only to frameworks, but also to many other new features, where you can easily use what has already been done, well tested, which thousands of developers trust and that work stably ; it’s not worthwhile here to engage in a demonstration of your skills only out of love for it. The only reason to do something yourself is to get something that does not exist or that exists but does not meet your requirements (poor performance, missing characteristics, etc.).

This is what is called smart code reuse . Take advantage of this opportunity.

Do not trust the developers


Defensive programming is akin to what motorists call "cautious" driving . With “careful” driving, we assume that everyone around us may act inappropriately. Thus, we must be careful, carefully monitoring the behavior of others. The same applies to defensive programming, when we as developers must not trust the code of other developers . We should not trust our own code.

In large projects involving many people, there can be many different ways to write and organize programs. This can be confusing and cause errors. That is why we must apply development styles and a code quality controller that simplify our lives.

Write a RELIABLE code


It is difficult for a (protective) programmer to write proper code . Many people know and talk about this, but in reality few people care or pay enough attention and effort to get a RELIABLE code .

Let's look at a few examples of the inappropriate approach.

Do not do this: uninitialized properties

transaction->process($to, $amount, $this->currency);
    }
}
// I forgot to call $bankAccount->setCurrency('GBP');
$bankAccount->payTo($joe, 100);

In this case, you must remember that to make a payment, you must first call setCurrency . This is a really bad approach - a state change operation, such as the one indicated (making a payment), should not be carried out in two stages using two (or more) public methods. You can have many methods of making a payment, but we must have only one simple public method for changing status (categorically it is unacceptable to find objects in an undefined state) .

In this case, we did even better by encapsulating an uninitialized property in the Money object :

payTo($joe, new Money(100, new Currency('GBP')));

Protect the program from improper handling. Do not use uninitialized object properties.

Don’t do this: leaking state outside the scope of the class

content = $content;
    }
}
class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage(){
        var_dump($this->message);
    }
}
$message = new Message();
$message->setContent("bob message");
$joeMailer = new Mailer($message);
$message->setContent("joe message");
$bobMailer = new Mailer($message);
$joeMailer->sendMessage();
$bobMailer->sendMessage();

In this case, Message is transmitted by reference, and the result will be “joe message” in both cases . The solution would be to clone the message object in the Mailer constructor. But what we should always try to do is use a ( immutable ) value object instead of a simple mutable Message object . Use immutable objects whenever you can.

content = $content;
    }
}
class Mailer
{
    protected $message;
    public function __construct(Message $message)
    {
        $this->message = $message;
    }
    public function sendMessage(
    {
        var_dump($this->message);
    }
}
$joeMailer = new Mailer(new Message("bob message"));
$bobMailer = new Mailer(new Message("joe message"));
$joeMailer->sendMessage();
$bobMailer->sendMessage();

Write tests


Is it really necessary to talk about this yet? Writing unit tests will help you to adhere to general principles such as strong connectivity, personal responsibility, weak binding, and proper composition of objects . This will help to test not only a working separate small module, but also a way to structure your objects. Indeed, you will clearly see, testing your small functions, how many modules you need to test and how many objects you need to simulate in order to get 100% coverage with code tests.

conclusions


I hope you enjoyed the article. Remember that here are only offers. You decide when and where to apply them and whether to apply them at all.

Thanks for reading!

Also popular now: