Greed and Simple Design
Some people like Carlo say that the famous Four Elements of Simple Design by Kent Beck are an oversimplification. Perhaps it’s true, but still I find that they are a very useful compass. Consider again:
A design is simple when
- Runs all the tests.
- Contains no duplication
- Expresses all the ideas you want to express.
- Minimizes classes and methods
in this order.
Rule 2 is important, as it pushes us to invent abstractions that capture recurring patterns. But rule 3 is also imporant, as it pushes us to invent abstractions that correspond to the ideas that we want to express.
The other day I saw this post by Luca about a fun kata: implementing the scoring rules for a dice game called “Greed”. This exercise is part of the Ruby Koans, but its use as a programming exercise dates at least from the OOPSLA ’89 conference, when Tom Love proposed a contest to show how a program could be written in different ways and in different languages.
A little research shows many solutions for this problem. As this problem is presented in the context of a Ruby programming exercise, people usually tries clever tricks that exploit peculiar Ruby idioms. For instance:
def score(dice) (1..6).collect do |roll| roll_count = dice.count(roll) case roll when 1 : 1000 * (roll_count / 3) + 100 * (roll_count % 3) when 5 : 500 * (roll_count / 3) + 50 * (roll_count % 3) else 100 * roll * (roll_count / 3) end end.reduce(0) {|sum, n| sum + n} end
http://stackoverflow.com/a/6742129/164802
There’s a place for this sort of exercises, but it’s not the sort of programming that I would like my collegues to practice! If we apply the rule 3, I expect to see in the source cose some mention of the *rules* of the game. I expect that there’s a programming element that corresponds to the rule that “three ones are worth 1000 points”, etc. Really, it does not take all that much more effort, and I assert that it’s more fun to code expressively!
This is my solution:
class Array def occurrences_of(match) self.select{ |number| match == number }.size end def delete_one(match) for i in (0..size) if match == self[i] self.delete_at(i) return end end end end def single_die_rule(match, score, dice) dice.occurrences_of(match) * score end def triple_rule(match, score, dice) return 0 if dice.occurrences_of(match) < 3 3.times { dice.delete_one match } score end def score(dice) triple_rule(1, 1000, dice) + triple_rule(2, 200, dice) + triple_rule(3, 300, dice) + triple_rule(4, 400, dice) + triple_rule(5, 500, dice) + triple_rule(6, 600, dice) + single_die_rule(1, 100, dice) + single_die_rule(5, 50, dice) end
There's some more duplication that could be removed (the five similar rules could be expressed as a single rule) and the names could be improved, but I think this is the way to go. Make your code look like a model of the problem!
January 21st, 2012 at 17:21
I usually invert the rule 2 and 3 to get tests-expression-no_duplication-minimal:
http://c2.com/cgi/wiki?XpSimplicityRules
while assuming there is a decreasing order of importance from rule 1 to 4.
In fact, if there is such an order, your code wins over the idiomatic Ruby example because it favors expressiveness over elimination of duplication (exhaustive list of rules) and minimality (four methods instead of one).
January 21st, 2012 at 18:30
Although I said something slightly different, I’ll bite your bait :-).
Simplicity is one of the many informal-yet-useful properties of a good design. It’s just not enough to declare a design “good”.
Honestly, even as far as simplicity is concerned, I find the 4 principles lacking.
#1 can be trivially satisfied by writing few or no tests :-)
#2 is not a measure of simplicity, per se. We usually remove duplication by abstraction. Some people find abstract code harder to read (I normally don’t, but I’ve met many).
#3 is too fluffy, which is unfortunate (see below)
#4 is stacking the cards against structural complexity (while still ignoring coupling altogether) but says nothing against procedural complexity. So, a single long method with no duplication and a huge switch/case inside would score as “simpler” than a number of small classes with short methods.
To be fair, the compensating force for #4 is #3, as “expressing ideas” should push you to introduce concepts, through classes and methods. Unfortunately, #4 is easy to grasp and calculate, #3 is ambiguous and fluffy, so they don’t really balance out. That’s particularly bad for people in early stages of skill acquisition, who lack the maturity to understand nuanced concepts.
Overall, the entire set of principles is heavily biased toward design-as-code, henceforth preventing any useful design conversation at larger scale. This is something I discussed in an older post (http://www.carlopescio.com/2011/02/is-software-design-literature-dead.html)
Useful compass? Perhaps. Still, design is way more than that :-)
January 22nd, 2012 at 15:37
Hi Carlo, thanks for biting. I agree that simplicity is not an end in itself; but in the context of Extreme Programming, is the enabling factor that makes it possible for Extreme Programming to make sense :-) Once you have simplicity, you are in a good position to obtain your other goals, whatever they are. I must address some of your comments below:
These 4 elements are meant as a compass for people doing Extreme Programming. In this context you don’t even have to mention “no tests”. In general, the real meaning of this first rule is that “working code is worth more than non-working code, no matter how beautifully designed.” Working code driven by test is base one. Almost everyone learning XP is able to get to this stage. The difficul bit lies in satisfying the later rules :-)
I disagree. Simple is not easy. Finding the right way to remove duplication is not always easy, and it’s not easy because there might be more than one way, and some ways are worse than others. It may happen that you in removing duplication we add complexity; this usually means that we have not found the right abstractions, or that we have not finished removing duplication.
It is true that #3 cannot be measured as easily as the other rules. So be it. Nobody promised that we could find the best design by a mechanical search in the space of possible solutions. We have to think; we have to decide what ideas we want to express. This is why it is not true that a single, monolithig procedure would score “simpler”. Rule #4 is a warning against letting our ego run free with unnecessary abstraction or generality.
True. I’m beginning think that the unspoken assumptions behind XP was that everyone had the design and coding skills of Ward Cunningham. Kent Beck wrote plenty about how to code effectively in SmallTalk, but wrote very little (in comparison) on how to code effectively in XP.
I find that reasoning about and practicing the consequences of these 4 rules is an essential step towards becoming proficient at TDD.
But Carlo, since you say that design is way more than these four rules, what would *you* say about the solutions such as the one above, and the many other that can be found on Stackoverflow? What would you say to these students from the perspective of your knowledge of design?
January 22nd, 2012 at 15:43
@Giorgio: (re)read what Ron Jeffries has to say about this:
http://groups.google.com/group/software_craftsmanship/msg/84106f8a09a9486c
January 22nd, 2012 at 18:38
I won’t go into a word-by-word dissection of what we said :-), in the end you’re choosing to read the 4 principles favorably, I don’t, therefore we come to different conclusions. That’s one of the many issues with a discipline stuck on “principles” (I have a long-due, half-baked post about this, waiting to be finished, someday).
For instance, you say “This is why it is not true that a single, monolithig procedure would score “simpler””. I say that it would according to principle 4, and that principle #3 is too fluffy to offer true guidance for those who need it. We could argue forever, and yet the discipline itself would make little or no progress.
About the code. The problem with small-scale “code katas” (or even worse, small-scale code katas within the context of a specific language), is that we easily slip into the mindset of solving the problem through clever usage of language features, like map-reduce. In this sense, I’m relatively skeptical about their contribution when your goal is to explore the design space.
Still, you probably remember that I solved a similar problem (yahtzee, http://www.carlopescio.com/2011/06/cut-red-wire.html), explaining my reasoning while writing code. I would probably point people in that direction (including the “limits and confessions” at the end).
But again, design is way more than that. It’s about understanding forces, materials, choices, consequences, recognizing familiar structures, exploring different venues for the fun of it, etc etc. Constraining all that within a few principles is terribly sad :-)).
Curiously enough, though we tend to disagree on many things here and there, your final suggestion (“Make your code look like a model of the problem!”) is very much aligned with the DSL approach I took for yahtzee. Again, I see this agreement/disagreement mostly as the inability of contemporary design narrative to faithfully represent our (largely shared) sensibility for “good design”.
February 22nd, 2012 at 23:15
Good code, like good art, should beautiful and simple and pure.