views:

351

answers:

6

I have an algorithm that returns a list of classifications(strings) dependant on the two arguments given to the algorithm: a type variable, and an extra category string that allows certain special classifications to be added to the result list.

The current implementation, is unreadable and unscalable due to the expression of the rules as ifs, and switch statements. Also the rules are hard coded.

A simplified version of the code:

 private static List<string> DetermineTypes(Type x, object category) {
  List<string> Types = new List<string>();


  if (category is DateTime) {
    types.Add("1");
    types.Add("2");
    types.Add("3");
  } else if (category is string) {
    switch ((string)category) {
      case "A":
        Types.Add("4");
        break;
      case "B":
      case "C":
      case "D":
        Types.Add("5");
        break;
      case "":
        Types = DetermineTypesFromX(Types, x);
        break;
      default:
        Types.Add("6");
        break;
    }
  }
  return graphTypes;
}


private static List<string> DetermineTypesFromX(List<string> Types, Type x) {
  if (x.Equals(typeof(int))) {
    Types.Add("7");
  } else if (x.Equals(typeof(double))) {
    Types.Add("8");

  } else if (x.Equals(typeof(System.DateTime))) {
    Types.Add("9");
    Types.Add("10");
  }
  return Types;
}

I was thinking that it would be good to maybe specify these with xml, so that a code change wasn't needed for new types/rules, but that is most probably too heavyweight for the situation. Basically I am trying to solve the problem that a new 'Type' may be added at anytime: common case would be that it is one of the 'rules' above, and an unlikely edge case that a new 'rule' branch may have to be added.

I am still to determine whether the work needed it to make it fully dynamic using xml defined rules( or any other way) is worth it compared to the likelihood of the edge cases ever happening and the business environment(schedules etc).

But my main point of the question is how could you elegantly simplify the nested conditional code above? maybe incorporating more flexibility into the design for increased scalability?

I was wondering if using a combination of F# pattern matching might be an appropriate solution? (NB: Never used F# before, have been curious as of late, so thats why I am asking)

+1  A: 

A pattern known as dispatch tables has been recently discussed in the following two blog posts and will probably be of interest to you:

Aaron Feng

K. Scott Allen

Simon Fox
A: 

I would suggest you look at a business rules/inference engine. NxBRE has a good community around it and is quite mature. This may be beyond your immediate requirements but if you expect these rules to increase in complexity over time a BRE will provide a nice framework to keep things under control.

Alex
+1  A: 

I wouldn't shy away from a config-based option; it usually has the advantage of not requiring a rebuild. If you don't want that, another option might be type-metadata via an attribute. This would make it trivial to add data for new types (that you write), and you can (indirectly) add attributes to exiting types (int etc) via TypeDescriptor.AddAttributes - as long as you use TypeDescriptor.GetAttributes to get them back out again ;-p

Whether this is a good idea or not... well, reflection (and the twin, TypeDescriptor) can be slow, so if you want to use this in a tight loop I'd look first at something involving a dictionary.

Marc Gravell
A: 

Since you mention F#, here is some F# code with very similar behavior to the C# code:

open System

let DetermineTypesFromX(x:Type) =
    if x.Equals(typeof<int>) then
        ["7"]
    elif x.Equals(typeof<double>) then
        ["8"]
    elif x.Equals(typeof<DateTime>) then
        ["9"; "10"]
    else
        []

let DetermineTypes(x:Type, category:obj) =
    match category with
    | :? DateTime -> ["1"; "2"; "3"]
    | :? string as s ->
        match s with
        | "A" -> ["4"]
        | "B" | "C" | "D" -> ["5"]
        | "" -> DetermineTypesFromX(x)
        | _ -> ["6"]
    | _ -> []

That said, I would recommend considering a table-driven approach as an alternative to hard-coded if/switch logic, regardless of whether you move the logic out of the code and into a config file.

Brian
+1  A: 

Your problem may be coded in terms of decision tree or decision table

Also, there is posts into Chris Smith's blog about decision trees:

Awesome F# - Decision Trees – Part I and Awesome F# - Decision Trees – Part II

ssp
A: 

I came across similar situation and I've asked a few questions previously in regards to the similar problem that may help you.

The system I did was a configuration driven, rule based dynamic system. All configurations and rules were saved in database. Decision tables were constructed dynamically based on the values and rules retrived from database. Values were then converted and compared in C#. Here's the question I asked about dynamic decision table in C#. And the question regarding dyanmically convert and compare values retrived from databse.

So I end up having something simliar to this in terms of the config table (just an example):

Conditions  IsDecision LHS        Operator  RHS
TTFF        False      PostCode   >         100
TFTF        False      PostCode   <         10000
FTTT        True 

Note: LHS is the property name of the object.

The above table in plain English:

Condition 1 PostCode > 100      Yes Yes No  No
Condition 2 PostCode < 10000    Yes No  Yes No
Outcome 1                       Yes
Outcome 2                           Yes
Outcome 3                               Yes
Outcome 4                                   Yes

Then you have other tables/configs to determine the action for each outcome.

The core parts of the implementation are how to dynamically construct decision table and how to dynamic convert and compare string values, all of which I have provided links to the specific implementations in the above paragraph. I believe you can apply similar concepts in your situations and I hope I've explained the concept in general.

Other Resources:

Martin Fowler's decision tree article.

Luke's post on decision tree.

Jeffrey C