Tools for Good Software Design
This blog is about my experience, not about actual software tools for design. What I’m actually going to discuss are rules of thumb, principles and approaches, I collected over the years of writing software. At some point, I realized that I have a list of tools/rules/principles I’m utilizing over and over again, so I decided to write them down. The result was a rather eclectic list of high level slogans.
This series of posts is my attempt to bring this list in some clarity. This is by no means a complete list of “how-tos”, nor do I believe such a list can exist. These are the tools and techniques I find most useful when I architect, design or write software. I hope that you might find them useful the next time you write a piece of software. Consider this as an alternative to doing it the old fashion way of making the mistakes yourselves :)
So here are the first four:
1. KISS — Keep it simple. Stupid.
I know this is an oldie and an obvious one, but it is a cornerstone in anything we do in software. Its importance can not be exaggerated. Just remember that keeping things simple means you found the simplest way to solve your problem. Thus, It would be the simplest code to maintain. It would be the most extensible — it is simple. It would be with the minimum amount of bugs. It would increase the complexity of your system, yes, but in a minimum amount possible — again because it is simple. Oh, and one more thing; not writing code is preferable to writing code — our jobs as software developers is to solve problems not to write code. So, not writing the line of code is one less line to debug or to be refactored, its simpler. This is a clear case of “less is more” the simpler your solution is, the better, simple.
There is a non scientific technique I employ at the end of design sessions: I ask my gut (not my head) does the solution I reached feels simple enough? My gut might give be one of two answers: One- This feels too complicated, need to reiterate. Two- It’s time for lunch. I got pretty good over the years with the distinction between the two.
It is always good to finish with a quote from a dead authority (that agrees with you):
“Make things as simple as possible, but not simpler.” - Einstein’s Razor
2. Single Responsibility Principle
This is KISS descendant. The Single responsibility principle is a tool that guides you in two facets: it reminds you to decouple your problem/code/system and it gives you a rule of thumb of how to decouple: Does this section/problem/function belongs to the same responsibility area. By giving your system, or service, or class, or method, or function (pick the level of programming that suites you now) a single responsibility. You are most likely to make it simpler. Having more than one responsibility to a service, for instance, would result in parts of your system competing to achieve different things with the same resources. It might be about complexity in code, because in general things tend to get more entangled and complicated as the code base grows over time. And with multiple responsibilities you would get multiple trajectories of growth and thus more entanglement of your code. It might be in mixed data models or in DB schema or data persistency or it might be about controlling CPU utilization or responsiveness of your APIs.
Once your service is doing other unrelated things during a specific API call, your API latency is guaranteed to fluctuated and confuse/break your clients, or just not be compatible with your requirements. Guess what would you need to do to fix it: one of two: bend over backwards in the other “responsibility” zone of your service, or decouple the responsibilities (a new service). Now for the most part, when push comes to shove, the bending-over-backwards is a faster short term solution. Unfortunately it might be solving the current problem, while introducing many new problems down the road, since bending over backward is by definition more entangled code. I suggest to be wise about it (a wise man never goes into holes that a smart man manages to get out of, or vice versa). Let’s reduce the amount of bad decisions we are forced to take, by taking the decoupled, single responsibility route first.
There are implications, nothing is without its tradeoffs. Performance issues and data consistency issues might arise when we decouple things. You get some complexities in maintaining the communication between the decoupled parts. This problem can be clearly seen in the Micro Services architecture — managing these Micro Services could be very tricky. However, I believe (and I’m putting the weight of my experience behind this next statement) it is way more preferable to write your code decoupled and then when you have a performance issues, find it and fix it. The opposite: decoupling of an entangled code base is often completely impossible. Worst case scenario for optimization sake (and only when you know you need to optimize) — couple it. Just remember that coupling two decoupled parts is a million times easier (this is an educated guess:) then decoupling a piece of software that was not designed to be decoupled.
If you want to learn more about simplicity in code, I recommend watching the presentation simple made easy by Rich Hickey (the creator of Clojure).
3. Ask why
Why should I ask why and about what and when? Well, this might have been the single most important design technique I ever learned. It goes back my rather early days in the industry. I already had a few years of C++ development experience and I thought I knew how to program and design. I used to design a piece of code, define class hierarchies and do OOD thingies. After feeling good about myself (and my design), I would go to my team leader and he would shred my design apart piece by piece: “Why do you need this?” pointing at a piece of my design. I’d start to explain and my explanation would get the same treatment, “why?” this and “why?” that. Sometimes, and I kid you not, I would stay with nothing — no code should be written because the problem I thought I was solving didn’t exist. In other cases it would lead to insights about the solution or even better, the nature of the problem- the root causes.
This is a known technique called “The five whys”. It was developed as part of the manufacturing practices at Toyota. It is extremely effective in software development and will help you in solving the right problems as well as in solving the problems right.
So the next time you design, plan or solve a bug. I urge you to use this technique, it is mostly effective when somebody else is doing it with/for you. Another person’s perspective gives clarity, just trying to articulate what you know to be true in our head, reveals the vagaries of our understanding and the (sometimes wrong) things we take for granted. I use it all the time, I suggest you do it when you do code reviews, go over designs etc…
Note: Do not hesitate to use this technique with your product managers…
4. Eat your own dog food
If you write an API, use it, and use it in a way your client would. Nowadays in the age of Micro Services, we are all generating APIs, usually RESTful APIs. We sometimes forget that our services were built for a purpose — and that purpose is (wait for it): To be consumed by clients. Problem is, these clients are not us and for the most part have different concerns than us. They employ different techniques of working, they probably use other APIs in parallel and in general, have a different set of challenges. For example: A service that exposes an API using the GET method and is consumed by a javascript client. Today, there is a limit of about 2000 bytes in the URI length (in some browsers) so you might be writing comprehensive server side tests (with no size limit) that gives great coverage of your API, while in the real world clients might be breaking when a request grows in size. Another example might be that the structure of the returned data might be perfectly natural to manipulate and handle in a server side language like java or Ruby, but would be hellish to do in javascript client or will not cooperate nicely with existing javascript frameworks. You will never know that if you don’t try to use your API as a client.
Bottom line: Use the APIs you create yourself, feel what it is it like to be the client of your APIs and I guarantee that you will write better, more effective and useful APIs. Remember that this is mostly what it is about — your service is built to be consumed by clients through these APIs.
So, don’t forget to ask why (a lot), keep it simple, design with single responsibility in mind and eat your own dog food.
Originally published at blog.naturalint.com on October 7, 2015.