If-else or DecisionMaker?

On the testdrivendevelopment list there has been a discussion lately about what a testable language should look like.

I threw in a thing I’ve been thinking about for a long time: make a language without if-else/switch-case’s. They hide bugs, make you miss states, make the code unreadable, and they make the testing complexity explode.

First no-one took me seriously, but after a while the discussion caught on. Some though I was mad, and some appreciated my intention of stretching the mind.

After a while I realized that what I really disliked are the logical expressions and all the states you need to keep in your head.

A good nights sleep later I came up with the following idea (implemented in Ruby):

class DoOrDont
    include DecisionMaker
    def doit
        puts "I did it!"
    end
    def dontdoit
        puts "I didn't do it"
    end
end

risk = 0.3
SimpleDecision.new.make_decision_on [ :doit, :dontdoit, risk<0.5]

The :doit and :dontdoit are explicit names of the states of the decision (risk<0.5). If the third expression is true, the first symbol will be used, otherwise the second.

Those symbols maps to the methods of the SimpleDecision class, hence making the aggregate state explicit, and every branch accessible for testing.

In this case there is only one decision, so the if-else variant might be just as pleasing:

if(risk>0.5)
  puts "I did it!"
else
  puts "I didn't do it"
end

Of course, this is still a simple example

The included module DecisionMaker is a simple decorator for any class wishing to make decisions in this way:

module DecisionMaker
    def make_decision_on *triplets
        meth  = ""
        triplets.each do |triplet|
            if( triplet[2] )
                meth +=  triplet[0].to_s+ "_"
            else
                meth +=  triplet[1].to_s+ "_"
            end
        end
        meth = meth.slice(0, meth.length-1)
        self.instance_eval(meth)
    end
end

(This method is pretty ugly, I hope someone can tell me how to do that Ruby-style)

As the number of states increases, or if the decisions cannot be described in a decision tree, the benefits become clearer:

class ClothingAdvice
    include DecisionMaker
    def give_advice temperature, clouds, rain, wind
        make_decision_on [:cold, :warm, temperature<0 ],
                         [:cloudy, :sunny, clouds>0.1 ],
                         [:rainy, :dry, rain>0 ],
                         [:windy, :still, wind>1 ]
    end
    def cold_cloudy_rainy_windy
        puts "Put on every piece of clothing you got"
    end
    def cold_cloudy_rainy_still
        puts "Put on every piece of clothing you got except for the wind jacket"
    end
    def warm_cloudy_dry_windy
        puts "Put on shorts and t-shirt"
    end
    def cold_sunny_rainy_windy
        puts "The weather is weird today, stay inside in your underwear."
    end
    def method_missing name, *args
        puts "For weather '" + name.to_s + "' I have no advice."
    end
end

temperature = -10
clouds = 1
rain = 1
wind = 0

ClothingAdvice.new.give_advice(temperature, clouds, rain, wind);

It is very clear here what states we have. Each state is accessible for testing from the outside, and it is explicit what to do in each method. Should several states have the same action, we can alias them, and it would still be totally clear what happens.

The corresponding if-else variant would look something like this:

temperature = -10
clouds = 1
rain = 1
wind = 0

cold = temperature<0
cloudy = clouds>0.1
rainy = rain>0
windy = wind>1

if(cold&&cloudy&&rainy)
  if(windy)
    puts "Put on every piece of clothing you got"
  else
    puts "Put on every piece of clothing you got except for the wind jacket"
  end
elsif(!cold&&cloudy&&!rainy&&windy)
  puts "Put on shorts and t-shirt"
elsif(cold&&!cloudy&&rainy&&windy)
  puts "The weather is weird today, stay inside in your underwear."
else
  puts "For cold=#{cold} cloudy=#{cloudy} rainy=#{rainy} and windy=#{windy} I have no advice."
end

(If anyone can simplify this, please let me know!)

When I look at this example I get instant state-dyslexia. I cannot easily tell if two states are the same, I cannot easily verify that don’t miss anything I’m interested of, and I cannot easily tell if I accidentally screw up the expressions making some states unreachable.

If the if-else would be test driven, there is a good probability that all states are correct and accessible, but getting this lump of code in you knee without tests would probably make Jack a dull boy.

When decisions are easily expressed in a “decision-tree” it is rather easy to write a readable if-else construct. Unfortunately, the general form is a “decision-matrix” (like weather-states and clothing), and for that if-else breaks apart.

If a language would be created for this the syntax could probably be improved significantly.

If you know how to improve the code above, please drop me a line.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: