When I was a mere slip of a lad, I read Donald Norman’s book: The Design of Everyday Things. This highly-respected tome has coloured the way I see design, and I’ve quoted from it often.
Many of the day to day objects you encounter have had a huge amount of thought put into them. The way the object presents itself and allows itself to be used can have a profound effect on how useful it really is.
Among the countless interesting and practical examples from the book, Norman discusses a very easy to understand illustration of the forcing function. Imagine a shopping mall toilet, where people usually enter with their shopping, and may forget to take it with them when they leave. What if the only way to store the shopping was on a shelf which drops down to lock the door? It would mean that you had to access that shelf to leave, and would probably remember to take your shopping with you. Right?
A similar trick is in play with cash machines/ATMs. After a few initial years of people leaving their ATM cards behind, they applied a forcing function so that you could not get the cash you came for – something you’re unlikely to forget, since it’s what you’ve been asking the machine for, only a few button clicks ago – until you had successfully pulled your ATM card out of the machine.
Forcing functions are awesome and you can use them in software development to make sure that future members of your team, busily making future features, either don’t accidentally do something weird, or can’t forget to do that thing that they wouldn’t necessarily remember to do.
Statically typed computer languages have a few forcing functions in them, like static type, visibility of members, whether something can be overridden or not. Similarly, linters have a few forcing functions, breaking your build with errors if you’ve not followed some code hygiene rules that might help your team.
Here are three recent use cases where I’ve added forcing functions to my work and either caught, or predictively caught mistakes that would be made without them.
Referential Integrity Test
We have a DAO which is there to store values in a table where a particular enumeration in the data must match a reference table in the database. We also have an automated test for that DAO which works against a dockerised real version of the database.
I added a parameterised test that tried out every possible value of the enumeration against the database, on the office chance that a future developer might add a new enumeration value and forget to also add it to the reference table it needs to meet the foreign key constraint with.
I caught myself a fellow unwary developer, who discovered themselves being told by a test something they’d forgotten to do in the SQL.
For reasons that are not worth explaining, we had a set of tests, each of which relied on a base class, which needed to be kept together in a test suite. Adding to the test suite didn’t prevent you from running the test on its own, and if you forgot to add the test to the suite, the test would function correctly.
However, the idea of having all tests in the same suite was to coordinate some setup for all of them, and bring them into one place.
As they all shared a base class, it was easy to create a little standard pre-test check in the base class, that the test object being used was of a type correctly installed in the test suite. If it was not, the test as a whole would fail.
This led to a new test failing on first creation with an error message that politely explained that the developer had forgotten to add it to the test suite. This is better than writing a warning in the read-me that nobody actually reads.
We have a rather neat way of analysing a user’s request to our back end services, and determining if they have not only the rights to that sort of request, but also the rights to the data returned by the request. It’s a neat pattern and it’s made securing our data a turnkey solution.
One possible security risk is that we got the permissioning wrong on one of our endpoints. This is a possible risk, but we do unit test these things and the mechanism for applying the permissions is intentionally very explicit in how it works, so it’s probably easy to spot if it’s doing something weird.
More of a risk is that there might be an endpoint we just forgot to add security to. Worse than that, in the future, some guy doing maintenance on the software might hurriedly add an endpoint with no security on it. How can we prevent this nightmare future scenario.
REST endpoints in most Java systems use some sort of annotated type (usually an interface) to describe the HTTP methods that are exposed. So you can be sure that a fundamental way to make the endpoint work at all, can also be spotted by a protection mechanism.
Via the tricks I discussed in Google Guice: AOP with Interface Annotations, I was able to weave a sentinel into the invocation of each endpoint. This sentinel could detect whether the permissioning had been invoked at all (regardless of outcome) and if it hadn’t been used, then the code could automatically force the endpoint to be unavailable.
A bit of coding, a blog post, a few tests and a trial integration later and the above pattern (in about 90 minutes of work) started spitting out errors relating to things we’d missed when we added security. It found bugs. We fixed them. It WILL be finding more bugs in future because you can’t use the system without it watching over your shoulder and forcing you to do the right thing.
A forcing function is a twist on your design or implementation that’s there to help your users get the outcome they need. Use them wisely. Win extra points and coffee if your forcing function reliably catches bugs before they’re even committed to your source code repo.
If there’s a recurrent bug, then design in a function or make it mistake proof and stop fixing it…