Developer tools: How to use assertions effectively

Written by: on September 24, 2015

A Case for Assertions

We’ve all heard the phrase “with great power comes great responsibility.” Assertions are just that: a powerful tool for software engineers to document procedures, isolate issues when they inevitably arise and identify potential problems early. The key is to know how to use them responsibly and effectively. This article will cover what assertions are, and when and why to use them.

Even if you haven’t used assertions, chances are you’ve run into them before. For example, some language libraries throw out-of-bounds exceptions that crash your software. The authors of these standard libraries have decided that your software shouldn’t fail silently. Instead, they’ve decided to communicate a developer’s error by crashing the software before something goes horribly wrong. But, what if you don’t want the software to crash on the end-user? I’ll cover that herein.

Over the last couple of years, I’ve experimented with assertions in a variety of projects. I’ve enjoyed incorporating assertions into my development methodology. I’d be lying if I said I don’t write any bugs, but I certainly write less. Admittedly, the use of assertions is a contested subject and is by no means the only approach to development. However, it’s a valuable tool to know how to use when the time is right.

What Are Assertions?

An assertion lets you define an assumption up front using a conditional statement that must evaluate as true. If an assertion is violated during execution, your software does the worst thing it can: it crashes. In short, they can provide insight into potential problems sooner than later, document the intention of your code and encourage you to lay out your assumptions for a segment of code.

How Assertions Help Identify Problems

There’s something magical about being handed a crash report and isolating the problem within the same breath even if you’re unacquainted with the codebase. Imagine you have a bug that seems particularly tricky at first glance. If your project uses assertions, your first step can be to fire up the debugger and follow the steps to reproduce the issue. If it violates an assertion, you may have already found the problem, or are at least closer to finding the cause. Additionally, if you subscribe to the notion that bugs fixes should be prioritized over new features, or the “fail early, fail often” mantra, the use of assertions in this way is a no-brainer.

Assertions as a Development Methodology

Assertions force you to define assumptions upfront about a procedure. What are its input requirements and what do you expect from its output? It also encourages you to add sanity checks which signal “this can’t ever happen.” Laying out these requirements is called contract programming, and it leads to cleaner and intentional code that is easier to maintain. Conversely, like having excessive log statements, if you have assertions that are being violated everywhere, they won’t be useful anymore and will become a burden to you and your team.

Below is a contrived example, in Python, where callers of multiplyVectors are expected to pass two arrays of the same length. While the list comprehension that performs the vector multiplication will return some result regardless of whether the length of the input arrays match, it may not be what the caller of multiplyVectors intended.

Assertions are Self-Documenting

You can use assertions to document assumptions you’ve made, so when someone else encounters it they know what your concerns were when you wrote it. That someone else could be you in a year. When you come across an assertion, it forces you to think twice when adding a new feature to existing code, lest you introduce new bugs. Imagine you refactored some code and you run into an assertion violation. Oops, you made a mistake, but you caught it early and the violation prompts you to ask why.

Consider Fine-Tuning Assertions

What if you don’t want your application to crash due to an assertion? You can turn off assertions for production builds. There is a whole debate on this: Should your software fail silently and potentially cause a catastrophe, or should it fail eagerly even when the consequence is negligible? Your stance may depend on what your goals are.

For example, if you’re working on an SDK whose users are other developers, you may not want your SDK to crash, as it may tarnish your company’s reputation. On the other hand, if your SDK crashes, you get early feedback into a bug and the assertion will help you provide a quick fix and ultimately produce a more reliable product. So the choice to use assertions in production may not be just a technical decision.

Avoid Side-Effects

Side-effects happen when you make an assertion but your assertion statement itself causes unintended consequences. Practically speaking, you might call a method in an assertion that performs a state check, but in actuality calling that particular method modifies some state elsewhere. When assertions are turned off in production these assertions disappear and now your application doesn’t behave the same. Every language has its quirks, so know what happens when you incorporate them.

Here’s a contrived example, in Objective-C, of an assertion that has a side-effect:

Avoid Them In Cases of Uncertainty

If there’s an element of uncertainty such as unavailability of a system resource, you don’t want to be using an assertion. Additionally, assertions on front-end code may be inappropriate because it isn’t as predictable or have easily testable outcomes. You might consider using assertions to document buggy third party APIs that may get fixed in the future, but prepared that those APIs could change from underneath you.

Use Compile-Time Features over Assertions

It’s worth noting that some languages, like Swift, have a number of compile-time constraints. For example, Swift’s optionals force the developer to consider nullability, and C++’s “const correctness” forces the developer to think about mutability. Since assertion checks happen at run-time, it’s better to depend on something compile-time when you can because a developer’s mishap won’t go unnoticed.

Assertions are fantastic as they help you identify problems early on. They help you deliver a more stable product in the long run. They also provide concise documentation which means your code will be easier to maintain. However, assertions are not the be-all end-all tool, and you need to make the decision whether and when they’re appropriate to use. Additionally, since assertions are done at run-time, favor compile-time language features when possible. With diligence, I hope you’ll find assertions to be a valuable tool in your toolbox.

Christopher Hale

Christopher Hale

Christopher is a multidisciplinary software engineer and a bouldering enthusiast. You can follow him on Twitter.
Article


Add your voice to the discussion: