You know Murphy’s Law, right? Or at least you know the way most people remember it: “Anything that can go wrong will go wrong.” It’s a fairly depressing way of summarising life, but we all recognise a large portion of truth within it. Things go wrong. All the time.
There’s actually contention over whether this is the original form of the law, which is named after aerospace engineer Capt. Edward A. Murphy, and there are several differing accounts of how the saying came about. My preferred account is that relayed by Australia’s Dr Karl Kruszelnicki, whose record of Captain Murphy’s original exclamation is:
If there are two or more ways to do something, and one of those results in a catastrophe, then someone will do it that way.
I like this version because it’s easier to see the qualified optimism that Murphy juxtaposed with his frustration. As Dr Karl explains, there is a hope embedded in this form of the law because it starts with a proposition: “IF there are two or more ways to do something…”
Murphy’s Law in the Physical World
You may have never realised it, but you’ve seen Murphy’s Law put into action all over the place, and I don’t mean by lots of things failing. For example, in most countries power plugs will plug into sockets in only one orientation – attempt to put them in any other way and they won’t fit. Another example is symmetrical keys: any flat key has two sides, so when a key is inserted into a keyhole it could be oriented one of two ways. The design of symmetrical keys ensures that it doesn’t matter which of these two ways you choose, the key will still turn the lock.
These two examples demonstrate two different approaches to applying Murphy’s Law in a design with the intent of preventing failure. The designers of the power sockets removed all but one way of inserting the plug, so there is no “wrong way” for people to do it. They eliminated the “if there are two or more ways” part of Murphy’s proposition. The inventors of the symmetrical key took the other tack: there are two ways to insert the key, but it doesn’t matter which you choose, because both will work. They eliminated the “and one of those ways results in catastrophe” half of the proposition.
By inverting the proposition of Murphy’s Law, we can distil two simple rules which can be used as design constraints for any design task – physical or digital – where we want to prevent an interface from being used in a way that causes catastrophe:
- Make all incorrect actions impossible; or
- Make all possible actions correct.
Murphy’s Law in Code
When we write code, every time we author a function or method signature, or a new class or set of classes, we’re creating an interface. Just like the power plug interfacing with a socket, and the key interfacing with a keyhole, the designs we choose for function signatures will determine whether it’s possible for other developers who use that function to stuff it up.
I don’t think there are many programmers who think of every function signature they create as a design task. Usually design is a discussion that stops at the level of how classes interact. While we may from time to time author a public API, the vast majority of functions are not created with the intent of someone else using them, but are created immediately before the same developer writes the code to call the function. This blesses us with a lethal dose of confirmation bias in approving a function’s interface as being “common sense” and “straightforward”. We are clear in our mind what needs to be passed into the function, but we don’t give much thought to what someone else might try to pass in. We’re coding without empathy.
It’s an accepted reality that most code gets maintained at some point in its life, and usually months or years after it was written. Code also gets re-used, sometimes for the same purpose for which it was created, but sometimes for something slightly, or completely, different. We all know we should be writing code that’s “maintainable” and the vogue for achieving this is simply using good names and adding a bit of documentation.
How much better would maintenance be, though, if, rather than having a collection of names and documentation that tried to help us to find the right way to do things, we got to work with code that makes it near impossible to do it wrong? The chance of errors would drop dramatically, and many tasks would become simpler. Just like the power plug, we could tell simply by looking at the code that there’s only one way to use a piece of code. We could know our usage was what the original author intended, rather than constantly asking ourselves “Have I understood this fully?”
How far is our current code from this ideal? What changes need to be made to the idiomatic way we write classes and functions today to bring this dream – of making the incorrect impossible – a reality?
Example of Foolproofing Code with Murphy’s Law
Here’s a fairly simple example to see how we can put Murphy’s Law into action. Let’s consider this function:
def distanceInMiles( latitude1 : BigDecimal, longitude1 : BigDecimal, latitude2 : BigDecimal, longitude2 : BigDecimal): BigDecimal
This is a pretty simple and idiomatic way to write such a function. It seems clear from the naming what it takes, what it calculates, and what it returns. The guy who wrote it is pretty happy with his latest creation, and he uses it straight away one layer up in the codebase:
val distance = distanceInMiles(location1.latitude, location1.longitude, location2.latitude, location2.longitude)
And life is good… until someone comes along later and writes some new code that calls the same function with these parameters:
val distance = distanceInMiles(location1.latitude, location2.latitude, location1.longitude, location1.longitude)
Why did he do this? I don’t know. Maybe it was the last day before the release and he was under the pump to get this feature done, so he was going a bit too fast and making liberal use of his IDE’s capability to auto-complete function arguments based on the parameter types rather than studying the names of the parameters. He’s made two mistakes. Firstly, he assumed he should pass in the two latitudes and then the two longitudes. Secondly, he did a bit of a copy-paste and forgot to change the last parameter to use location2
, instead passing in location1
‘s longitude. The interface of the function did nothing to help him here. His code compiled and he committed it, or pushed to origin, as the cool kids like to say these days.
We can fix the first problem with Murphy’s Law by making his first incorrect action – passing a latitude in as a longitude – impossible. A simple way to do this is to wrap different values in their own custom types:
class Latitude(val latitude: BigDecimal) class Longitude(val longitude: BigDecimal) def distanceInMiles( latitude1 : Latitude, longitude1 : Longitude, latitude2 : Latitude, longitude2 : Longitude): BigDecimal
Users of the function will now get compile errors if they try to pass a Longitude
in as a Latitude
. We haven’t prevented the problem where location1
‘s longitude was passed instead of location2
‘s. Strictly speaking, we can’t make it impossible for the wrong argument to be passed in – the caller has to have the freedom to select their arguments! However, what we have in this particular case is a pair of parameters that are related to each other but are passed in separately. We can make it harder for this kind of error to occur if we always pass around the pairs of coordinates as a typed pair, rather than as individual parameters:
class Latitude(val latitude: BigDecimal) class Longitude(val longitude: BigDecimal) class GeoLocation(val latitude: Latitude, val longitude: Longitude) def distanceInMiles(location1: GeoLocation, location2: GeoLocation): BigDecimal
That’s a pretty simple set of changes, and we’re in a good place now when it comes to calling this function: you can’t mix up latitudes and longitudes, and you’d probably have to be acting maliciously to transpose coordinates from two discrete GeoLocation
s. Murphy would be proud.
We haven’t yet looked at how the result of this function could be used, and it turns out it’s just as dangerous as the parameters. One particularly programmer, employing the “By Coincidence” methodology, is using it like this:
val distance = distanceInMiles(location1, location2) val etaInHours = distance / speedInKmh
This seems like the kind of mixup only an idiot could make, the kind of error that is most easily mitigated by hiring a team of smart software engineers. Of course, if you think that smart people can’t make silly mistakes like this, you’re plain wrong, as proved by the NASA team that burnt up the Mars Climate Orbiter because of a failure to convert from Imperial units to metric.
The problem above is that, while the function name makes it clear that what is coming back is in miles, having a really good name doesn’t make it impossible to do the wrong thing. There are many ways to use the return value, and it just so happens that lots of them are wrong! As with the function parameters, we can use a more strongly-typed value object for the return value to prevent this kind of thing:
// Now returns a 'Distance' object which encapsulates the units def distanceBetween(location1: GeoLocation, location2: GeoLocation): Distance abstract class DistanceUnit { … } abstract class SpeedUnit { … } abstract class TimeUnit { … } class Distance(private val length: BigDecimal, private val unit: DistanceUnit) { def durationWhenTravellingAt(speed: Speed): Time = { … } } class Speed(private val speed: BigDecimal, private val unit: SpeedUnit) class Time(private val timeValue: BigDecimal, private val unit: TimeUnit) { def in(unit: TimeUnit): BigDecimal = { … } } val distance = distanceBetween(location1, location2) val speed: Speed = new Speed(45, MilesPerHour) val eta = distance.durationWhenTravellingAt(speed) printf("ETA: %.1f hours", eta.in(Hours))
The above (very incomplete) code encapsulates the units of each of the different types of values along with the values themselves, making it very near impossible for users of the resulting values to do the wrong thing. Of course, they can still hang themselves by wrenching the bare values out of their context and abusing them, but this is akin to cutting the plug off the end of a power cord because you think plugs are cumbersome and you have a preference for poking the bare wires into the socket.
Most interesting in this final example, and different to the previous examples, is the application of the second protection suggested by Murphy’s Law: “Make all possible actions correct.”
Take a look at the design of the Distance.durationWhenTravellingAt(Speed)
function: it doesn’t care what the units of the Distance
or the Speed
are. Internally, of course, it has to be aware of the units in order to calculate the correct result, but at the interface level there is no need to make the Speed
units match the Distance
units. You don’t have to match metric to metric or imperial to imperial, and there aren’t approved pairings and illegal pairings, with the latter communicated at runtime using exceptions. If we pass in a Speed
with a correctly-implemented SpeedUnit
, the function is able to always take the correct action, even with a SpeedUnit
that’s not written by the author of the function.
Are You Committed to Writing Foolproof Code?
If there are two or more ways to do something, and one of those results in a catastrophe, then someone will do it that way.
Remembering Murphy’s Law in this form replaces the anxiety of thinking “Everything always goes wrong” and instead gives you an actionable mantra. NASA seems to make liberal use of it to try and predict what might go wrong and how they might mitigate it.
So, are you and your team just in the business doing things the right way?
Or are you also in the business of designing things that can’t be used the wrong way?
Want to learn more?
Here’s some great books that demonstrate various ways to build quality into software as you’re writing it:
Image credits: 30 Ways to Shock Yourself scanned by Bre Pettis
French (Type E) power socket by Etienne Rastoul
Code Bug by Guilherme Tavares
There is not much in the net conceening this so it was a real life-saver to
stumboe across your webpage. It’s assisted me a good deal
and I’m positive many others would state the same also.
Look at my web-site e liquid samples