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

  1. Runs all the tests.
  2. Contains no duplication
  3. Expresses all the ideas you want to express.
  4. 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!

6 Responses to “Greed and Simple Design”

  1. Giorgio Sironi Says:

    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).

  2. Carlo Pescio Says:

    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 :-)

  3. matteo Says:

    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:

    #1 can be trivially satisfied by writing few or no tests :-)

    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 :-)

    #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).

    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.

    #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.

    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.

    That’s particularly bad for people in early stages of skill acquisition, who lack the maturity to understand nuanced concepts.

    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?

  4. matteo Says:

    @Giorgio: (re)read what Ron Jeffries has to say about this:

    http://groups.google.com/group/software_craftsmanship/msg/84106f8a09a9486c

  5. Carlo Pescio Says:

    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”.

  6. David Semeria Says:

    Good code, like good art, should beautiful and simple and pure.

Leave a Reply