Most software “engineers“ (engineers in double quotes) don‘t set out to create “bad designs“. Yet most software eventually degrades to the point where someone will declare the design sucks.
So how do you define “bad design“?
To define this, I think there is one set of criteria that all of us will agree with. A piece of software that fulfills its requirements and yet exhibits any of the following five traits has a bad design.
1. It is very difficult for a person to understand the code/logic/design (Complex)
2. The code does not communicate what is it‘s trying to do (Communication Gap)
3. It is difficult to change the code because every change is divergent and affects too many other parts of the system. (Rigidity)
4. Changes result in unexpected parts of the system breaking. (Fragility)
5. It is hard to reuse the code because it cannot be disentangled from the current application. (Immobility)
However, it would be difficult to demonstrate that a piece of software that exhibits none of those traits, i.e. it is simple, communicates, flexible, robust, and reusable, and that also fulfills all its requirements, does not have a bad design. But to some extent, we can use these five traits as a way to decide if a design is “good“ or “bad”.
During my experience of developing software so far, I feel upfront design does not do a great job to avoid these five traits. Though it starts off trying to achieve these points, as the implementation goes on, the whole design goes for a toss.
I would say, TDD [Test Driven Development] (I usually call it Test Driven Design) coupled with Design patterns and Refactoring does the best job of avoiding these five traits. Since we are following a bottom-up approach coupled with unit testing, we can easily see how this takes us closer to robust, flexible and reusable design that communicates and is simple to understand.
Design patterns are a great tool to simplify the design. They are also an excellent way of communicating the design. [at least between the techies].
As the software age and new functionality is added, the design degrades. There is no better way to stop this other than refactoring. And again this is not one person‘s job. The whole team has to consciously, proactively refactor code to avoid the design from degrading. Infact I would say, refactoring is a way of improving the design rather than just maintaining it.
Hence I would say, Just In Time [JIT] design helps to achieve a better design.