Software design is all the rage now. It looks like every year a few more design ideas appear. First there were the GoF design patterns. Now, MVC is the way to do web applications, while ideas like: domain driven design, ports and adapters, microservices see increase interest and adoption.
I learned software design by doing, with a mentor who kept giving me very useful (and sometimes annoying) feedback. This was before I knew about design patterns, SOLID principles or TDD. Therefore, my understanding of design starts from the core laws of software design. That’s why, when my colleagues or I work with developers, we teach them not only design patterns and SOLID principles, but also a way to think about design. Here are 4 ideas you should consider when you make a design decision (that is, every time you write a function, class or module) to get better software design:
1. Every Design Decision Has Advantages And Disadvantages
When doing architecture and design exercises, such as Architectural Kata, Code Retreats or one of the software design workshops, I ask participants to create a design, either by writing some code or drawing a diagram. We then discuss and give feedback to their solution. Often, a participant asks me to give the “best solution” for the problem. The unexpected answer is that there is no such thing.
The reality is that any software design decision has advantages and disadvantages. After all, think about all the characteristics of a good design. Here’s a list I compiled from the top of my head:
- Fast to implement
- Easy to change
- Easy to find issues
- Reduces the chance for mistakes
- [add yours here]
It is impossible to write code that gets a 10/10 for all these criteria at once. That’s why we have the catchphrase “quick and dirty” instead of “quick and fast and scalable and without bugs and easy to change and …”. The important question becomes then: for what of the criteria is acceptable to get a 8/10, 6/10 or 4/10 in your particular context.
This translates to a “most fit” solution for a certain context; it will probably not look like the “best solution” you were thinking of. Here’s a personal story to support this fact. When I was developing in C#, I discovered that I could use delegates as lambda expressions, thus reducing the number of lines of code I had to write. I resisted the temptation because my colleagues would find this code construct confusing, so it increased chance of making mistakes. I could also have tried to teach them this way of writing code, but I wasn’t that good at training people back then. All in all, it was a conscious decision for the best of the project.
I have two ways of identifying advantages and disadvantages so that we get better software design. Whenever we evaluate potential solutions for a problem, we either:
- List the alternatives, list the advantages and disadvantages of each of them and then make a conscious choice for one of the solutions.
- Experiment: start implementing one of the solutions, being ready to throw it away or change course if it’s not good enough
This process doesn’t have to take a long time. Usually 30′ is more than enough for the first option, and a maximum of 2 hours for the second. After some practice, you will start doing it automatically for most decisions.
If there’s one thing you take out of this law, this should be it:
To make better software design, be mindful about the advantages and disadvantages of the solution you choose.
Reflection question: what are the disadvantages of the class you’re working on? What could go wrong?
2. Developers Are The Users Of The Software Design
Whenever we talk about design in other domains than software, we discuss it from a user-centric point of view. Apple’s products are renowned because they focus on the experience of a user with their device: how it feels, how it looks, how fast it responds, the sounds it makes etc.
Software design is the only type of design that seems to be userless. After all, the end-user has no idea how the software works and doesn’t care. All they do care about is to work fine.
So why should we care about the internal structure of software? There are very good economical reasons to do so. If software is not easy to change, developers won’t add features fast, resulting in potential loss of customers. If the structure of the software is not organized properly, more bugs could appear, potentially annoying the customers and requiring many hours of digging through the code to fix them, thus increasing maintenance costs.
These are not new problems. Initial designs for many things we now use everyday were poor, and they improved in time. How? The key is to listen to user feedback.
In our team, Claudia works full-time while I work part-time on an eHealth product. Besides my development tasks, I serve as a mentor, coach and help her with more difficult decisions. One of my recurrent questions is: “So, what was difficult to change in the past two weeks?”. Based on this question, we heavily improved the changeability of the code in the areas that change the most.
Software design is not userless. The user is the developer that will have to change the code after you do. If you have collective code ownership (like most Scrum teams these days), you’d better consider user-centric software design.
And here’s an idea for you: how about running usability tests on your software design to cut the development costs? Let me know if you want to experiment, I can help.
To make better software design, look at it from the other developers’ point of view.
Reflection question: What are some common complaints of your team mates related to the code? What is difficult to change? How could you make it easier?
3. Names Matter Much More Than You Think
Software is unique in that it doesn’t exist outside your brain, but you can interact with its results. OK, I might be stretching it a bit here, but consider this: software is executable knowledge. When you’re writing an accounting application for example, it encodes everything to know about the rules and procedures of accounting. How did that knowledge end up in the application? By going from specialists through developer brains into code.
(An aside remark is that the two characteristics that differentiate developers from the rest of the world is that they understand computers and can think with a very high level of precision. This is why I don’t believe in visual programming done by specialists.)
How do humans learn and structure knowledge? By naming things. If you think about your school years, you remember learning about numbers, arithmetic operations, multiplication table etc. These are all names given to certain concepts, names that help us communicate with each other about complex ideas.
I invite you now to take a short break and write some of the class, methods and variable names you find in the code you’re working on. Then write down what the application does. We’ll wait.
Ready? OK, now count how many of the names match the application domain. Or, even better, ask someone who doesn’t know what you’re working on to guess what the application does based solely on the list of names. If they did guess, then please contact me because I want to learn from you how you do it.
This test shows a typical disconnect between what the application does and how developers structure this knowledge in the code. Why is this bad? Because your brain will need to spend some valuable cycle times trying to translate between what you’re reading and what the application is doing. This leads to reduced productivity, getting tired and, sometimes, swearing.
There is no way we know at this time to eliminate this distance between the knowledge and the code. It can however be reduced much by iterating through the continuum of names. One word of advice: you will find it very difficult at first, but it gets easier with practice.
To make better software design, name the classes, methods and variables as close to the business domain as possible.
Reflection question: What does your application do? What are some names particular to its business? Are they present in code?
4. Conceptual Integrity Is The Lost Principle Of Good Software Design
40 years ago, Fred Brooks wrote a seminal book for software development called “The Mythical Man Month”. The book contains many essential findings about software development and engineering that most people working in the industry don’t know and don’t apply because they haven’t read it.
The most important design idea from the book is the following:
I will contend that conceptual integrity is the most important consideration in system design. It is better to have a system omit certain anomalous features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas.
– Fred Brooks, The Mythical Man Month
Ward Cunningham’s wiki page on the topic gives the following examples of conceptual integrity:
Can we identify specific, well-known examples of ConceptualIntegrity? I’m starting a list here… please feel free to make additions
- Unix (based on the notion of a “file” (e.g. directories, devices, filesystems, named pipes and sockets are all sort-of files)
- Smalltalk (“everything is an object”, and the small set of other accompanying principles)
- SQL (“all data is in tables”, with keys and constraints)
- Lisp (“everything is a list”)
Why is conceptual integrity useful? Probably because how our brains work. The human working memory is limited to keeping four items in parallel, but there’s a catch: each item can actually be a set of related concepts (a so-called chunk). When you have to make difficult design decisions, it’s easy to imagine that you need to consider more than four things at the same time. It helps if they are similar, because the brain can chunk them, allowing you to make more informed design decisions.
You can apply conceptual integrity at different levels, from variables, methods to classes. For example, let’s make the following test. Write down the names of a few classes and the names of all their public methods. Show the class interface to someone who doesn’t know what you’re working on and ask them what the class does. Ask them if there’s anything that seems out-of-place from that class. If they guess and everything fits, congratulations: you have achieved conceptual integrity at class level. Now do the same exercise at namespace and module level, showing only the public interface of the module.
At system level, things become more complex. Ports and adapters and microservices are some of the patterns that make easier to bring conceptual integrity at system level. But the lunch isn’t free, each of them has disadvantages (remember the first tip 🙂 ).
A word of warning: conceptual integrity is very hard to meet, harder than good names. However, I can tell you from experience that when you do make it, it is not only very useful but also beautiful. In a world filled with bugs and ugly code, beauty can do wonders for your moral.
To make better software design, strive for conceptual integrity at class, namespace, module and system level.
Reflection question: What parts of your application have conceptual integrity? Pick a class you’re working on; how can you bring it closer to conceptual integrity?
Are these helpful? Have you found any other core laws of software design? Let us know in the comments.
Photo sources: http://pixabay.com/p-266858/?no_redirect, http://www.freshboo.com/wp-content/uploads/2014/04/Design-is-how-it-works.jpg, http://media.cmgdigital.com/shared/img/photos/2013/05/31/89/84/baby_name.jpg, http://www.stellman-greene.com/blog/wp-content/uploads/2007/05/business-plan-2007-05-14.png