On Simplicity
My first attempt at a TDD kata1 was Roy Osherove’s String Calculator.
I’m a fan of Spock, which enhances JUnit tests (which must be written in Groovy) by providing a load of syntactic sugar to help with (for example) parameterised tests and mocking/stubbing. I decided to write the implementation in Groovy as well.
My full test and implementation are in this Gist, but I’ll highlight a few of the steps I went through.
I wanted my tests to read as cleanly as possible, so I went pretty much straight to this:
class StringCalculatorSpec extends Specification {
def "add of empty string is 0"() {
expect:
add('') == 0
}
def add(expression) {
new StringCalculator().add(expression)
}
}
with a trivial implementation following closely behind:
class StringCalculator {
int add(String expression) {
0 // implicit 'return'
}
}
I perhaps shouldn’t have created that add
helper method until I’d written a second test, but it looked nicely readable.
Next test:
def "add of number is itself"() {
expect:
add('2') == 2
}
and solution:
def add(String expression) {
if (expression.empty) {
0
} else {
Integer.parseInt(expression)
}
}
Simplest thing possible? I reckon so. Next test…
def "add of two numbers is their sum"() {
expect:
add('1,2') == 3
}
Now it got a bit more interesting. This is what came to my mind as the simplest possible way to split a string, parse its parts and sum them:
expression.split(',')
.collect { s -> Integer.parseInt(s) }
.sum(0)
I thought about this for a few moments, and decided to swap the functional style2 for a good-old-fashioned explicit loop instead:
def sum = 0
for (String string : expression.split(',')) {
sum += Integer.parseInt(string)
}
sum
(You might point out that I’ve now implemented the addition of n numbers rather than just 2, so I’ve gone further than the test requires3, but the code to check the length of the array to decide what to do would be really ugly, so let’s just skip over that particular debate.)
Here’s what bothers me: why did I feel that, at some level, the procedural code was “simpler”? Is internal iteration “simpler” than external iteration? Certainly, for people unused to the functional programming paradigm, it’ll take a moment longer to understand when reading it, but if we assume the reader is reasonably familiar with functional concepts, aren’t they pretty much the same?
If I had just needed to sum a list of integers, I think it would have been clearer which approach is simpler and more readable:
def sum = 0
for (int number : numbers) {
sum += number
}
sum
// versus
numbers.sum(0)
So, what’s the difference when I add in the string-to-integer conversion?
I think it’s the use of higher-order function (in this case, collect
). Yes, it makes for fairly succinct, side-effect-free code. But it still feels more complex. Perhaps that’s just because I’m so accustomed to seeing code which creates a variable, modifies it while looping over something, and returns the result. If I’d never seen that style before, perhaps I’d have to think about it a bit harder in order to understand it.
What if the parsing of an integer were less verbose? That Integer.parseInt() is a bit clunky… I could statically import the method. Or, Groovy adds a toInteger()
instance method to String
, so we could do this:
expression.split(',')
.collect { s -> s.toInteger() }
.sum(0)
Using the implicit closure parameter name it
:
expression.split(',')
.collect { it.toInteger() }
.sum(0)
I think that’s a little nicer. Groovy also has a “spread operator” *.
which allows you to call a method on each element of a collection, returning the collection of results. It also happens to add a toInteger()
method to String
. So, I can do this:
expression.split(',')
*.toInteger()
.sum(0)
// it’s now short enough to fit comfortably on one line:
expression.split(',')*.toInteger().sum(0)
So far, so concise. But all we’re really doing is increasing the density of our instructions. Sugary syntax is great, sometimes; but unless you are reading code like this all the time, you probably still have to consciously translate each part of the expression. If we’re concerned that it’s the actual concept that’s a little complex, using a shorter syntax isn’t going to make it any better.
What if we split it into steps, naming the intermediate results?
def stringParts = expression.split(',')
def numbers = stringParts.collect { it.toInteger() }
numbers.sum(0)
To me, that’s worse.
So, what’s my point? Aside from the observation that it’s surprisingly hard to stick to the principle of coding the “simplest possible thing”, I think there’s a reasonable argument that “simplicity” is somewhat subjective, as is “readability”. Personal preferences and familiarity with different styles will always come into play. When you’re coding and you can’t choose between a couple of different ways of writing the same instructions, get a second opinion.
What do you think? Which of the above is “simplest”? or “most readable”? Is there a difference between the two?
-
Code Kata is an idea from “Pragmatic” Dave Thomas http://codekata.com/
↩ -
If you’re not familiar with Groovy, “collect” is a method on collections which accepts a transform function (in our case, one which converts a string to an integer) and applies it to each element of a collection, returning a new collection. [Many languages call this function “map”](https://en.wikipedia.org/wiki/Map_(higher-order_function) and I wish Groovy did, too.
↩ -
A common principle in TDD is to “write the simplest possible thing to make the test pass”.
↩