Our elevator announces the direction it's going after the floor number. It says, "8th floor, going up", or "8th floor, going down". One day, however, it simply said, "8th floor". Nothing else. I waited, nothing. Waited some more, still nothing. Until I walked away.
You might laugh if the elevator says "8th floor, going nowhere". But it does make some sense. It eliminates uncertainty. It leaves no surprise. Most importantly, it turns an exception into a normal case.
Exceptions are a major source of complexity. Many years ago, I realized that most code are there to handle special cases. You struggle through 30 lines of code trying to figure out what a function does, only to find the answer in the last 2 lines, which calls another function. Look at this code:
private void ProcessLine(string line, IList result) {
if (isBlank(line)) return;
if (isComment(line)) return;
string typeCode = GetTypeCode(line);
IReaderStrategy strategy = (IReaderStrategy)_strategies[typeCode];
if (null == strategy)
throw new Exception("Unable to find strategy");
result.Add(strategy.Process(line));
}
Exceptions are also a major source of bugs. You could forget to check for zero, or get lost in a maze of if-then-else. Even if you don't, people who maintain your code will certainly do.
One obvious way to deal with exceptions is to turn them into normal cases. A good example is "8th floor, going nowhere". Another example is a Null class which simply does nothing. Experienced programmers return an empty list instead of a null, for the same reason. The above code could be simplified greatly if we have a CommentStrategy, a NullStrategy:
private void ProcessLine(string line, IList result) {
string typeCode = GetTypeCode(line);
IReaderStrategy strategy = (IReaderStrategy)_strategies[typeCode];
result.Add(strategy.Process(line));
}
Many design patterns have the same goal. The Adapter pattern turns a special case into a normal case. The Composite pattern makes a group of objects to behave like a single object.
In my trading application, the user can take the same action on multiple accounts. The obvious way is to check each action to see if it's meant for multiple accounts. If so, loop through the accounts and do the action. However, this would mean to write the same code in at least a dozen places. Using the Composite pattern, I wrote a ComboBroker class which works on multiple accounts but implements the same interface as a single account Broker. This removes the special case from client code. Moreover, the ComboBroker can implement complicated allocation logic which would be extremely messy if you have to do it in a dozen places.
Another way to deal with exceptions is to divide and conquer. In my first job, I had a chunk of Visual C++ code which draws stock charts. It was buggy. I fixed it a few times but sooner or later, a new problem would come up. I had a lot of if-then-else logic there to paint the screen in different ways for different charts and different user preferences. After a lot of frustration, I broke the chunk of code into several classes, each handling only one special case. Each class is single minded. No more if-then-else. And the bugs never came back. Incidentally, this is a Design Pattern, called Strategy.
A third way to deal with exceptions is to externalize them. I once had to fix a Unix script which calls many subscripts only available in production. I had to make many changes just to get it to run in development environment. This messed up the script. Moreover, I had to make sure to reverse all these changes before checking the code back into production, with no way to finally test it. It was very risky. The environmental exceptions not only got into my code, they also got into my process.
My solution was to create a fake production environment with fake scripts. Without touching the master script, I got it to run in development. All changes I made to it were meant for production, not for dealing with environmental exceptions. And most importantly, it went into production unchanged after a successful test run.
Exceptions are not only bad for software, they're also bad in our every day lives. One day, I left my cellphone at work. It's bad because I use it to read books on the train. I always put it on a fixed spot. However, that day, I had to charge it but the charger was on my computer. Murphy's Law says: if anything can go wrong, it will. Exceptions are evil.
It's weird for an elevator to say "8th floor, going nowhere", but for a software developer, it's brilliant. If our software is free of exception processing code, it would be tremendously simpler. Uniformity is heaven.
I'll end this post with my favorite song of today.