Clear defensive programming with Go using Verifier library

By Bogdan Storozhuk

Some software has higher than usual requirements for availability, safety or security. Very often in such projects, people practice pragmatic paranoia and coding style called Defensive Programming.

The idea behind this approach is that your code should behave in a consistent and predictable manner even in the case of unexpected conditions. In practice, you will defend against the impossible, because things become possible when the outer world is changing: new people join the team, the code goes into maintenance, dependencies upgrade… Actually, humans make anything possible when it comes to errors.

Jim Bird has gathered a set of rules on how to employ Defensive Programming in your codebase:

  1. Protect your code from invalid data coming from outside, wherever you decide outside is. External systems, files, or any call from outside of the module/component. Establish “trust boundaries” — everything outside of the boundary is dangerous, everything inside of the boundary is safe. In the barricade code, validate all input data.
  2. After you have checked for bad data, decide how to handle it. Defensive Programming is NOT about swallowing errors or hiding bugs. Choose a strategy to deal with bad data: return an error and stop right away (fast fail), return a neutral value, substitute data values… Make sure that the strategy is clear and consistent.
  3. Don’t assume that a function call or method call outside of your code will work as advertised. Make sure that you understand and test error handling around external APIs and libraries.
  4. Use assertions to document assumptions and to highlight “impossible” conditions. This is especially important in large systems that have been maintained by different people over time, or in high-reliability code.
  5. Add diagnostic code, logging and tracing intelligently to help explain what’s going on at run-time, especially if you run into a problem.
  6. Standardize error handling. Decide how to handle “normal errors” or “expected errors” and warnings, and do all of this consistently.

Such rules really work and help people to craft reliable software, but when you use these approaches, your code can quickly become a mess. Sometimes it’s hard to distinguish between verification code and underlying business logic. Check out this bunch of verification performed before transfer execution:

I think we all can agree that these lines are repetitive and even error-prone to some degree. Error handling is error-prone 😏. But we can make it better using verifier library:

Verifier is built on error handling pattern described by Rob Pike in Go blog called Errors are values. This library is transparent and unopinionated. It is not here to handle errors for you. It won’t force you to any error handling style, you will be in control and decide what is better for your project.