views:

184

answers:

8

I have to take a piece of data, and apply a large number of possible variables to it. I really don't like the idea of using a gigantic set of if statements, so i'm looking for help in an approach to simplify, and make it easier to maintain.

As an example:

if (isSoccer)
    val = soccerBaseVal;
else if (isFootball)
    val = footballBaseVal;
.... // 20 different sports

if (isMale)
   val += 1;
else
    val += 5;

switch(dayOfWeek)
{
    case DayOfWeek.Monday:
       val += 12;
    ...
}

etc.. etc.. etc.. with possibly in the range of 100-200 different tests and formula variations.

This just seems like a maintenance nightmare. Any suggestions?

EDIT:

To further add to the problem, many variables are only used in certain situations, so it's more than just a fixed set of logic with different values. The logic itself has to change based on conditions, possibly conditions applied from previous variables (if val > threshold, for instance).

So yes, i agree about using lookups for many of the values, but I also have to have variable logic.

A: 

As a first step I would probably break up each logical processing area into its own method: (May not be the best names as a first pass)

EnforceSportRules
ProcessSportDetails
EnforceGenderRules 

Next, depending on how complex the rules are, I may break each section into its own class and have them processed by a main class (like a factory).

GenderRules
GenderContext
blu
A: 

I have nothing special to offer you than to first recommend not to just leave it as a big block-- break it into sections, make comment dividers between important parts.

Another suggestion is if you are going to have many very short tests as in your example, break from convention and put the val incrementors on the same line as the evaluatation and indent so they align with eachother.

if (isSoccer)              val = soccerBaseVal;  
if (isMale)                val += 1; 
else                       val += 5; 

switch(dayOfWeek){ 
    case DayOfWeek.Monday: val += 12; 
    ... 
}  

Excess whitespace can make those hundred things into several hundred lines, making vertical scrolling excessive and difficult to get an overall view of the thing.

smdrager
+7  A: 

A common way to avoid large switching structures is to put the information into data structures. Create an enumeration SportType and a Dictionary<SportType, Int32> containing the associated values. The you can simply write val += sportTypeScoreMap[sportType] and you are done.

Variations of this pattern will help you in many similar situations.

public enum SportType
{
    Soccer, Football, ...
}

public sealed class Foo
{
    private static readonly IDictionary<SportType, Int32> sportTypeScoreMap =
        new Dictionary<SportType, Int32>
        {
            { Soccer, 30 },
            { Football, 20 },
            ...
        }

    private static readonly IDictionary<DayOfWeek, Int32> dayOfWeekScoreMap =
        new Dictionary<DayOfWeek, Int32>
        {
            { DayOfWeek.Monday, 12 },
            { DayOfWeek.Tuesday, 20 },
            ...
        }

    public Int32 GetScore(SportType sportType, DayOfWeek dayOfWeek)
    {
        return Foo.sportTypeScoreMap[sportType]
             + Foo.dayOfWeekScoreMap[dayOfWeek];
    }
}
Daniel Brückner
Better semantics than mine.
NickLarsen
Nice. I'll probably use something like this. However, i've expanded the problem a little. See my edit.
Mystere Man
You will always reach a point where it is not possible (or useful) to extract more functionality in common methods - this is ideally when all operations you have to perform are distinct and there is no common pattern remaining. This incompressible rest (the analogy to data compression fits quite well - one tries to extract all common patterns and all that remains looks like structureless random noise) can still be very complex. The best one can do with the rest is to modularize it as good as possible in several small methods with good names.
Daniel Brückner
This is *exactly* the use-case for Java's powerful enum-class - you could associate the extra data with the enum-value itself, negating the need for all those dictionaries (and allowing you to, for instance, put GetScore() in the enum class, so you could call `sportType.GetScore(dayOfWeek)`). One of the few things that Java did right and C# did wrong...
BlueRaja - Danny Pflughoeft
See also: [here](http://stackoverflow.com/questions/1376312/whats-the-equivalent-of-javas-enum-in-c/1376455#1376455). It's complex enough that it's only really useful if you have need for **a lot** of Dictionaries like this, though..
BlueRaja - Danny Pflughoeft
+1  A: 

Use either a switch statement or filter function.

By filter function, I mean something like:

func filter(var object, var value)
{
    if(object == value)
        object = valueDictionary['value'];
}

Then apply the filter with:

filter(theObject, soccer)
filter(theObject, football)

Note that the filter works much better using a dictionary, but it is not required.

samoz
You beat me too it.
NickLarsen
A: 

If you are really just adding values in this sort, I would either create an enumeration with defined indices that correspond to stored values in an array. Then you can do something like this:

enum Sport
{
    football = 0,
    soccer   = 1,
    //...
}

int sportValues[] = { 
    /* footballValue */,
    /* soccerValue */,
    /* ...Values */
};

int ApplyRules(Sport sport, /* other params */)
{
    int value = startingValue;
    value += sportValues[(int)sport];
    value += /* other rules in same fashion */;
}
NickLarsen
+1  A: 

Cribbing from The Pragmatic Programmer, you could use a DSL to encapsulate the rules and write a process engine. For your presented problem, a solution might look like:

MATCH{
    Soccer   soccerBaseVal

    IsMale   5
    !IsMale  1
}

SWITCH{
    Monday   12
    Tuesday  13
}

Then match everything in the first col of MATCH, and the first item in each SWITCH you come to. You can make whatever syntax you feel like, then just write a bit of script to cram that into code (or use Xtext because it looks pretty cool).

Adam Shiemke
+1  A: 

Here are a few ideas:

1 Use lookup tables:

var val = 0;

SportType sportType = GetSportType();

val += sportvalues[sportType];

You can load the table from the database.

2 Use the factory pattern:

var val = 0;

val += SportFactory.Create(sportType).CalculateValue();

The Dynamic Factory Pattern is useful in situations were new (sport) types are added frequently to the code. This pattern uses reflection to prevent the factory class (or any global configuration) from being changed. It allows you to simply add a new class to your code.

Of course the use of an dynamic factory, or even a factory can be overkill in your situation. You're the only one who can tell.

Steven
A: 

Consider implementing the Strategy Pattern which utilizes inheritance/polymorphism to make managing individual functions sane. By seperating each function into its own dedicated class you can forego the nightmare of having miles-long case blocks or if statements.

Not sure if C# supports it yet (or ever will) but VB.NET integrates XML Comment CompletionList directives into intellisense, which--when combined with the Strategy Pattern--can give you the ease of use of an Enum with the open-ended extensibility of OO.

STW