No matter how good you are, you will make mistakes. Probably more mistakes than you will ever know.
Accordingly, it is necessary to not only do what you can to not make mistakes, but also to rigorously test your code to confirm that you were successful.
You will still have bugs, though…
Our first line of defense will be unit tests: small, directed tests aimed to check whether or not the smallest units of our code are working correctly.
Ideally, our development process would be driven by our unit tests.
This concept is referred to as Test Driven Development (TDD).
Alternatively, we can restate these laws as the following list of steps:
If we do this well, then we know that all of the desired functionality has been tested, because, otherwise, it would not exist.
Also, we now have a safety net to protect us from regressions in the code base. If someone breaks our code, we should immediately know because of failing tests.
Unfortunately, it doesn’t always go so smoothly. For instance, we have to write our tests correctly, we have to test for unintended results as well as intended results (which is more difficult to do!), and, in some cases, we can’t write unit tests!
In many fields, safety relies on the “Swiss Cheese” model:
There will always be holes in any layer of safety that you implement, but if you have enough layers in places, it greatly decreases the odds that a hole will exist in the entire system.
So, we try to use our brains to make sure we right good code, and we try to write good tests. We will fail at both, but will hopefully come closer to eliminating bugs than if we didn’t test at all.
Another simple layer to add, which a lot of people overlook, is using the type system to model your problems.
Strong Static Typing can often give you similar benefits as unit testing.
In languages like Agda, Idris, Coq, you can model problems so completely that compilation “guarantees” correctness (sort of…), because invalid states can not be compiled.
In these languages, you are basically writing proofs.
This gives us:
To combat bugs in our code
So, our unit tests will allow us to ensure code performs as expected, to modify code without causing regressions, and empowers us to refactor our and improve our code without fear.
But how do we write unit tests…