Write Fail-Safe Code
When something is said to be fail-safe, that means it is designed in such a way that when it breaks, it will do so with the least amount of harm. This is important in software development because there are ways to minimize the impact something has when things go south. All it takes is some unexpected value to come into your logic to produce an unexpected result. You want to make sure that if something goes wrong, that it will take the path that will do the least amount of damage. Think of it as an exercise on covering your own ass.
Let's evaluate the following bit of code:
public decimal CalculateCharges(Order o,Customer c){
decimal val=0;
switch (c.VipLevel) {
case 0:
val=o.Total;
break;
case 1:
val = o.Total * .1m;
break;
case 2:
val = o.Total * .2m;
break;
}
return val;
}
This is a simple example that has a lot wrong with it, but let's just assume that this funtion is being used is production to get the value from an order to stick on an invoice or sent to a credit card processing gateway. It's been running along merrily for a year without issue. One day your marketing dept decides that we need a Level 3 customer that gets charged full price, but gets some other benefit that is totally unrelated to the scope of this code. What happens now? With the code above it looks like you are going to be giving away free product for those awesome level 3 customers.
The most obvious thing here is to make sure that all of your case statments have a default handler:
public decimal CalculateCharges(Order o,Customer c){
decimal val=0;
switch (c.VipLevel) {
case 0:
val=o.Total;
break;
case 1:
val = o.Total * .1m;
break;
case 2:
val = o.Total * .2m;
break;
default:
val=o.Total;
}
return val;
}
Always having a default for your case statement is just good practice. But, let's just assume that in place of the simple switch statment there is some crazy set of weaving logic that has been hacked up by 3 different former employees. You know the type of stuff I'm talking about. It's that smelly code that you're afraid to touch because it works, but you're not sure why. Now the code looks like this:
public decimal CalculateCharges(Order o,Customer c){
decimal val=0;
#region Al's Code -- Don't Touch!
if(condition1 &&
(condition2 || condition3 && !false) ||
(1=0 || c.MakeRemoteCall(o.Total,false,1,"Fred")==3.23)
){
decimal subVal=38;
if(Global.YoMomma==YoMommaSoFatJokes.Number194){
subVal=o.Total/CONSTANT_VALUE;
}else if(Life==42){
subVal=7;
}
if(condition1 || condition2){
subVal+=3.27m;
}
if(condition1 && condition3){
subVal*=.99m;
}
if(condition3 && !condition2){
subVal=o.Total;
}
if(subVal > 7 && subVal < o.Total)
val=subVal;
}else if (o.Date.DayOfWeek==DayOfWeek.Tuesday &&
o.Date.PhaseOfMoon==MoonPhases.Full &&
Global.Hell==HellStatus.FrozenOver
){
val=o.Total/DateTime.Now.Ticks;
}
#endregion
return val;
}
You've all seen code written like this (maybe some by me, eek!), and if it's in your code base, you are going to keep that region rolled up so you never have to look at that.
public decimal CalculateCharges(Order o,Customer c){
decimal val=0;
//+ Al's Code -- Don't Touch!
return val;
}
All of this silly example code to make a point right? Yes, I'm getting there. If we're under the assumption that Al's code works as expected today, but might not be future-proofed, then we can make sure that the worst thing that happens is that we charge the normal price. So, as long as Al's code doesn't depend on the value of "val" being zero, then we can rewrite it to be this:
public decimal CalculateCharges(Order o,Customer c){
decimal val=o.Total; //Default to the order charges
//+ Al's Code -- Don't Touch!
return val;
}
This example is a little forced. My real-world examples are a lot more complicated. It's important to realize that business rules will change and sometimes funny stuff might end up in your data that you weren't expecting 2 years ago. Try to write your code in a way that if(when) it gets confused that it takes the path of least damage.
Comments(0)


