You Need to Have an Outstanding Design to Write Awesome Code
If you design it right, it will last forever.
Broadly speaking there can be six phases in every project
· Search for the root cause
· Punishment of the innocent
· Praise and honors for the non-participants
The “search for the root cause” phase is very tiring and demanding especially when a project is doomed to fail. Here, we look back on projects gone horribly wrong and ask ourselves, “What happened?” We do a post-mortem and try to put together the broken pieces that will explain how we failed.
And then we come up with valid reasons like.
· Poor requirements (Client does not know what he wants!!!)
· Poor Planning (our project manager screwed it up!!!)
· Poor Management (Wish the CEO had shown more balls!!!)
And the reasons go on…….
Finally, we toss up a nice PPT, give “big assurances” for future projects, “shuffle” two or three resources out of the project and then majestically close that one painfully long fucking project to everybody’s “satisfaction.”
But in the whole process of cover-up, we conveniently ignore the single most important factor that resulted in failure
The software became so complex that it reached a stage where no one knew what it does. When any project reaches a point where no one completely understands how the impact of code changes in one area will impact any other area, we can rest assured that the project is doomed to failure. Sooner or later the project comes to a grinding halt.
And a complex project gets created due to a stinking design.
As Irene Au has rightly said.
“Good design is like a refrigerator — when it works, no one notices, but when it doesn’t, it sure stinks.”
And here are some of the wonderful design principles that helped me throughout my programming career.
Create Consistent Abstractions
Josef Albers rightly said.
“Abstraction is real, probably more real than nature. “
Abstraction is the ability to engage with a concept while safely ignoring the finer details existing at different levels. We find and use abstraction everywhere in the real world. For example, when we refer to any object as a car, we automatically refer to it as a whole rather than breaking it into its individual parts like chassis, engine, brakes etc.
In software terms, the need of the hour is to create powerful base classes that allow you to focus on a set of common attributes of a set of derived classes while ignoring the details of specific classes.
Thus a good class interface is an abstraction that allows you to focus on the interface without worrying about the inner workings of the class. This creates a design which is simple and then easily decoupled.
A great software design means abstractions created at the routine-interface level, class-interface level and package-interface level and this results in faster and safer programming.
Hide the Implementation Details
Len Wein hits the nail on the head when he says.
“In general, shorter is better. If you can encapsulate your idea into a single captivating sentence, you’re halfway home.”
Hiding or encapsulating picks up where abstraction leaves. While abstraction says “You are allowed to look at any object at a higher level of detail”, encapsulations goes one step further and says “You are not allowed to look at that object with any other level of detail also.”
This is where the wonderful concept of inheritance comes in.
In designing any software system, we can often find that objects are very similar to each other except for some differences. For example, while designing a contracting system, we can have different types of contracts (fixed, time and material, incentive-based etc.), but the general attributes of each contract remain the same. Thus you can create a generic object called “contract” and then define “fixed price” contract as “inherited” from contract object with some additional attributes and so on.
Inheritance simplifies the design because you write a generic routine to handle anything and then write specific routines to take care of specific scenarios. Not only is the code “clean” but it also offers a great way to decouple unnecessary details from the developer who will be using the class or object for inheriting.
Inheritance is one of object-oriented programming’s most powerful tools. It is a double-edged sword; it can provide great benefits when used correctly and it can do great damage when used naively.
Change will Happen. Plan for it.
Amit Ray kills it beautifully when he says.
“In every change, in every falling leaf there is some pain, some beauty. And that’s the way new leaves grow.”
While designing any system, changes anticipated need to keep in account and provisions should be kept in place to prevent any heartburns and grieving’s later. As a rule of thumb, you need to design your system such that the effect or scope of the change is directly proportional to the probability of the same happening.
The more the probability, the more the system needs to be ready to accommodate the change.
A good starting point can be to identify the areas which have the potential to change and then identify the bare minimal subset which is expected to remain constant. All subsequent planning can be done on top of this minimal subset.
Thus by identifying the core first, you can clearly see which add-on components need to be built and accordingly leave “exits” in the code to plan for the implementation at a later point in time.
Thus, in a nutshell, create a set of classes and routines with small, direct and flexible relations with core classes in a “loosely coupled “way. This loose coupling enables the design to be flexible enough to accommodate changes and make way for easy implementation.
Iterate until you get it right.
Sebastian Thrun rightly said.
“Few ideas work on the first try. Iteration is key to innovation”.
Design is always an iterative process. You would often find that you go from point A to point B and then again come back to point A to start all over again. It is frustrating but there are no shortcuts to arriving at a robust workable design. Patience is the key.
As you walk along the path from A to B, you will see various designs, try various approaches and envisage various high and low-level views. The initial high-level view is fluid and as you walk along, you solidify the same into concrete low-level views. Finally, you decide on an approach; top-down or bottom-up and then create a framework based on that.
The key is not stopping at the first attempt. The second attempt is always better than the first and you learn things on each attempt which improves the overall design progressively. Incremental refining is a very powerful tool to reduce complexity.
As Polya has rightly said.
“Understand the problem, devise a plan, carry out the plan and then look back to see how you did.”
Prototyping is the proof in the pudding
Laurie Anderson correctly has said.
“The problem with prototypes is they don’t always work. But that is how it is meant to be”
Perhaps the biggest advantage of prototyping is the visual representation of design, a picture even if on a smaller scale is more than a thousand words. This picture not only convinces us of the workability of the design but also helps in getting the necessary “buy-ins” and approvals to move ahead.
But having said that, Prototyping isn’t for every project, but for the projects, it is right for, it can be a tremendous asset.
The Prototyping Model is a system development method (SDM) in which a prototype (an early draft of a final system or product) is built, tested and then reworked as necessary until an acceptable prototype is eventually achieved from which the complete system or product can be developed.
A prototype serves as a throwaway model made to understand the requirements of a project before design and coding begins. In essence, prototyping is a project test run.
There are many unexpected problems that can occur during the software development process, even if you have revised the design several times. Prototyping test will at least let the development team knows where are the problems and have the opportunity to improve it before released the product public.
Used with discipline, prototyping is the workhorse tool, a designer has at his disposal to combat design wickedness. The payback can be immense; just look at the iPhone.
So How much is enough design
Unfortunately, there is no rational or logical answer to this question. In most of the cases, it is more of a judgment call than anything else. But while you cannot guarantee with precision the right amount of design required in any project; two design approaches are bound to fail every time.
· Having no design at all
· Designing everything upfront till the last detail.
Strive for simplicity and dogmatically persist with iteration until you arrive at the most optimal design. The more your design is closer to the real-life problem, it is designed to solve, the more are its chances of success, as simple as that.
As Steve jobs has aptly said.
“Design is not just what it looks like and feels like. Design is how it works.”
About the author-:
Ravi Rajan is a global IT program manager based out of Mumbai, India. He is also an avid blogger, Haiku poetry writer, archaeology enthusiast and history maniac. Connect with Ravi on LinkedIn, Medium and Twitter.
You Need to Have an Outstanding Design to Write Awesome Code was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.