Three of Diamonds
Updated to add Jon Jagger’s and David Allsopp’s links.
The number of people who have tried the Diamond Problem continues to grow. The original articles that I saw were:
Here are the additional ones that I know of:
- Philip Schwarz did the exercise in Clojure. At first he didn’t have tests, then he added some, and has since made other improvements. Check out his repository.
- John Rusk, @agilekiwi built a solution in C#. He prefers his single 24-line method over my more highly factored style.
- George Dinwiddie, @gdinwiddie also worked in Ruby, and took a very different design path, working less with the details of the patter, looking instead at things like how many lines it had. And he used RSpec.
- Sandro Mancuso, @sandromancuso provided an interesting version of the problem in Java.
- A late entry from Jon Jagger, @JonJagger uses a unique observation: “This is a squashed circle!”. Very interesting! Jon also offers this article, regarding slime.
- David Allsopp, @doublehelix gives us an example in Scala.
- Kevin Rutherford, rather than writing an example, wrote about the implications of TDD within a team, noting that these examples were done by individuals or, at best, with pairing.
I can’t resist mentioning that George’s solution, and probably the diamonds in the air, inspired me to write and tweet this version:
rows = "ABCDCBA"
cols = "DCBABCD"
rows.chars { |r|
l = ""
cols.chars {| c|
l << ((r==c)?r:"-")
}
puts l
}
Not to be outdone, Jonas Elfström (@jonelf) tweeted this one-liner:
A,Z=65,90;((A...Z).to_a+Z.downto(A).to_a).map{|r|puts (Z.downto(A).to_a+(A+1..Z).to_a).map{|c|c==r ?c.chr: " "}*""}
Many Solutions
It’s really interesting to see all these examples of ways to program this little pattern problem. Bill Tozier and I were talking at lunch and he said he would treat the problem as a string-rewriting one, producing each generation from the previous by changing the pattern in some simple repetitive way. He hasn’t done that example yet, nor have I but it’s kind of enticing. Consider this sequence:
A
-A-|B-B|-A-
--A--|-B-B-|C---C|-B-B-|--A--
There’s something interesting going on there, sort of like
- Wrap all your substrings in dashes;
- Duplicate the middle one;
- Insert a new middle one with the next letter twice with 2n+1 dashes.
So that might lead to yet another interesting solution. Surely there are more solutions and more styles than we’ve seen so far.
What you TDD is what you get
Everyone who used TDD seems to have done a different series of tests. Those tests will drive out different parts of the solution, and even with good refactoring, it seems likely that the solutions will have some fundamental differences.
It’s also clear that there are basic approaches that will differ. If you decide it’s a string-writing problem, you’ll get a different result from thinking of it as an array problem or a line generation problem.
I wonder if you could approach it as a cellular automaton Game of Life kind of thing …
If you don’t TDD
There are plenty of ways to program, and TDD is just one. TDD doesn’t completely constrain what you do, but it does tend to produce code that emphasizes whatever ideas you put into the tests. When I program without TDD, as I often do, the ideas I think about go into the code. With TDD, many of those ideas get expressed as tests, and the test-ideas and brain-ideas both go into the code.
I suspect that my TDD code and pure brain code are qualitatively different, and very likely quantitatively different as well. I’d expect to see longer methods without TDD, for example, but I’ve never looked. Are there important differences between TDD’d code and non-TDD? Are there some things that tend to be better one way or the other? Are there others that go the other way? Or do all the differences come down to the individuals doing the work?
We don’t know, and it would probably be good if we did.
Understandability
Take a look at the examples linked above, and see what you think about them. Imagine you were just hired to maintain the software you see before you. Would you be thinking “Ah, this is clear”, or would you be looking for the phone number of the person who wrote it … or for their address, to pay a little call on them in the dark of night? If you see what I’m saying.
I don’t know Clojure, which certainly made Philip’s solution harder to grok, but one can sort of read it. He added some tests later, which certainly helps.
But there’s more to it than that, isn’t there? Some people like to see things in large functions of ten or twenty or more lines. Some people like tiny methods. We don’t even all agree on what to name the variables, and surely what we’re used to.
I do find real TDD tests to be helpful, since they show the reasoning that went into creating the code. To make that work, they need to appear in some reasonable order, typically easiest first.
But in his article, Seb Rose speaks of recycling unit tests. He writes a simple test, makes it work, then “recycles” it to make it a more complete test, driving the design forward. I’m not sure why he does that instead of just adding a new test. I do know that some people find that their old tests begin to fail and have to be rewritten or removed. That happens to me sometimes, as well.
I think that when that happens, it’s because my test doesn’t say something universally true about the problem and solution. Often it knows too many details about the solution, like my test for the start and stop values in the first diamond article, which has way too much information about how the program works. But compare with George’s tests, which express rather universal truths like how many lines there will be. Those aren’t going to need much change.
Whatever the reason, if we delete or recycle tests, our reader will be left out of some of our thinking. That will make the program harder to understand, no matter how clear our code is. The tests tell us what we wanted as we went along, and they trace our thinking.
Conclusions, I have none …
Fortunately, I have no point to prove here, so I don’t have to draw a conclusion. I’m trying to shine a light on practices, so maybe it’ll suffice to finish with some observations:
- TDD Narratives like mine and George’s make it a lot easier for me to understand the program. It’s not as good as pairing but it’s better than tests and code alone.
- Tests plus code is easier for me to understand than just code, even if the tests are after the fact and not TDD.
- Code that explicitly expresses intention, and code that is more modular, with smaller methods, is generally easier for me to understand.
- A plain narrative is easier for me to understand than just seeing the code.
- Just seeing the code is often quite confusing to me. Even knowing what it was supposed to do, I often had trouble seeing how it worked, and more trouble seeing why it was done that way.
Your mileage may vary. I look forward to seeing more about diamonds, and will of course be writing more or less randomly about other TDD type things. Thanks for reading!