If-else or DecisionMaker?

November 17, 2007

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.

Lately I’ve been coding away on my hobby project Bumblebee. Bumblebee is about integrating your documentation into you test cases and get human-readable documents from it.

My strongest impressions so far are:

Documentation, acceptance testing and the features of the application are in symbiosis. When I add features in Bumblebee, I start out writing the documentation for it, which is a comment in the test case or a test method. This forces me to think of how I present the feature to the user, and the resulting text keeps me on track while developing the feature. It also helps me see duplication in features much earlier than I would otherwise. Then I add an assertion for the resulting document to get a failing test case.Then I add the code to make the test pass. When refactoring the code and tests aggressively, I realize additional things about the application that I wouldn’t have if I only had unit tested it, and I come up with things that make the application better, as when I realize the same things with unit tests I usually only improve the unit, which often proves to be a sub-optimization.

I don’t miss my unit test that much. I rather am pretty happy for not having them around.

So far, this has proven to be a rather successful strategy that feels easy to work with, so I will continue full speed straight ahead to see where the next wall is…

I just released the first version of Bumblebee. You can read more about at
http://www.agical.com/bumblebee/bumblebee_doc.html
which of course is a document that is generated by Bumblebee.

It shows the first version of how to create snippeted documentation in a really easy way. It is rather
comprehensive, so I won’t repeat the details here, but feel free to ask any questions!

History
A couple of years ago I got extremely annoyed when I had to write a word document with basically a copy of the code I had written.

There was to be a lot of copy’n'pasted code, runtime values etc and the knowledge that I would have to go through everything again a couple of weeks later because of smaller changes in the code.

I of course argued that there were a lot of test cases describing the system, but the client wouldn’t accept that for documentation.

Since most of the information that would go in that document was available in the test cases, I felt that there had to be a way to get that data into something more human friendly.

Read the rest of this entry »

During the years when I started to learn agile development, especially TDD and good OO, I had the fortune to work as a consultant for a company that ran a massive number of small projects, each lasting for approximately 3 months. This was exactly the amount of time I needed to learn something new, apply it, and discover the limitations of it.

At the end of each project I realized there was a much better way of doing things, and the effort to rewrite the code probably would have eaten me alive. But hey, no problem, I’ll be on a new project next week, with a fresh code base to apply my new findings at.

At that time the recurring project restarts actually was a good strategy to improve the average code quality! If I would have had to keep on working with the same code base I would never have been able to incorporate that much new technology and knowledge in the code base, and I would never have learned that much about what makes code good.

Later on I also learned refactoring, but I doubt I would appreciate it as much if I hadn’t learned about good code first. I think this is one of the great limitations of refactorings, it requires a lot of experience to know in what direction to go.

By allowing people and projects to restart you can make people raise their sight to take a new direction, to get a new vision of what it will be like there, to learn and incorporate their experiences in the new. Granted, most agile processes have retrospectives, but sometimes they are not enough a kick-in-the-butt to start off something new.

If it is hard to introduce refactoring in order to improve the code base, or when retrospectives don’t seem to do it either; try a fresh start iteration where you can dream a little! If you realize it’s not leading anywhere, just keep on working with the old code base. In the long run you will probably gain more from the new perspectives than the lost iteration cost.

How much is a team-minute?

August 12, 2007

As opposed to the light-year, which is a very long distance, there is also the notion of the beard-second, which is a very short distance. As the name implies, it is how long a straw of beard grows in one second. When you calculate the length of the beard-second (5 nm) it is amazing that anybody has beard (or hair) at all! Still, you see it every day. How is that possible!?

The not so surprising answer is: It accumulates over time.

When in a software project (or any project for that matter) the team always spend time on doing the same tasks over and over again, every day. I have always felt that it is good to cut down on build times, having fast computers etc, but I never calculated the gain from it.

The other day I asked myself “how much would we gain if everyone on the team gained one minute per day?”

There are approximately 200 working days in a year, and on my team we are approximately 10 people. If we gain one minute each per day, that is

200*10*1/60=33h20min (for one person)

Wow! That means that I can spend 33h on making a one team-minute improvement, and it will pay back in about a year!

If we gain half an hour that is

200*10*1/2 = 1000h

Whew!

Then I started to measure in money. If the approximate hourly cost for one person is €100, every team-minute costs €3300. That is a lot of money. For a minute! Half a team-hour a day costs €100.000 per year.

If the machine park is aged, or the network is slow, add up all your team-minutes and present them with hard numbers to your boss. I’m not saying he or she will listen, but it will be much harder to reject such a proposition!

The plan could backfire and your coffee- and lunch breaks be revoked, but it might be worth the risk! ;-)

Good luck!

All substantial knowledge I’ve come across as a SW developer has been about dependencies, and particularly about managing dependencies to minimize their impact on design and its flexibility.

A thinking tool

By thinking of what I do as “dependency management” I have a tool that will guide me in any situation, including managing projects, servers, build automation, xml- and property files, database design, architecture and object design.
It may sound somewhat abstract, but it always manifests itself very clearly when getting down to work. I just keep the following quote in mind:

“What need this part NOT know about?”
-Joakim Ohlrogge

Good dependencies are the minimal set of vital dependencies on work organized to minimize the set of vital dependencies.

Circular proof?!?

Not really, but usually an iterative process.

The range of dependency management

Dependency management ranges from organisations and projects, down to the smallest piece of bit-coding:

“Hey, we cannot finish this piece of work because we need that
piece of work from that other project and they’re not ready”

or

“It would be so much easier to parse this stream protocol if the length information was in the beginning of the stream…”

There are ways around everything, but having the right dependencies will make all work flow.

Dependency smells and refactoring

Dependency management can also be used when refactoring, by trying to identify “dependency smells”. Here are some examples that will show you what I mean and how I think about dependency management in the software design space. The examples are basically design principles versus dependency smells:

Don’t repeat yourself (DRY) vs Degraded Dependency (DD)
DRY:
“Remove duplication” - One of the core principles of all software development.
DD:
The code modules that should be depending on one single module is actually depending on two identical modules that are not the same, hence degrading both dependencies. This does not increase the code management burden to the double, but rather one order of magnitude, since the knowledge of the duplication must be passed on to and remembered by everyone involved.

Single responsibility principle (SRP) vs Piggyback Dependency (PD)
SRP:
“Let each module have one responsibility”. This principle is also interpreted as “Let each module have only one reason to change”.
PD:
If a module A has a dependency on a two-responsibility module B(r1,r2), but it is only interested in one of the responsibilities (say r1), there is an implicit piggyback dependency A->r2. This piggyback dependency will actually tie the application down, and make it more and more difficult to change over time.

Liskow Substitution Principle (LSP) vs Obscured Dependency (OD)
LSP:
States that a if a base module A (class/interface) is accepted for execution, any module B that extends A should be accepted as well. This principle protects the dependency B has on A.
OD:
Often when extension is used only to make A’s functionality available in B, or when the chain of inheritage gets long, LSP is in danger as complexity makes it harder to manage the original B->A dependency and its meaning. The functionality of the code can be validated by tests, but the design has been obscured.

Abstraction (Abs) vs Hardwired Dependency (HD)
Abs:
An abstraction tells that a module only has a description of its access points, not of how those access points will execute.
With this design pattern there is an explicit anti-dependency on the implementation.
HD:
When not using abstractions in code, executing modules are depending directly on other executing modules. This hardwiring has a tendency to spaghettizie over time, making it virtually impossible to change a piece of code without breaking another.

Dependency management and TDD

Well used, and especially with a low threshold for akward tests, TDD tends to lead to good design regarding dependencies. However, good knowledge of dependency management will lead to even better tests. I personally started to understand the value of dependency management when I started to write tests for my code, but I write much better tests (and code) now that I think in terms of dependency management.

Conclusion

By abstracting your thinking to using dependencies it is often much easier to reach good, concrete design decisions that will stand the test of time.