Abstract
In this article I’d like to write
about Software Design Smells.
In the SOLID – The Principles of Object Oriented Design article I posted, I wrote extensively about how we
could prevent these design smells, by designing our solution with respect to
the SOLID principles.
It’s important to understand the
meaning of software design smells, and the negative affect on our application,
to emphasize the importance of the SOLID principles.
Introduction
Software Design Smells are symptoms
of bad design and violation of software patterns and object-oriented
design principles that our codebase would experience, if we’ll not take
measures to prevent them.
There are 7 well known 'Design
Smells' that could have negative effects on our solution codebase:
1.
Rigidity - The software
is difficult to changes. (every change impact depended modules)
2.
Fragility - The software
breaks in many places when a single change is made.
3.
Immobility - Components
can't be reused.
4.
Viscosity
1. Software
Viscosity - Preserving the existing
complicated design, or Hacking it.
2. Environment
Viscosity - The development environment is
very slow and inefficient.
5.
Needless Complexity
- Over Engineering & Over Design.
6.
Needless Repetition
- Duplicate code that could be avoided by abstraction.
7.
Opacity - Writing modules that are not clear
and hard to understand.
Rigidity
The
software is difficult to changes
Every new requirement, even a small
single change in a particular module, could cause additional (sometimes
complex) changes in other dependent modules.
This indicates on a rigid design
and naturally effects on the implementation-time.
In addition, since we couldn't fully
anticipate this change implication, regressions issues might occur.
Fragility
The
software breaks in many places when a single change is made
When modifying a single area in our
code, other areas in the code, with no 'visible (logic) relation' to this
change, were harmed or broken.
So, in such cases, fixing one issue
could cause more problems and even regression issues.
The design is easy to break.
Immobility
Components
can't be reused
When trying to reuse a common
component (module) in several areas in the code (or in other systems), it's too
tightly coupled to the original code (system), and almost not possible,
since it includes lots of unnecessary effort and meaningful risks.
This inevitably creates 'code
duplication' in our business logic (BL), which naturally effects on the
maintainability of the code and especially on new BL requirement.
The design is hard to reuse.
Viscosity:
There are 2 levels of viscosity:
1.
Software
Viscosity –
Modifying the code while preserving the existing
complicated design, or by 'Hacks', meaning by introducing new 'design'
which is NOT compliant to the existing design.
Many changes in the code could be
made in more than one way, if the easiest way is without preserving the
existing design, then this solution is considered high viscosity software,
since developers sometimes tend to do the 'wrong thing' and thus breaking the
design and causing other issues, such as 'Spaghetti Code'.
2.
Environment
Viscosity – The
development environment is very slow and inefficient.
Developers spend many hours in the
development environment.
If an environment is slow, meaning:
§
Compile-time is very long
§
Unit tests projects runs inefficiently
§
Source control check-ins are long and complex
§
Long deployment\build processes
§
Other bad designs that affect the environment, e.g.:
performance testing tools.
Then sometimes developers would
search 'Hacks' and the easiest ways to complete their tasks. E.g. they won't
perform full 'feature tests', in order to prevent spending many hours in a not
'friendly' testing environment.
Another e.g.: developers would write
code in wrong modules in order to prevent long compile-time.
Needless
Complexity
Over
Engineering & Over Design
With the aim of maintaining good
designs and implementing robust solutions (and to avoid software design
smells), developers sometimes exaggerate with their designs while trying to be
as generic as possible and to capture every extreme case that could appear in
the future.
This is somewhat unfulfilling these
concepts, since we always need to use common sense and good design judgment on
which extreme cases to include and others to exclude.
Such extreme designs create
unreachable code (unused elements) that causes the entire solution size to
grow, and add complexity in the code maintenance.
Sometimes, we should be reminded that
good robust designs should be flexible, lightweight, and easy
to understand in order to provide trivial new features implementation.
Needless
Repetition
Duplicate
code that could be avoided by abstraction
Duplicate (mostly) business logic code
appears in the system, that could be united using abstraction.
This could be the result of
copy-paste, and probably lack of awareness to the many problems such poor
design could cause.
This, of course, effects on the
maintainability of our systems, bug fixing in many different areas in the code,
and naturally features growth.
Opacity
Writing
modules that are not clear and hard to understand
Developers could write
'self-explanatory' code that is easy to understand and explore, and as opposed,
developers could write code that is very hard to understand and sometimes even
doesn’t make sense.
Difficult to understand code requires
a significant amount of time trying to implement new features, and could
potentially cause undesired behavior and severe bugs.
Bad initial design could cause such
effects over time with features changes and features growth.
Summary
As experienced developers, we know
that in large scale systems (containing millions of lines of code), and as the
application evolves with numerous new requirements (over the years), sometimes
we are bound to get software design smells.
However,
we could always take measures to minimize (and finally prevent) the design
smells in our codebase, especially with respect to new requirements.
Few measures to take in order to prevent
design smells:
1.
First and foremost, maintain the ‘set of mind’ to always remember the negative
effect of software design smells, on our application, development-time, bugs,
performance and on the entire product life-cycle.
2.
Educate the developers to practice the SOLID principles when designing new solutions.
3.
Design & Develop according to Design Patterns.
4.
Maintain other software engineering 'Key Design Principle', such as:
-
CQRS
- Command & Query Responsibility Segregation pattern
-
DRY
- Don't repeat yourself
-
LoD
- The Law of Demeter (a.k.a The Principle of Least Knowledge)
-
Rule of Three
-
The Null Object Pattern
-
The Monad Pattern
- Maybe Monad
-
AOP
- Aspect-Oriented Programming
-
Strangler Pattern - Recreate
legacy
system
-
Composite Reuse Principle
- Favor Composition over inheritance
-
etc.
5.
As part of development standard, always perform code reviews.
6.
Perform code inspections
to core designs and important features, receive feedbacks.
7.
Encourage the developers to write proper code comments when relevant.
8.
Encourage the developers to log
informative messages in critical code sections, regardless to the regular
BL logging.
9.
Work according to code guidelines
and common code styling. (use static
code analysis tools)
10. Create unit-testing projects with sufficient code
coverage. (run automatically with every check-in, use report system for
notifications)
11. Create performance testing projects to maintain
proper KPIs. (bad designs = low performance)
Remember…
well-designed clear code provides better Maintainability, Scalability,
Testability... and finally influence on the entire Product Life-Cycle.
Related
articles:
Intro:
S.O.L.I.D
The End
Hope you enjoyed!
Appreciate your comments…
Yonatan Fedaeli
No comments:
Post a Comment