Simple is a very subjective word. But is Simple Design as well equally subjective?
Following is what dictionary.com has to say about the word “Simple”:
- easy to understand, deal with, use, etc.: a simple matter; simple tools.
- not elaborate or artificial; plain: a simple style.
- not ornate or luxurious; unadorned: a simple gown.
- unaffected; unassuming; modest: a simple manner.
- not complicated: a simple design.
- not complex or compound; single.
- occurring or considered alone; mere; bare: the simple truth; a simple fact.
- free of deceit or guile; sincere; unconditional: a frank, simple answer.
- common or ordinary: a simple soldier.
- not grand or sophisticated; unpretentious: a simple way of life.
- humble or lowly: simple folk.
- inconsequential or rudimentary.
It turns out that some of these adjectives define the characteristics of a Simple design very well:
- easy to understand, deal with: communicates its intent.
- is clear or has clarity
- not elaborate or artificial; plain: crisp and concise
- helps you maintain clear focus
- is unambiguous
- not ornate or luxurious; unadorned: minimalistic; least possible components (classes and methods).
- unaffected; unassuming; modest: does not have unanticipated side-effects.
- not complicated: avoids unnecessary conditional logic.
- not complex or compound; single: just does one thing and does it well.
- occurring or considered alone; mere; bare: to the point.
- free of deceit or guile; sincere; unconditional: abstracts implementation from intent, but does not deceive someone by concealing or misrepresenting the actual concept.
- common or ordinary: built on standard patterns which are well understood.
- not grand or sophisticated; unpretentious: fulfills today’s needs without unnecessary bells and whistles (over-engineering).
- humble or lowly.
- inconsequential or rudimentary: does not draw your attention to unnecessary details; achieves good abstractions
What is Simple Design?
A design that allows you to keep moving forward with least amount of resistance. Its like traveling light; low up-front investment, and not much to slow you down when you want to change. Its like clay in the hands of an artist. Simple is a direction (dynamic) not a location (static). To achieve this:
- Do the Simplest thing that could possibly work. In this context the “doing” is very important; just thinking will not help.
- YAGNI – You Aren’t Gonna Need It. Don’t design for something that isn’t needed today. Think about the future, but test, code and design for today’s needs. Don’t design for future’s complexity that may not happen or change.
- The use of Design Patterns contributes to the simplicity by using standard constructs and approaches that have been observed over many years of software development.
- Code Smells have a wealth of knowledge on symptoms of rotting design. Being aware of them is very important for every programmer.
- Similarly, the Unix Programming Philosophy and good Object-Oriented design principles will guide the code to be simple and maintainable.
- Simple Design and Test Driven Development (TDD) go hand in hand. Since the code is written to make the test pass, it tends to be more focused and much simpler. Check out: Smells in Test that indicate Design problems.
You know you have achieved a Simple Design when: (the official scoop):
- The System Works: all the tests are passing.
- Communicates Well: expresses every idea that we need to express.
- Contains no duplication: says everything Once-And-Only-Once and follows Don’t Repeat Yourself (DRY) principle.
- Has no superfluous parts: is concise. Has the least possible number of classes and methods without violating the first 3 guideline
I would like to add a 5th guideline here. If any developer on your team cannot draw (explain) the design in a couple of minutes, there is scope for simplification.
In my experience design is a very involved activity. Every now and then, one needs to make trade-off decisions. Some of the guiding principles I use while designing (listed below), do tend to compete and forces me to make a balanced trade-off decision. Sometimes I make the wrong decision, but Refactoring gives me another chance to set it right.
- Lessons learnt from The Art of Unix Programming
- Modularity: Write simple parts connected by clean interfaces
- Clarity: Clarity is better than cleverness.
- Composition: Design programs to be connected to other programs.
- Separation: Separate policy from mechanism; separate interfaces from engines
- Simplicity: Design for simplicity; add complexity only where you must
- Parsimony: Write a big program only when it is clear by demonstration that nothing else will do
- Transparency: Design for visibility to make inspection and debugging easier
- Robustness: Robustness is the child of transparency and simplicity
- Representation: Fold knowledge into data so program logic can be stupid and robust
- Least Surprise: In interface design, always do the least surprising thing
- Silence: When a program has nothing surprising to say, it should say nothing
- Repair: When you must fail, fail noisily and as soon as possible
- Economy: Programmer time is expensive; conserve it in preference to machine time
- Generation: Avoid hand-hacking; write programs to write programs when you can
- Optimization: Prototype before polishing. Get it working before you optimize it
- Diversity: Distrust all claims for “one true way”
- Bob Martin’s OO Design Principles: SOLID
- Single Responsibility Principle (SRP): There should never be more than one reason for a class to change.
- Open Closed Principle (OCP): A module should be open for extension but closed for modification
- Liskov Substitution Principle (LSP): Subclasses should be substitutable for their base classes or Design by Contract
- Interface Segregation Principle (ISP): Depend upon Abstractions. Do not depend upon concretions. Abstractions live longer than details.
- Dependency Inversion Principle (DIP): Many client specific interfaces are better than one general purpose interface or Narrow Interface
- OAOO – Once and only once: Mercilessly kill duplication. Whether its code duplication or conceptual duplication. It all gets in the way sooner or later.
- DRY – Don’t Repeat yourself: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. DRY is similar to OAOO, but DRY applies to effort as well, not just code.
- Tell Don’t Ask: As the caller, you should not be making decisions based on the state of the called object which then results in you changing the state of some other object. The logic you are implementing is probably the called object’s responsibility, not yours. For you to make decisions outside the object violates its encapsulation.
- The Law of Demeter: Any method of an object should only call methods belonging to:
- any composite objects
- any parameters that were passed in to the method
- any objects it created
- Triangulate: When you are not sure what the correct abstraction should be, instead of pulling out an abstraction upfront, you get the second case to work by duplicating and modifying a small piece of code. Once you have both the solutions working, find the “generic” form and create an abstraction.
- Influence from Functional Programming:
- Separate Query from Modifier: always separate methods which have side-effects from those which don’t. If possible make the method signature express that. And if you really want to spice-up things a bit, try having side-effect free methods and classes as much as possible.
- Prefer immutable objects over objects whose state changes after construction. Better for concurrency and better for freely passing them around.
Also don’t forget: