Wednesday, 21 January 2009

KataArgs and clean code

Over Christmas I finished reading the book "Clean Code" by Robert C. Martin. I thoroughly recommend the book, which is highly practical, technical and well written. In it, Bob seeks to present the "Object Mentor school of clean code", as he puts it, "in hideous detail".

The book is full of code examples, clean and less clean, and detailed advice about how to transform the latter into the former. All the examples are written in Java, though, which leaves me wondering a little if "clean code" in the Object Mentor meaning of the word, looks the same in other languages.

In Chapter 14 of the book, there is a fully worked example of a little coding problem that I would call a code Kata. It's a little program for parsing command line arguments. I know, there are loads of libraries that do this already. But never mind. It's a non trivial problem yet small enough to code up fairly quickly. One thing that caught my attention was the footnote on page 200, just after he has presented his best Java solution to the Kata. "I recently rewrote this module in Ruby. It was 1/7th the size and had a subtly better structure." So where is the code, Bob? What is this subtly better structure?

I had nothing better to do on Boxing Day than sit around and digest leftover-turkey-curry, so I sent a little mail to Bob asking him for the code. To my delight, I got a mail back only a few hours later, with a message that I was welcome to it, and the url to where he'd put it on github. Evidently Bob also had time on his hands on Boxing Day.

I have had a look at the Ruby code, and although my Ruby is fairly ropey, I think I can follow what it does (surely a sign of clean code?). The design is very similar to the Java version presented in the book, with a couple of finesses. (The next part of the post will make most sense if you first look at Bob's Java version and Ruby version).

The first finesse I spotted, is that the Ruby version defines the argument "schema" in a much more readable fashion. Rather than "l,p#,d*" as in the Java version, it reads


parser = Args.expect do
boolean "l"
number "p"
string "d"
end

ie the program expects three flags, l,p, and d, indicating a boolean, number and string respectively. You can do this in Ruby but not Java because the language allows you to pass a code block to a method invocation. (the stuff between "do" and "end" is a code block, and the class "Args" has a method "expect") I think the Ruby version is rather more readable, don't you?

The second finesse I can see is that the argument marshallers dynamically add themselves to the parser, rather than being statically declared as in the Java version. This means that if you discover a new argument type that you want to support, in the Java version you have to crack open the code for Args.java and add a new case statement in the "parseSchemaElement" method, as well as adding the new argument marshaller class. In the Ruby version, you just add the new class, no need to modify an existing one. Bob invented the Open-Closed principle, so I guess it's not so surprising to see him following it :-)

So in Args.java:


private void parseSchemaElement(String element)
throws ArgsException {
char elementId = element.charAt(0);
String elementTail = element.substring(1);

// long if/else statement to construct all the marshallers
// cut for brevity
[...]
else if (elementTail.equals("#"))
marshallers.put(elementId, new IntegerArgumentMarshaller());
else if (elementTail.equals("*"))
[...]

or in the Ruby code, each marshaller just tells the parser to add itself:


class NumberMarshaler
Parser.add_declarator("number", self.name)
[...]

in the Parser class:

def self.add_declarator(name, marshaler)
method_text = "def #{name}(args) declare_arguments(args, #{marshaler}) end"
Parser.module_eval(method_text)
end

def declare_arguments(args, marshaler)
args.split(",").each {|name| @args[name] = marshaler.new}
end


You can do this in Ruby but not Java, since in Ruby you can dynamically construct and execute arbitrary strings as code, and add methods to classes at runtime. (The string declared as "method_text" is constructed with the details of the new marshaler, then executed as Ruby code in the next line, by Parser.module_eval) This is an example of metaprogramming.

So it seems to me that the "subtly better structure" that Bob refers to in his footnote, is made possible by powerful language features of Ruby, such as metaprogramming and closures.

Of course my favourite programming language is Python, which also has these powerful language features. I am rather interested to see if I can come up with an equally clean solution in Python. I am also interested if any hotshot Java or Ruby programmers out there can improve on Bob's solutions. To this end, I have added a description of this Kata to the catalogue on
codingdojo.org. We had a go at it at our last GothPy meeting, without any great success, although I hope we might do better at a future meeting.

So please have a go at KataArgs and see if you can write some really really clean code. Do let me and the community on codingdojo.org know how you get on!

No comments:

Post a Comment