I’m sure most of us have heard of the term “Death March.” Some of us have even experienced a project doomed to failure firsthand. According to Wikipedia.org, a “Death March” is “a project which participants believe to be destined for failure, or that requires a stretch of unsustainable overwork.”
Another very common experience is being afraid to make a code change because you don’t know what you’ll break in the process. Any change to the software runs the risk of producing bugs that go undetected until found in production.
Both of these experiences leave us feeling helpless, unfulfilled, and frustrated. In the moment, they may even make us wonder why we chose this line of work in the first place.
The good news is these are completely avoidable if you follow simple action steps!
In my e-book, I talk about 3 Common Software Design and Development Mistakes That Cause Teams to Fail. One of those mistakes is centered around code, its structure, and the process for writing it.
You may be surprised to learn that agile framework guidance subtly suggests to us how to solve this.
Maximizing The Amount Of Work Not Done
The 10th Principle in the Agile Manifesto states, “Simplicity–the art of maximizing the amount of work not done–is essential.”
Even though we’ve heard of these principles, we may not have thought much about how to employ them to our own benefit, or how these principles can overcome painful experiences and ultimately prevent project failure.
The Software Development Team’s Responsibility
The truth is nearly all software development teams are unaware that it is their responsibility to design the code with the goal of simplicity. We follow agile frameworks expecting the ceremonies to be sufficient guidance, but unfortunately, that’s not the case. We shouldn’t expect our Scrum Masters or Project Managers to be the software development experts. That’s our job and – and it’s exactly what we try to convince hiring managers during the interview process.
Knowing this is our responsibility is the first step because we have control over our own success.
Building Software with Simplicity in Mind
The keys to “maximizing the amount of work not done” are designing the code before development starts and leveraging reusability across use cases. This is often called detailed design and should be code reviewed and committed to source control before implementation.
Designing The Code Using SOLID Principles
This design effort is accomplished by leveraging the well-known SOLID Principles and being aware of certain design naming smells that hint to an unripe design. This alone will lead to less code to write, less code to test, and huge time savings as a result!
We become more efficient with a proper code design, increasing the whole team’s velocity. With a proper code structure, our software systems don’t have to become more difficult to add features over time. They actually require less effort than before, giving us a perpetual maintainability improvement.
Another benefit of following the SOLID Principles during design is greater loose coupling. This enables a higher degree of abstraction over implementation, reducing the chance of bugs when changes are made to our code. By considering reuse across use cases, we won’t be required to change our code structure nearly as often, reducing the ripple effect of a change.
How to Achieve Detailed Design
Detailed design is performed by properly factoring our service contracts (interfaces) and data contracts (data transfer objects or DTOs). These contracts are the abstractions that all calling code will work with.
The next step is to create the class hierarchies for the data contracts and implementation classes for those interfaces. Doing this leads to two benefits: we produce guidance that serves as documentation for the developer doing the implementation and directly improves the implementation effort by suggesting structural design patterns.
An additional way to enhance loose coupling and reduce the likelihood of the ripple effect (i.e., unintentionally breaking functionality in other areas of the system) is to eliminate any methods in your data contracts. Doing this will prevent us from passing any behavior between the layers in our architecture. When we pass behavior or logic between layers/components, we circumvent the encapsulation each layer (and component) provides.
Eliminating methods in data contracts can also be applied in some legacy systems without rearchitecting. Remove the methods from your DTOs and emit new ones in each layer keeping the layer-specific logic in the code in that layer.
Proper detailed design efforts lead to improved testability when employed in all components in our architecture. Good code structure based on abstractions automatically promotes unit testability and therefore higher overall code quality.
Download Our Quick Software Engineering Training E-Book
Designing the code after development is one of the common mistakes that software developers make. Designing our code before development enhances loose coupling, enables us to leverage abstractions better, and improves the testability and overall maintainability of our software systems perpetually.
To learn more about the other two common design and development mistakes (and how to avoid them), download my e-book!