When software projects become bigger and more complex, you might reach a tipping point: one day, code quality becomes paramount. But as soon as complexity increases, you'll begin to feel sorry for every compromise you've made so far. We've certainly experienced these growing pains in our own journey - with Tower steadily growing to now serve , users. This post talks about some of the important things we've learned along the way. Part 2 of this series will soon follow. Be sure to sign up for our newsletter to be notified.
Building even the smallest of software applications is a major investment for most companies.
Your team will spend days, weeks, months, and maybe even years building that application. But not only is this a huge one-time investment - you'll have to continue to put time and effort into it over its complete lifetime.
Neither the coding nor the investment will stop when the application launches. Let's take our own product Tower as an example: we had worked for 12 months to develop version 1.
- Architectural Programming | WBDG - Whole Building Design Guide?
- REAL Rheims Epistle of St. Jude.
- Works of Victor Hugo!
- Exotic Proverbs on Human Nature.
- Surviving the Stained Glass Jungle: The State and Fate of the Christian Ministry?
- Architectural Space Programming | ICON!
But in the six years since then, we have produced many times more code than the original product contained. This constant, never-ending maintenance and extension of an application means that its foundation becomes crucial. Much like with a house, it's not a clever idea to save some money by building just a cheap foundation. Logically, one of our biggest goals is to make extending and improving that application as easy and safe as possible. This is where application architecture comes into play. Only a really good architecture will help us protect the huge investment that building an application means.
We will explore in greater depth what "good" architecture means in our humble opinion. But the main qualities you'll want to aim for are the same timeless classics in every project: solidity, maintainability, extensibility and scalability. Improving and refactoring an app's architecture will often seem like luxury - or maybe even like wasted time. But in reality, it should better be seen as a necessity, as a crucial and one of the most important duties of an experienced software engineer. When we ask ourselves if we should invest into architectural improvements, the answer is usually yes.
When solving a complex problem, it's sometimes very tempting to choose an extraordinary solution.
Not only will this solve the problem - but it will also impress your teammates and bring you everlasting glory. An even better solution, however, would probably be a boring one. One that is easy to understand, even for your junior colleagues. One that is well-known on your platform and language.
Software Architecture - The Difference Between Architecture and Design
One that is absolutely not extraordinary. Using such a boring solution means that you're using simple vocabulary, which greatly increases the chances of everybody understanding you. This will make it a lot easier for everybody to follow along - including new team members and yourself, a couple of weeks after you've written that code. Trick question: if you had both modeling clay and Lego bricks available, what would you choose for building your application? Let's say you went with the modeling clay: pretty easy to shape, bright colors, strawberry taste - what more could you want? But the problems are inevitable as soon as you want to correct, extend, or improve something you've already built.
There is no way to easily separate individual parts after you've mixed and mingled them. If you've chosen Lego bricks, on the other hand, subsequent changes are easy: the yellow "authentication" block isn't big enough? Just take it out and replace it with a bigger one. The green "export format" block needs to be extended with a PDF option? Just put an additional light green brick next to it. Modularity, the concept that the Lego bricks symbolize, is synonymous with extensibility, maintainability and longevity of your application.
No matter which framework, language, or programming principle you prefer: always shoot for modularity in your code!
Acronyms FTW! The reasons why this is true are almost endless. And they might be easier to understand when looking at the opposite: complex code.
Architecture, Performance, and Games · Introduction · Game Programming Patterns
When you realize that your solution isn't really necessary at all, you should drop it. This is what YAGNI is about - " You ain't gonna need it " reminds us to stay modest when planning the volume and scope of our implementations. Will users really need this feature? Will they need this option within a feature? This point might not apply to every application in the same extent. But with Tower being used by over 80, people worldwide, we constantly had to redefine the term "edge case" for us. If your application serves a large user base, you will inevitably have to be more thorough when thinking about how people will use it.
Things that rarely occur with a thousand users might become a daily event for , users. This makes defining the term "edge case" a very individual matter: each and every team has to define for themselves what they consider an edge case. Also, be prepared to constantly redefine this term as your user base grows: Your current edge cases become too common to qualify for that label; and, at the same time, new edge cases will appear.
It pays off to invest a little more time thinking about these things before jumping into implementation. This way, you can include graceful handling of these cases already when writing the original implementation. This is much easier than having to catch up on it a couple of weeks later - when both your memory of the problem isn't fresh anymore and when the innocent little edge case has somehow turned out to be a full-blown bug. I'm sure you've used a third-party API at some point in your dev career - for example to create new contacts in your CRM, to send emails through a newsletter service, or to virtually do anything else with a third-party service.
If you've interacted with a couple such APIs, you will certainly have noticed some differences between them: using one was probably more pleasant than the other. It's easy to notice which API was designed thoroughly, by an experienced developer, and probably with a lot of effort and thought.
And it's just as easy to be frustrated with an API that was designed in a poor and sloppy way. The former was probably a joy to use, while the latter was probably Since the effects are so obvious, most developers tend to quickly agree that it's almost a duty to design public APIs in a careful and thought-out way. Nobody wants to work with a crappy API - and nobody wants to burden other developers with using their API being crappy.
Modern software design puts great emphasis on the concept of "application programming interfaces". However, as most developers already know, the concept goes a lot deeper and is not exclusive to a public interface. Instead, you should build APIs inside your application, for internal use, too. Approaching these internal APIs in exactly the same way you'd create a public one can make a huge difference: your colleagues and you will want to interact with this part of your application.
Making the interaction as easy as possible for these people is one of the best goals you can have. An easy, thoughtful API is probably the part of your software where quality matters the most. Your colleagues might forgive you a little sloppiness in the internals of this or that method. But they won't and shouldn't forgive you for creating a bad API. The Microservices pattern received its reputation after being adopted by Amazon and Netflix and showing its great impact.