Design Tips

In a room full of top software designers, if two agree on the same thing, that's a majority. -- Bill Curtis [source]

Overview

Most hard-core project courses place much emphasis on the design of the system. Here are some things to keep in mind when designing your system.

Tips

Abstraction is your friend

A software design is a complex thing, especially, if you do not use abstraction to good effect. The design should be done at various levels of abstraction. At higher levels, you visualize the system as a small number of big components while abstracting away the details of what is inside each component. Once you have a good definition of what each of those components represent and how they interact with each other, you can move to the next lower level of abstraction. That is, you take each one of those components and design it using a small number of smaller components. This can go on until you reach a lower level of abstraction that is concrete enough to transform to an implementation.

Separate different concerns

For example, parsing (and things related to the "parsing" concern) should be done by the parser component, and everything that has to do with sorting should be done by the sorter component.

Don't talk to strangers (aka law of Demeter)

Keep unrelated things independent of each other. For example, if the parser component can function without any knowledge of the sorter component, then the sorter is a stranger to the parser. That means the parser should not have any reference to the sorter (E.g., can you compile the parser without compiling the sorter?)

A classic example where this principle applies is when choosing between a central controller model and chain of controllers model. The former lets you keep strangers as strangers, while the latter forces strangers to talk to each other. Notice how one design below let B and C remain strangers while the other forces them to know each other.

Along the same vein, minimize communication between components. Avoid cyclic dependencies (e.g., A calls B, B calls C and C calls A) 

keep your secrets safe

A component should reveal as little as possible about itself. This is also known as information hiding. For example, other components that interact with your component should not know how your component store certain data and they should not be allowed to manipulate those data directly.

Preserve the conceptual integrity

Fred Brooks contends that (in The Mythical Man-Month, page 42)

... the conceptual integrity is the most important consideration in system design. It is better for a system to omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas.
A student team is a team of peers that usually does not have a designated architect to dictate a design for others to follow. Everyone may want to have their say in the design. However, after discussing all alternative designs proposed you should still choose one of them to follow, rather than devise a design that combines everyone's designs (sometimes combining ideas into one design has merits, but do not do it just for the sake of achieving a compromise between competing ideas). If in doubt, get your supervisor's opinion as well.

Standardize solutions

Similar problems should be solved in a similar fashion. Sometimes it pays to solve even slightly different (yet largely similar) problems in exactly the same way. It makes programs easier to understand. In other words, do not go out of your way to customize a solution to fit a problem precisely; It may be better to preserve the similarity of the solution instead.

Use patterns

Patterns (design patterns, as well as other types of patterns such as analysis patterns, testing patterns, etc.) embody tried-and-tested solutions to common problems. Learn patterns and use them where applicable.

Don't overuse patterns

Most patterns come with extra baggage. Do not use patterns for the sake of using them. For example, there is no need to apply the Singleton pattern to every class that will have only one instance in the system; use it when there is a danger of someone creating multiple instances of such a class.

Value simplicity

Simple yet elegant designs are much better than complex (and hence ugly) solutions; The former is much harder to achieve however, but that's what you should aim for. Given any design, try to see whether you can simplify it. Resist any change that makes it more complex.

Try to make the design as simple as possible (but no simpler!).

Increase Fan-in, decrease Fan-out

When many other component use a given component (i.e., the component has high fan-in), that is a good thing because it increases reuse. When a given component uses many other components (i.e., it has high fan-out), that is not a good thing because it increases coupling.

Do not underestimate the power of brute force

Some problems have an obvious and simple brute force solution. Do not dismiss this solution too quickly in your haste to look for a smarter solution. If you can afford it, you this brute force solution a chance; it may be all you need.

Less is more

Trim all parts of the design that is not immediately useful to the system. It does not matter how elegant that part is, how proud you are of dreaming it up, and how hard you worked at it. The same applies to code.

Do not forget non-functional qualities

some NF qualities need to be incorporated from the design stage. One NF quality rarely mentioned in the spec and often forgotten in the design is the testability. Improving testability improves many other qualities of the design.

beware of Premature optimization

Hoare wasn't kidding. Opt for a simple design. If it is fast enough, stick with it. If it is not, find the bottlenecks (using profiling tools) and optimize accordingly.

Caveat: this does not mean that you should start with a stupid design. Some designs are obviously inefficient and should be discarded immediately. Start with a design that is as simple as possible, but no simpler.

Avoid Analysis paralysis

During analysis and design, consider all the known facts but do not fret about all unknowns. If you are given a concrete and stable specification (e.g., writing a parser for a given language) it would be stupid to start with a design that does not take into account all details of the given specification. Such short-sighted designs will eventually require change, causing rework that should have been avoided. On the other hand, if you are defining a first-of-a-kind exploratory system for an unspecified user base, go for a design with a reasonable degree of flexibility; do not worry about all nitty-gritty details that it might (or might not) have to face later.

Keep the documents rough for a while

Documenting desing often requires wrestling with UML editors (and other graphical tools). Therefore, it is very frustrating when we have to modify those documents as the design change over time.

While designs should be documented as they are done, there is no need to start creating well-polished design documents right away. You can keep the documentation as low-maintenance rough sketches (with none of the important points missing) until the design is sufficiently stable. For example, you can take a photo of the whiteboard on which you drew the initial design, print it out, do your (minor) modifications on the hard copy, and convert it to a computerized UML diagram much later.

Grading tips

Further resources

Giving feedback

Any suggestions to improve this book? Any tips you would like to add? Any aspect of your project not covered by the book? Anything in the book that you don't agree with? Noticed any errors/omissions? Please use the link below to provide feedback, or send an email to damith[at]comp.nus.edu.sg

Sharing this book helps too!

 

---| This page is a part of the online book Tips to Succeed in Software Engineering Student Projects V1.9, Jan 2009, Copyrights: Damith C. Rajapakse |---

free statistics free hit counter javascript