Universal Design Principles

In the absence of design quality measurements, there is no objective way to prove that one design approach is better than another. Still, there are a few universal principles—which seem to apply to any programming language or platform—that point the way.

None of these ideas are my invention. How could they be? They’re all old, worn, well-loved principles. They’re so old you may have lost track of them amidst the incessant drum-beating over new fads. Here’s a reminder.

The Source Code Is the (Final) Design

Continue to sketch UML diagrams. Discuss design over CRC cards. Produce pretty wall charts on giant printers if you want. Abstractions like these are indispensible tools for clarifying a design. Just don’t confuse these artifacts with a completed design. Remember, your design has to work. That’s nonnegotiable. Any design that you can’t turn into software automatically is incomplete.

If you’re an architect or designer and you don’t produce code, it’s programmers who finish your design for you. They’ll fill in the inevitable gaps, and they’ll encounter and solve problems you didn’t anticipate. If you slough this detail work off onto junior staff, the final design could be lower quality than you expected. Get your hands dirty. Follow your design down to the code.

Don’t Repeat Yourself (DRY)

This clever name for a well-known principle comes from Dave Thomas and Andy Hunt. Don’t Repeat Yourself is more than just avoiding cut-and-paste coding. It’s having one cohesive location and canonical representation for every concept in the final design—anything from “the way we interface with a database” to “the business rule for dealing with weekends and holidays.”

Eliminating duplication decreases the time required to make changes. You need only change one part of the code. It also decreases the risk of introducing a defect by making a necessary change in one place but not in another.

Be Cohesive

This old chestnut is no less essential for its age.[63]

A cohesive design places closely related concepts closer together. A classic example is the concept of a date and an operation to determine the next business day. This is a well-known benefit of object-oriented programming: in OOP, you can group data and related operations into the same class. Cohesion extends beyond a single class, though. You can improve cohesion by grouping related files into a single directory, or by putting documentation closer to the parts of the design it documents.

Cohesion improves design quality because it makes designs easier to understand. Related concepts cluster together, which allows the reader to see how they fit together into the big picture. Cohesion reduces error by improving the reader’s ability to see how changes to one concept affect others. Cohesion also makes duplication more apparent.

Decouple

Different parts of a design are coupled when a change to one part of the design necessitates a change to another part. Coupling isn’t necessarily bad—for example, if you change the date format, you may have to change the routine to calculate the next business day.

Problems occur when a change to one part of the design requires a change to an unrelated part of the design. Either programmers spend extra time ferreting out these changes, or they miss them entirely and introduce defects. The more tenuous the relationship between two concepts, the more loosely coupled they should be. Conversely, the more closely related two concepts are, the more tightly coupled they may be.

Eliminating duplication, making designs cohesive, and decoupling all attack the same problem from different angles. They tie together to improve the quality of a design by reducing the impact of changes. They allow programmers to focus their efforts on a specific section of the design and give programmers confidence that they don’t need to search through the entire design for possible changes. They reduce defects by eliminating the possibility that unrelated parts of the design also need to change.

Clarify, Simplify, and Refine

If good designs are easy for other people to modify and maintain, then one way to create a good design is to create one that’s easy to read.

When I write code, I write it for the future. I assume that people I’ll never meet will read and judge my design. As a result, I spend a lot of time making my code very easy to understand. Alistair Cockburn describes it as writing screechingly obvious code:[64]

Times used to be when people who were really conscientious wrote Squeaky Clean Code. Others, watching them, thought they were wasting their time. I got a shock one day when I realized that Squeaky Clean Code isn’t good enough.

...

It occurred to me that where they went down a dead end was because the method’s contents did not match its name. These people were basically looking for a sign in the browser that would say, “Type in here, buddy!”

That’s when I recognized that they needed ScreechinglyObviousCode.

At the time [1995], it was considered an OK thing to do to have an event method, doubleClicked or similar, and inside that method to do whatever needed to be done. That would be allowed under Squeaky Clean Code. However, in ScreechinglyObviousCode, it wouldn’t, because the method doubleClicked only says that something was double clicked, and not what would happen. So let’s say that it should refresh the pane or something. There should therefore be a method called refreshPane, and doubleClick would only contain the one line: self refreshPane.

The people fixing the bug knew there was a problem refreshing the pane, but had to dig to learn that refreshing the pane was being done inside doubleClick. It would have saved them much effort if the method refreshPane was clearly marked in the method list in the [Smalltalk] browser, so they could go straight there.... The reading of the code is then, simply: “When a doubleClicked event occurs, refresh the pane” rather than “When a doubleClicked event occurs, do all this stuff, which, by the way, if you read carefully, you will notice refreshes the pane.”

Screechingly obvious code is easier to produce through iterative simplification and refinement. Bob Martin has a great example of this in “Clean Code: Args—A Command-Line Argument Parser.”[]

Fail Fast

A design that fails fast (see Chapter 13) reveals its flaws quickly. One way to do this is to have a sophisticated test suite as part of the design, as with test-driven development. Another approach is use a tool such as assertions to check for inappropriate results and fail if they occur. Design by Contract [Meyer] makes designs fail fast with runtime checks of class invariants and method pre- and post-conditions.

Failing fast improves design by making errors visible more quickly, when it’s cheaper to fix them.

Optimize from Measurements

Everyone can quote Tony Hoare about premature optimization,[65] at least until they reach their pet optimizations. Optimized code is often unreadable; it’s usually tricky and prone to defects. If good design means reducing programmer time, then optimization is the exact opposite of good design.

Of course, good design does more than just reduce programmer time. According to my definition, it “minimizes the time required to create, modify, and maintain the software while achieving acceptable runtime performance.”

Although well-designed code is often fast code, it isn’t always fast. Optimization (see Performance Optimization” in Chapter 9) is sometimes necessary. Optimizing later allows you to do it in the smartest way possible: when you’ve refined the code, when it’s cheapest to modify, and when performance profiling can help direct your optimization effort to the most effective improvements.

Delaying optimization can be scary. It feels unprofessional to let obviously slow designs slide by. Still, it’s usually the right choice.

Eliminate Technical Debt

You probably aren’t lucky enough to work on a project that follows these principles all the time. (I rarely am.) Even if you are, you’re likely to make mistakes from time to time. (I certainly do.)

Despite our best intentions, technical debt creeps into our systems. A lot of it—perhaps most of it—is in our designs. This bit rot slowly saps the quality of even the best designs.

Eliminating technical debt through continuous code maintenance and refactoring has only recently been recognized as a design principle, and some people still question it. It’s no less essential for the controversy. In fact, it may be the most important of all the design principles. By focusing on removing technical debt, a team can overcome any number of poor design decisions.



[63] Coupling and cohesion come from [Constantine]’s 1968 work on structured programming, “Segmentation and Design Strategies for Modular Programming.” In that article, he presents ways to analyze coupling and cohesion based on static code analysis. I’ve phrased his ideas more generally.

[65] “Premature optimization is the root of all evil.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset