36

The Four Elements of Simple Design

4 years ago

I realize that I’ve never really written this down before, and I say it so frequently in my work as a trainer and mentor, that I think it bears repeating.

Updated December 11, 2013! After you read this article, you’ll almost certainly want to read this article next.

I have reduced everything I’ve ever learned about effective object-oriented design to the four elements of simple design that I first learned from Kent Beck’s work. Maybe you can, too.

I define simple design this way. A design is simple to the extent that it:

  1. Passes its tests
  2. Minimizes duplication
  3. Maximizes clarity
  4. Has fewer elements

Note that I put these properties in priority order. I’m willing to copy-and-paste to get a test passing, but once the test passes, I can usually remove the duplication quickly. I’m willing to extract code into a method and call it foo() in order to get rid of duplicate code, although that name foo() rarely survives more than 15 minutes. Finally, I will gladly introduce interfaces, classes, methods and variables to clarify the intent of a piece of code, although generally speaking once I make things more clear, I can find things to cut.

Some people put “minimize duplication” and “maximize clarity” in a tie for second place. I don’t. My experience has led me to conclude that removing duplication helps more than fixing bad names does. Moreover, removing duplication tends to allow a suitable structure to emerge, whereas bad names highlight an inappropriate distribution of responsibilities. I use this observation as a key element of my demonstration, “Architecture Without Trying”.

Now I should point out that, as a test-driven development (and now also a behavior-driven development) practitioner, I write tests as I draw breath, so I don’t really need to emphasize that part.

  1. Passes its tests
  2. Minimizes duplication
  3. Maximizes clarity
  4. Has fewer elements

I should also point out that I’ve yet to see a codebase with low duplication and high clarity that, nonetheless, had considerably more design elements than it needed, so I don’t really need to emphasize that part, either.

  1. Passes its tests
  2. Minimizes duplication
  3. Maximizes clarity
  4. Has fewer elements

As some of you have commented, while we can generally agree on what constitutes duplication, “clarity” remains up for intense debate. I have developed over the years a few heuristics related to code clarity that have helped me a great deal, and they relate mostly to names. While I could probably write a long pamphlet or short book on the topic, let me start with one example: a process for improving names.

Names tend to go through four stages: nonsense, accurate, precise, meaningful. Laziness or ignorance push us towards the left end of this spectrum, while with diligence we can move to the right. I contend that names further to the right of this spectrum provide more clarity. This, too, matches Kent Beck’s original conception of this idea, as he referred to “intention-revealing names”.

When I find fifteen lines of duplicate code, I start by extracting them to a new method, and since I probably don’t yet know what those lines of code do yet, I name the new method foo(). After around 15 minutes of working in the same area, I begin to understand what this method does, so I give it an accurate name, such as computeCost(). As I try to write tests for this new method, and find it more tedious than I expected, I realise that it does more than just compute the cost of an item, so I rename it more precisely: findItemThenComputeCost(). The mere appearance of a conjunction, like “and”, “or”, “but” or “then” tells me that the method has more than one responsibility, and as a result, I soon find myself writing tests that duplicate irrelevant details. When I remove this duplication, I have to split this method into two: one that finds the item and the other that computes the cost. When I look at computeCost more closely, I see that it really adds taxes and shipping charges to the net price of an item, which leads me to split that behavior into multiple methods, each that applies a charge to an order item. This leads me to introduce the more meaningful OrderItem class and the OrderItemCharge interface with method applyTo(item) and implementations PrinceEdwardIslandSalesTax, CanadianGoodsAndServicesTax and DomesticShipping. These more meaningful names communicate the story of the design much better than the previous computeCost() name did. You can find similar examples in your code bases of the movement towards meaningful names. Most of the time, I struggle to reach the accurate and precise stage, then I find the jump to meaningful names quite easy. I have found that most clarity issues reduce to misleading names.

That leaves me with two key elements of simple design: remove duplication and fix bad names. When I remove duplication, I tend to see an appropriate structure emerge, and when I fix bad names, I tend to see responsibilities slide into appropriate parts of the design.

I claim these to be axioms of modular design, with a “parallel postulate” of whether you use objects or not. If you use objects, you get object-oriented design, and if you don’t, you get structured design. (I don’t know how functional design fits into this yet, because I haven’t done enough of it.)

I claim that developing strong skills of detecting duplication, removing duplication, identifying naming problems, and fixing naming problems equates to learning everything ever written about object-oriented design.

Put more simply, if you master removing duplication and fixing bad names, then I claim you master object-oriented design.

Now I wouldn’t bother burning your old OOD/OOP books, but I will tell you that if you have an interested buyer, then feel free to sell them.

Now that you’ve read this article, you’ll almost certainly want to read this article next.

Reactions

This entry was posted in Featured and tagged . Bookmark the permalink. -