No code is perfect; thus, no software is perfect. Some mistakes are simple code inefficiencies, while others can have disastrous effects on security, stability or quality -- but it's not easy to banish bad programming habits.
What often separates good programming from bad programming isn't skill, it's guardrails. Organizations can impose standards and limits for how developers work, but the most effective limits are the ones developers place on themselves.
The 20th anniversary edition of The Pragmatic Programmer, by David Thomas and Andrew Hunt, explores ways for developers to approach their work effectively and efficiently. Updates to the book not only cover ways tools can aid programmers, but also how to fundamentally adapt coding habits, methods and expectations to release quality software at a demanding cadence.
Chapter 4 of the book, titled Pragmatic Paranoia, gets into ways to apply defensive programming techniques, which are logical guidelines to write application code. Defensive programming is an approach wherein the programmer assumes he is capable of mistakes, and therefore can apply the proper practices to produce higher-quality code.
The authors recommend developers follow these five defensive programing techniques: design by contract, respect that dead programs tell no lies, implement assertive programming, learn how to balance resources and don't outrun your headlights.
Defensive programming is good programming
With design by contract (DBC), a term programming consultant Bertrand Meyer coined, organizations should define agreements for how software modules interact: Just as people agree on contracts, so too should systems. DBC aims to ensure the program works in all cases, and everyone validates data in that approach.
The design technique posits that a program should do no more or less than it claims to do, as defined by preconditions, postconditions and class invariants the organization details in the programming language. In this approach, any time a program fails to adhere to a contract, it results in a bug.
Some programming languages, such as the dynamic languages Clojure and Elixir, offer automatic contract checks and support for conditions. If developers work with a language that lacks DBC support, they can still adhere to the concept with the use of unit tests or comments in the code.
Dead programs tell no lies is a way to say: Detect problems as soon as possible in the program, then have it crash rather than continue to run with issues. As the authors point out in The Pragmatic Programmer, errors result in compromised applications:
"[W]hen your code discovers that something that was supposed to be impossible just happened, your program is no longer viable. Anything it does from this point forward becomes suspect, so terminate it as soon as possible. A dead program normally does a lot less damage than a crippled one."
Developers can use assertive programming to verify assumptions as they write code. The authors recommend that programmers leave assertions in place when they deliver a program, rather than follow the common practice of removing them upon delivery. Assertions aim to prevent seemingly impossible things from happening -- though they can't replace error handling. Assertions, while useful, can cause side effects, such as Heisenbugs -- bugs that disappear or alter behavior when a developer or tester tries to study them.
Thomas and Hunt explain another defensive programming practice is to accept responsibility for resources such as files, memory and devices. The person who allocates a resource should be the one to deallocate it. For example, if a developer accesses a file within code, she should close it within the same routine. Object-oriented programming languages enable developers to encapsulate resources and classes, or they can use code wrappers to check for details like resource state. When in doubt, a defensive programmer should build code to check that the software handles resources appropriately.
Finally, the authors recommend that good programmers take small, deliberate steps rather than risk exceeding the rate of feedback via unit tests and user feedback. Thomas and Hunt recommend that programmers avoid project completion and maintenance timetables that extend months into the future, and eschew guessing at users' future needs, or technology availability.
While a programmer can reasonably predict some things, it's all too easy to fall into the trap of an assumption.
"Much of the time, tomorrow looks a lot like today. But don't count on it."
Editor's note: This excerpt is from the 20th anniversary edition of The Pragmatic Programmer, authored by David Thomas and Andrew Hunt, published with Pearson's Addison-Wesley imprint, October 28, 2019, ISBN: 978-0-13-595705-9, which is listed here.
Check out other books that will improve code and test quality, whether you're faced with product quality issues, ready to advance your career in software testing or looking for guidance from DevOps examples.