Archive for January, 2010

The OCP kata

Tuesday, January 12th, 2010

Read the first chapter of the Patterns book, it’s all there, where it says “favor composition over inheritance”,

said Jacopo. We were chatting about the Open/Closed Principle, and how I read about it in Meyer in 1991, yet it didn’t “click” for me back then.

Now I see how the OCP is key to writing code that can be changed easily, which is the chief technical goal of an agile team. I wondered, is there a way to teach and learn the OCP? Is there a kata to learn OCP?

It’s unfortunate that most common coding katas result in “single-object-oriented programming”. The famous bowling score example by Robert Martin, for instance, is usually solved by creating *one* object. This might be fine for learning how to write simple code. It’s not so good for learning how to do object-oriented design.

So I invented this little exercise to practice and learn OCP.

Take any coding problem. The bowling score, the string evaluator, the supermarket checkout, you name it. Then follow these instructions.

0. Write the first failing test. Then write a factory that returns an object, or an aggregate of objects, that make the test pass.

The factory should be limited to creating objects and linking them together. No conditionals allowed.

1. Write the next failing test.

2. Can you make it pass by changing the factory and/or creating a new class and nothing else? If yes, great! Go back to 1. If not, refactor until you can.

The refactoring should bring the system to a state where it’s possible to implement the next test just by changing the aggregate of objects that is returned by the factory. Be careful not to implement new functionality; the current test should still fail.

For instance, take the bowling score problem. The first test is

  @Test public void gutterGame() throws Exception {
    BowlingGame game = new BowlingGameFactory().create();
    for (int i=0; i<20; i++) {
      game.roll(0);
    }
    assertEquals(0, game.score());
  }

The code to make this pass is

  class BowlingGameFactory {
    public BowlingGame create() {
      return new BowlingGame();
    }
  }
  
  class BowlingGame {
    public void roll(int n) {}
    public int score() {
      return 0;
    }
  }

Nothing strange here. Now the second test is

  @Test public void allOnesGame() throws Exception {
    BowlingGame game = new BowlingGameFactory().create();
    for (int i=0; i<20; i++) {
      game.roll(1);
    }
    assertEquals(20, game.score());
  }

The simplest code that makes both tests pass would be to change BowlingGame to accumulate rolls in a variable. But our rules stop us from doing that; we must find a way to implement the new functionality with a new object. I think about it for a few minutes, and all I can think of is to delegate to another object the accumulation of rolls. I will call this role “Rolls”. Cool! This forces me to invent a new design idea. But I must be careful not to add new functionality, so I will just write a Rolls object that always returns 0.

  interface Rolls {
    void add(int n);
    int sum();
  }
  
  class BowlingGame {
    private final Rolls rolls;
    
    public BowlingGame(Rolls rolls) {
      this.rolls = rolls;
    }
    
    public void roll(int n) {
      rolls.add(n);
    }

    public int score() {
      return rolls.sum();
    }
  }
  
  class BowlingGameFactory {
    public BowlingGame create() {
      Rolls zero = new Rolls() {
        public void add(int n) {}
        public int sum() { return 0; }
      };
      return new BowlingGame(zero);
    }
  }

This passes the first test, and still fails the second. In order to pass the second test, all I have to do is provide a real implementation of Rolls and change the factory.

  class Accumulator implements Rolls {
    void add(int n) { ... }
    int sum() { ... }
  }
    
  class BowlingGameFactory {
    public BowlingGame create() {
      return new BowlingGame(new Accumulator());
    }
  }

And so on. The point here is to think about how to

  1. compose functionality out of existing objects, and
  2. avoid reworking existing code.

Feedback?

Updates

24/12/2013 Chris F Carroll‘s Gilded Mall Kata is a new exercise that can be done with the OCP kata rules. Thanks Chris!

29/05/2014 There is now a repository with a few prepared exercises! I presented this at XP2014.

Project automation for Confluence plugins

Monday, January 4th, 2010

A while ago our team was working at customizing Confluence for one of our customers. I’d like to share a few tips on how to automate Confluence plugin deployments.

(It should not be necessary to point out that all repetitive operations should be automated. Read all about it in The Pragmatic Programmer and Pragmatic Project Automation.)

Our automation was mostly Bash-based. I think it’s important to write expressive code in any language. When you’re scripting with Bash, the main way to be expressive is to use functions. We keep our scripts in a $(project)/script directory, so that you can invoke them with a minimum of keystrokes. The script we use for installing our plugin on localhost is:

  
 1  confluence_url=http://localhost:8080        
 2  confluence_install=~/confluence-2.9.2-std
 3  admin_password=admin
 4  
 5  source src/main/bash/lib.sh || exit 1
 6  
 7  quietmvn package || exit 1
 8  plugin_jar=$(ls target/ourplugin-*.jar)
 9  [ -f "$plugin_jar" ] || (echo "no plugin generated"; exit 1)
10  confluence_login 
11  confluence_uninstall_plugin
12  confluence_install_plugin

Lines 1-3 set up some variables for future use.

Line 5 loads our library of functions in the current shell process. The “|| exit 1” bit means “if this command fails, then quit immediately”.

The “quietmvn” in line 7 invokes maven with a filter that hides useless warnings. We found that Confluence plugin builds generate a lot of warnings due to dependencies on Atlassian packages that have poorly written “pom.xml” files. This (according to Atlassian) is harmless; but then again, useless warnings are harmful, so we filter them out.

Getting back to line 7, this builds the jar package of our Confluence plugin.

Line 8 defines a $plugin_jar variable with the relative pathname of the plugin jar. The $(foobar) bit is a Bash command that executes the foobar command and returns the text output generated by the command.

Line 9 says “if the plugin file does not exist, then exit with a meaningful error message.” In Bash, the command [ -f foobar ] tests if file “foobar” exists.

Line 10 to 12 are the interesting bit. They invoke three Bash functions that do what their name implies. They depend on the variables we set up in lines 1-3. This allows us to use the same functions for both local and production deployments. And here is our precious Bash library:

 1  cookie_jar=/tmp/confluence-cookies.txt
 2  curl="curl --cookie-jar $cookie_jar --cookie $cookie_jar --output /tmp/curl-output.html"
 3  
 4  function confluence_login() {
 5    echo "login to confluence"
 6    $curl -s -S -d os_username=admin -d os_password=$admin_password "$confluence_url/login.action"
 7  }
 8  
 9  function confluence_uninstall_plugin() {
10    echo "uninstall plugin"
11    $curl -s -S $confluence_url'/admin/plugins.action?mode=uninstall&pluginKey=ourpluginkey'
12  }
13  
14  function confluence_install_plugin() {
15    echo "reinstall plugin ($plugin_jar)"
16    if [ \! -f $plugin_jar ]; then
17      echo "ERROR: $plugin_jar not found"
18      exit 1
19    fi
20    $curl -F file_0=@$plugin_jar\;type=application/octet-stream $confluence_url/admin/uploadplugin.action
21  }
22  
23  function quietmvn() { 
24      mvn $* | grep -v '\[WARNING\] POM.*Not a v4.0.0 POM' 
25  }

The three functions I was talking about use Curl to interact with Confluence as if our script was a web browser. This is not the way to automate installs that Atlassian recommends, which is to use Maven with some Atlassian plugins. We found that the Maven way was not as reliable as using Curl.

And this is all there is to it. It took some time to find the right way to invoke Confluence with Curl, but every minute spent was worth it. This small library of Bash commands allows you to perform installs and uninstalls of any plugin with maximum reliability. It takes away a lot of pain from Confluence plugin development.