Rules without exceptions

Every software you will work on will have some kind of "rules" it has to follow. The broader term would be Business logic. Some systems will obey invariable rules that can be hard coded. Some systems need dynamic rules, either defined using system configuration (read: web.config) or through user administration (read: /admin/...). But no rule comes without exceptions, right?

Now, dealing with rules and exception is not always good for your mental health. You can easily spot brain damage caused by exceptions to these rules in code:

// 80 km/h using the worker's hourly-rate
var travelPremium = distance / 80 * worker.Rate;

if (worker.Profession == Profession.Refrigeration)
{
    // Refrigeration workers get a bonus premium
    travelPremium += 50m;
}

You get the idea. And when you deal with collective agreements, among other things, you'll get A LOT of this non-sense. Some of it makes sense, but there is nothing logical about it.

Make it part of your domain model

Instead of using a enum to represent professions, make it a full-blown class:

public class Profession
{
    public string Name { get; set; }
    public decimal TravelPremium { get; set; }
}

Now, your model is configurable, allowing it to change dynamically from a database, for instance.

But then, you model gets flooded with properties and data that is only necessary to certain algorithms. And as the number of rules and exceptions grows, so does your domain model. By the time you have completely implemented the clauses of a collective agreement, your models are a mess. There's the travel premium. Is the distance calculated from the employer's main office or from the worker's main residence? What if the work site is secluded? What if the employer provides the vehicle? If employees carpool? If they do with more than five employees on board (that is an actual "exception" to a rule in a software I'm working on. It's written in the collective agreement. That's how it works.)

We only covered travel premiums. You have pensions, work hours, overtime, night premiums, and a lot more. This method breaks down very quickly.

No exceptions, just rules

The visitor (GoF) pattern is great for this kind of stuff. Run the travel premium through multiple visitor objects, each one doing it's job and implementing a rule or an exception to a rule.

public class HourlyTravelVisitor : ITravelVisitor
{
    public void Apply(TravelResult result, IContext context)
    {
        result.Premium += context.Distance / 80 * context.Worker.Rate;
    }
}

public class RefrigerationPremiumVisitor : ITravelVisitor
{
    public void Apply(TravelResult result, IContext context)
    {
        if (context.Worker.Profession == Profession.Refrigeration)
        {
            result.Premium += 50;
        }
    }
}

Now, you still have a magic value, 50, but the exception has become a rule. The design considers the refrigeration case as a separate rule even though it's considered an exception for our clients and end-users.

In this particular case I used a visitor because I'm modifying an existing, incremental result. But this would also work with the strategy pattern.

The point is, next time you encounter business logic rules drizzled with exceptions, special cases and ifs, ask yourself: "How can I transform exceptions into rules?" Your design will be cleaner, more flexible, configuration-ready and more understandable. Don't try and if your way to success.

designbusiness-logic
Posted by: Bryan Menard
Last revised: 05 Nov, 2011 12:30 AM History

Comments

No comments yet. Be the first!

No new comments are allowed on this post.