Effective Approach to Pragmatism in Software Design
“You can ignore Design, but it won’t ignore you.”
Designing an object-oriented or a service-oriented architecture for a domain (horizontal or vertical), requires a wide range of talents. Design is one of those activities where most believe they can design, Crudely paraphrasing Richard Feynman, “If you think you are good at design, you probably can’t design”. A student or a young developer will often think they can design a lot earlier than their experience warrants. It takes years to understand the subtle aspects of design in order to gain a cohesive, coalescent and mostly positive outcome. This article will look at some aspects of design, and outline key considerations when designing to formulate key principles. These principles were outlined in the article AMMERSE. Here we will look at them from an agile development standpoint.
Language of Design
In order for us to communicate, let’s establish a quick language to boost the effectiveness of this method. When you look through code it can tell you a lot about the design. Sometimes, you will find all sorts of clues of how many developers, what their mindset or experience, perhaps even gauge development time. If you go further into code repositories and look through code, you will find many clues. Here are some terms and clues to their existence.
A design evolves out of the developer’s code. This is a code-centric activity where code evolves into a design, based on no design. The developer does not actually look at the whole, or the larger aspects, but rather the details within and codes as they hit a decision point. This approach still creates design decisions and the design is what it is, albeit not with foresight. This approach is used by all, junior or veteran alike, as it is common to have situations that sometimes demand this approach. Developers are influenced by many things, time-lines and sometimes LackingDesign occurs, like it or not and has no reflection on how good or bad a developer is.
In no specific order, and not considered black and white. All clues are subjective and can mean many things when coalesced. Treat these as hints.
- Lack of architectural/domain layers
- Lack of code partitions and logical arrangement
- All layers are open Layers
- High Coupling of code assets (modules, classes etc)
- Low Cohesion (of modules, classes etc)
- Lack of Pattern Usage such as (GoF)
- Lack of reusable functions
- Chunks of repetitive code with minor changes
- A single developer
- Short development time
- Cluttered code, lack of file structure, naming patterns
Imagine a dark room, and all you have is your torch. The little bit you see, is what you design. The lit areas are where the focus is placed. The dark regions of the room (or large building), is akin to not knowing, or not understanding the entirety of the situation. You may design a raft since your torch looks like a simple stream not understanding that a waterfall is nearby around the bend. The kind of design often feels like it doesn’t quite fit, or visible code areas that contradict the design emerges. Torchlight design leaves clues in the code.
- Lack of architectural/domain layers
- All layers are open Layers
- Reusable code assets, that are not reused where intended
- There is an increase in refactoring
- Code requires modification before it fits in the next use case.
- Mixture of extremely coarse-grained and fine-grained code
- Unused code, that is not called but still there
- Cluttered code, lack of file structure, naming patterns
- Dead code, that is code that returns a value which is never used
- unused variables, dependencies
- A few interfaces
- Badly named
- Code doesn’t rely on the contract of interface
- lack of generic or reuse in the design
- Commented code left in the codebase
BadDesign may not be obvious at first glance, but not all design is good design, which leaves only bad design. A design that is bad, includes design that is overcomplicated, or underwhelming or overly simplified. A good design solves problems elegantly.
All the bad clues from Torchlight and LackingDesign
- You cannot extract a module or piece of code easily
- Code is copied between projects
- Time bottlenecks with activities such as configuration, setup
- A BadDesign nearly always leaves lots of things to do
- A BadDesign often takes long periods of time to discover issues
- Altering code, not only takes time, but it is highly susceptible to the introduction of bugs
- Once a change is made, you spend more time fixing what it breaks
- Domino effect of changes ripple through a large codebase
- In some form or another code seemingly fights or is at odds with the design
- There is too much configuration or negotiation with the design to get your code working
There are many anti-patterns for BadDesign
- Analysis Paralysis
- God Objects
- Bad Naming
- Abstraction for no purpose
- CopyPaste Design
Design vs Implementation
Just like other industries, design flows from the engineer through many roles. If we leave out how the design was created, documented and issued to the implementers, and assume its not the problem, we have to look at what the design was meant to be versus the implementation.
Design: Here is a perfect basket of glistening red and green apples. Does the implementation match the ideal, the principles, or is it a bit wonky and contains a few rotten apples among the contents.
The implementation may fall short, and often does due to time constraints and other factors, but that is not saying that all problems are from implementation. In fact the largest snowball affect can come from a bad design, but there is always a consequence for the implementations.
Problems with designs
There are many potential problems, too many to count, but let’s look at the 500 foot view, grouping issues into some sort of larger category of problem.
- The Design is overkill
- The Design doesn’t solve the whole problem
- The Design takes too long to develop
- The Design is brittle and breaks easily
- The Design is not future proof
- The Design doesn’t fit well with other code
- The Design makes for difficult implementation
For these kind of problems there is a pragmatic way of getting through it and getting a better design, by asking the right questions.
The ideas behind AMMERSE are two fold, to aid in clarifying the design choices and motivations as you design, but also to put decisions into words. This is much akin to software Design Patterns, whereby a large part of the positivity of using the pattern, comes from our shared language of the pattern. The shared language is already a huge leap forward. AMMERSE also creates a language to share design choices.
A developer not only needs to design and implement something new, he needs to convey the reasoning behind the design and describe the design choices and motivation to the team.
Before doing the design, he has a quick discussion with one of his colleagues and asks the right questions.
AMMERSE is a mnemonic for Agile, Minimal, Maintainable, Environmental, Reachable, Solvable and Extensible.
The mnemonic allows the formulation of easy, important and vital questions.
How much weight shall I place on Agile?
How much weight shall I place on Minimal?
How much weight shall I place on Maintainable?
How much weight shall I place on Environmental?
How much weight shall I place on Reachable?
How much weight shall I place on Solvable?
How much weight shall I place on Extensible?
The developer concludes that its a rather quick piece of code, and needs to be done by the end of the week. It solves a particular problem and is not really needing extensibility. The code needs to be minimal, as the Product Owner, wants to design something in the next iteration that is more wide-spread.
So emphasis, is placed on a reachable implementation (within 5 days), minimal piece of code that solves the problem, and does not have much weight on Extensible, Agility and Maintainability.
Read more about AMMERSE
**Is that it? **
No, that’s just the start. Each principle weight within AMMERSE, has a wealth of considerations within, including pointers to design patterns that work well within the principle.
What is an Effective Approach to Pragmatism
What is pragmatism? Pragmatism, is dealing with a problem with extremely practical actions. Not over the top and not indulgent. Personally I would rather be pragmatic, than unrealistic and indulgent, however, being pragmatic in itself, doesn’t mean the actions of the pragmatist is all effective. What we need is an Effective Approach.
‘Effective Approach to Pragmatism’.
- Effective: successful in producing a desired or intended result.
- Approach: A way of dealing with a situation or problem
Pragmatism: Dealing with things sensibly and realistically in a way that is based on
practical rather than theoretical considerations
Pragmatic solutions, tend to be immediate, developer driven and tend to be LackingDesign or TorchlightDesign. Within the AGILE methodologies, often design is neglected. In a team, you will find varying levels of design practice and pragmatism, therefore a hodgepodge of implementation.
The AMMERSE principles are there to guide when their are no guidelines for how a developer should design. Consider it the code conventions of Design. AMMERSE provides a language of design.
Consider the following weights (More important = higher number):
Imagine you must design a login system and presented by two different managers these two differing sets.
SET A: Minimal = 50, Agile = 0, Maintainable = 100, Reachable = 100, Solvable = 100, Environmental = 20, Extensible = 0
Set B: Minimal = 0, Agile = 100, Maintainable = 100, Reachable = 100, Solvable = 100, Environmental = 100, Extensible = 500
Simply by providing weights to the principles, you can get a good feel for what sort of design we apply. Set A demands a minimal, get-it-done approach that concentrates on solving the login problem and gets there quickly. Set B on the other hand, demands an extensible login system, which is friendly to a particular environment. Perhaps the environment is web, or perhaps web/desktop.
You can easily gauge the focus pertaining to the design by the weighted principles in a very succinct language.
How is this effective?
As an architect, you often have to decide how much time you can focus on various aspects of a project. If your team is also well versed, you may need to allow your developer/s to run free and design some aspects themselves. After all, software engineers are talented and will find holes or gaps in which to design. What you don’t want is the LackingDesign or any other anti-pattern finding itself inside your well planned software.
By using the correct language, you can instil the desire and nature of the required solution. Too often, developers may under think or over think to analysis paralysis. In either case, the design will not be what you desire. But consider your brief has this design language.
The login system, should be extensible at the following 3 hotspot areas: Server Authenticator, User verification token type and redirection after sign up and registration. The fourth hotspot, the UI, should be Agile and minimal. The server authenticator is where your design and focus should be. Clients using REST/AJAX/DESKTOP/WEBSOCKETS/ETC should all be able to communicate and sign-in.
The full Design
- Server Authentication API – EXTENSIBLE
- Emails sent – AGILE/MINIMAL
- User Verification token used in email – Extensible
- UI – AGILE/MINIMAL
- The focus on Environmental is HIGH (multiple client test cases)
- The UI will be implemented by other systems in their applications, the API must be ENVIRONMENTAL
SOLVABLE and REACHABLE in 4 iterations
- 1: SOLVE REACHABLE MINIMAL universal Website Logins (all websites have same login an understands you are logged in)
- 2: SOLVE AGILE EXTENSIBLE ENVIRONMENTAL universal Web Applications (all apps understand the login system)
- 3: SOLVE EXTENSIBLE ENVIRONMENTAL login API as a Service for all other applications
- 4: SOLVE MAINTAINABLE AGILE refactor and solidify solution
The language clearly dictates the solution is to be iteratively released with each iteration having a different focus.
Considerations for being effective
All parties must understand the language of design, whether they occupy a role that features design or not. All developers are participating in design whether you know it, think it or want it. They are part of the overall design and must receive proper training.
The language of design is vast, and you need to collaborate and get to a clear well defined design language understood by all. A few lines of bad code, can render a design useless.
Training developers to understand the principles of design is vital for the success of software projects. Even the best architects, can struggle to keep a large system in check, as code is committed to the source repositories by the minute and you cannot keep up. Developers must be held responsible for the design as well.
- Understand the responsibility of each piece of code
- Understand the design choice applied to each piece of code
- No code should be written without thought
- Always be aware of LackingDesign and TorchlightDesign as influences on the system
- GRASP, GoF and other patterns should be understood
- Anti-patterns should be understood
- The instructions given to developers must be clear not just of functional specification, but of abstract design choices.
It is clear that design should not be placed on the shoulders of one or more ‘Designers’. Architects are critical, but the engineers around them must also be responsible for design. The team must be focused on the same goals, having the same motivations in order to ensure that all the effort is not wasted.