Thursday, May 15, 2008

Build to work vs. Build not to fail: eliminating the unhappy path

Consider the following methods written in Java. The requirement of the method is that it returns true if the parameter equals "Red"
// returns true if the value of the argument color equals Red
public boolean isRed(String color) {
return color.equals("Red");
}

// returns true if the value of the argument color equals Red
public boolean isRed(String color){
return "red".equalsIgnoreCase(color);
}

Both implementations satisfy the same business requirements, but they are different. They are vastly different.
There are a couple of things that the second implementation does that the first doesn't.
The second implementation uses the equalsIgnoreCase method to compare the values, that allows for variations in casing--if the string comes in as red, Red, RED, or reD, it's going to say that it is red.
That's nice and it might never be needed, but there's another advantage to the second implementation. It won't throw a NullPointerException if it is invoked with a null argument. The equalsIgnoreCase is invoked against the String "red", and not the argument which is of type String. The big difference is that a null argument can be passed for any object.
To many programmers the first implementation is their most comfortable and natural way to write that method.
For me, the second way is more natural because it is more robust. When I look at a method, or a system, I look for opportunities for it to fail.
As a kid I used to look at systems and try to find the way to make them fail. I can't recall all the mischief that I caused growing up finding the flaws in systems. For me, finding them and finding a way to exploit them was fun, but I rarely actually executed any of my plans.
One example that comes to mind is an apartment I had in college. There was a security phone in the building that kept people out.
My landlord would not issue me a second key to the security door, nor would the locksmith make me a duplicate. The motivation for this is to charge more for extra renters.
This system made having guests stay over a weekend a little tricky. Tricky because I couldn't leave them a key. They'd need to come and go around my schedule. That's not very hospitable. And I like to be hospitable.
I found a way around this system though. I gave my guests a small 900 MHz phone that had a base in my apartment, my apartment was well within the range of the security phone. My guests could ring my apartment through the security phone answer it with the cordless phone and buzz themselves in. Problem solved.
I also discovered that the security phone answered calls to it and would open the door if it heard the 9 button tone.
I actually found carrying the phone to be more convenient for myself than using the key. I programmed the number of the security phone into my phone on speed dial and would dial the number then hit 9 as a rudimentary keyless entry system. It didn't take too long for me to be able to get the door open on my way home without breaking my stride.
The security phone in question is actually pretty typical of most engineers. They built the system to fulfill the use case that was given to them. Like many engineers, they probably had many priorities competing with designing the security phone and they did what it took to get a product that meets the requirements out.
What's wrong with this system? Well, for one it's a SECURITY door. Anyone who knows the phone number for it can gain entry to the building. It should probably be renamed to obstruction door.
What does the security door have to do with the first isRed method? Both of them are vulnerable to unexpected results when provided arguments that are outside the expected parameters, or as we call it off the happy path.
Programming and testing only the happy path are inviting the opportunity for unexpected results down the road.
One way to minimize the risk of unhappy path conditions is to limit the way that uncontrolled variables are used. This can be illustrated by the second isRed method. The uncontrolled portion of the method, the color variable, is never assumed to not be null nor are any methods invoked against it.
Instead, a new object is instantiated within the method, and completely within control of the method, so unexpected results should be at a minimum.
“When you have eliminated all which is impossible, then whatever remains, however improbable, must be the truth.” --The Adventure of the Blanched Soldier, Sir Arthur Conan Doyle
The quote above is probably the most influential line I read in my childhood. I try to apply that principle to my programming. By eliminating the possibility of unhappy end conditions through controlling the number of paths in your programs you're leaving only the path and the purpose for which the program is designed.
When you have eliminated all the unhappy paths, all that will remain is the paths that are happy.

No comments: