In this post, I’ll explore the value and business case for good software architecture. If you’ve worked with a programmer, you know that we tend to take strong stands on seemingly small or unimportant things, and we like to talk about the mysterious “right way” of doing something.
Most of the time, we are talking about software architecture, or how code is generally organized or structured. Good architecture includes confirming various parts of your software project aren’t coupled together, clean interfaces and responsibilities for each piece of code, and testing as much of the project as possible. But why should any of this matter to you, what value does it have?
In short, good software architecture saves valuable development time and as we all know, time equals money. This might feel counterintuitive since doing things “the right way” always seems to involve spending more time, not less. And the programmers and development agencies that insist on good architecture usually cost more per hour than the ones who don’t understand or care about it. So how can this possibly be true?
By investing a little time in architecture as you build software, your code will be cleaner and your product more stable. It will also make any future work on the product much easier to do. This saves you time by making it safer to change your product or add new features. It makes bug-hunting faster and less necessary, and it makes it easier to test your code, which in turn makes it even easier to change your code while preventing bugs from ever happening in the first place.
This process is the opposite of accruing technical debt, where over time your product gets so bug-riddled and hard to update that you feel like it’d be faster and easier to “just start over,” which of course is time-consuming and expensive. Think of good software architecture as “technical savings;” if you start investing early and consistently, over time you will save much more than if you wait until later to start investing. Better programmers have come to rely on these habits so that it takes even less time to ensure that the project has a good architecture.
Change is inevitable, especially in software development. Without updates, it doesn’t matter how perfectly researched your app is, in a few years it will no longer match the needs of the rapidly-changing market. It doesn’t matter how well-designed your app is, it will eventually look stale and outdated. It doesn’t matter how fast your app was when it came out, without updates and improvements it will start to lag behind your competition. As time goes on, services that you rely on will fail or go out of business leaving your product looking bad. Frameworks that your app depends on will deprecate and your app will start crashing. Ultimately, users don’t know or care what services you are using or what frameworks are; all they know is that your app is broken and this makes you look bad.
Good architecture patterns make software easier and safer to change. The DRY pattern – “Don’t Repeat Yourself” – is a tenet of good architecture. Any function of your software should only be in one place, not scattered and repeated throughout your code. If a piece of functionality is focused in a single place, it is very easy to make changes and fixes to that functionality. If you have the same functionality copy-pasted 10 different places, when you go to fix a bug or make a change, you need to make sure that you fix it correctly in all 10 places. Any place that you miss will have a new bug or maybe the implementations were slightly different and what fixes one may break another.
A well-architected app should also have abstractions around its different layers and frameworks, especially any third-party dependencies. Therefore, if you switch frameworks or services, it won’t break your entire app since the rest of the app has had that service’s implementation details abstracted away from it. This isn’t a theoretical programming concern; this is a practical business concern.
There are times when a point of sale (POS) system, payment processing company, etc., no longer works for you and you need to switch. You don’t want to have to consider the cost of rebuilding your whole app when you’re deciding if a switch is right for you. You may also find that a third-party service is no longer available because it’s gone out of business. I once worked on an app that used Parse for its backend and to manage push notifications. Parse announced it was going out of business so I switched the app to use Apple’s CloudKit instead. Because the app’s backend code was properly abstracted away, I was able to seamlessly make the switch in less than a day.
Bugs are the worst and not just for developers, but for the business as well. They can’t be anticipated or estimated, they aren’t a part of your plan and they can be very expensive.
Good architecture helps with bugs in two different ways:
- It helps to prevent bugs in the first place
- It makes bugs easier to hunt down and fix
The Single Responsibility Principle or “Do one thing and do it well” combines nicely with ‘DRY’ to simplify hunting down bugs. If each piece of your software only has a single responsibility and that functionality isn’t repeated anywhere else, it’s very easy to pinpoint where the bug is coming from when a feature starts to fail. If you have objects in your software that handle many different things and your functionality is separated, when you encounter a bug it is difficult to identify where the issue originates from, especially as different responsibilities get tangled. Failing to separate responsibilities in your code allows for bugs in different parts of the app to work together creating weirder, harder-to-fix bugs. Two bugs working together are exponentially harder to identify and fix.
Trying to fix bugs without understanding their root cause (more likely in bad architecture, or when multiple bugs are working together) results in “hotfixes” or “Band-Aids.” You may wonder why developers hate these so much. Fixing things quickly and Band-Aids both sound like good things; the problem is that these names are misleading. A real Band-Aid covers a wound temporarily while it is actually healing, but a code Band-Aid or hotfix is not a fix at all; it just writes a second bug.
As an oversimplified example: Let’s say you have a bug where all of the views on a screen are unexpectedly 10 pixels farther left than they should be. Instead of finding the root cause of the bug, you just shift all of the views 10 pixels to the right. Now it looks correct, but the original bug is still there and you’ve added more code that does not do what you want. If the original bug ever gets fixed, your views will now be in the wrong place again.
Or maybe a new device size comes out, and instead of the views being off by 10 pixels, they are now off by 8 and your code no longer fixes that. Then it’s easy to continue making hotfixes, so you write new code to move the view a few pixels back to left. The original bug has still not been addressed and you’ve added even more code making the bugs harder to untangle. This unwanted code can also combine with other unrelated bugs making them more expensive to fix and sending you down the technical debt spiral. Sometimes in a software emergency, a hotfix is necessary, but make sure to treat this like an actual Band-Aid – a temporary solution while continuing to work on finding and fixing the real problem.
One of the major goals of good software architecture is ensuring your code is easy to test. Testing adds value in many ways, particularly in making changes easier and catching bugs for you automatically. It’s easier to follow good architecture principles if tests are written before coding (known as “TDD” or “Test Driven Development”), by helping you to define clear responsibilities and interfaces for your code.
Tests ensure that you aren’t breaking existing functionality when you add new features or make changes. This can take a terrifying, bug-spawning refactor and make it safe and painless. As mentioned earlier, the sooner a bug is caught, the less time it takes to fix.
Once again, the single responsibility principle is important here. As an object takes on more responsibilities, there are exponentially more ways those responsibilities can interact and more things that need to be tested to ensure the software works properly. When each object has a single, clear responsibility it is easy to test completely. Then, even when these objects are combined, the combinations need less testing because you already know that every piece does its job correctly.
When approaching important deadlines or budget constraints, it may be tempting to save time and money by cutting tests or trying to squeeze in features faster than can be done correctly. By rushing through the process and focusing on speed and not quality, you aren’t actually saving time or money, but entering the technical debt spiral.
Similarly, when looking for a partner to build your software, you may believe the cheapest option is the best depending on your needs. But keep in mind, in software development it holds true that you get what you pay for. Typically, a cheaper development company will look for those quick fixes and will cut corners where they see fit, creating an architecture unfit to build upon or change. Consider this a fair warning: your best bet is to pay the upfront costs for a well-architected software solution. The architecture will support the long-term evolution of the product and you’ll find yourself paying less for more in the end.