Either through intention or incident, the architecture of your software typically reflects at least some patterns. After all, if your development team is reasonably committed to conform to at least some kind of internal standards, we can expect certain architecture patterns to emerge.
What differentiates a junior developer who has read a pattern book from a more experienced developer reading the same book?
Book smart vs. street smart
There are plenty of books on software architecture (some even by your favorite author!), along with even more online resources: articles, blogs, videos, free for everyone to consume. And while it is rather easy to pick up a copy of an architecture book, merely reading them arguably moves you further away from designing good software.
That’s a counterintuitive claim, to be sure. But with your now-gained knowledge of architecture patterns, aren’t you likely biased towards solving future challenges by trying to apply what you’ve learned?
I call it the law of the instrument, and it may be formulated as follows: Give a small boy a hammer, and he will find that everything he encounters needs pounding.
If you try really hard, you can find hundreds of design patterns, all of which are used by some people in their code. But that’s other people’s code and other people’s patterns. They may work for them, but they are not universal.
What matters more than the patterns that you found and want to apply is the context you want to apply them in. Out of the patterns you know and love, which of those allow you to write better code, and in what ways?
Go write them down, I’ll wait.
Even the term better code is incredibly vague: Shorter code can be better. Elegant code can be better. Code can often also express intent through comments or explicitness – a shortcut may achieve the same result but no longer conveys the original meaning. What better code is varies a lot even for different developers in the same company.
And so it is little wonder that, in trying to find patterns improving your code, you’re often found surveying the landscape within your company of what common goals you want to achieve. That leaves you with options to explore. There’s never just one way to build software, yet different paths lead to different outcomes.
And learning what works for the context you’re in is essential to gaining experience. The best way to learn is not by imitating others blindly, but by applying patterns alongside your own knowledge. If you think you’re on the wrong way, you need to have the confidence to turn back and analyze why it didn’t work out.
Optimizing for developer use
Every building block you provide for developers to use in their day-to-day work should be focused on solving actual business problems. Patterns can simplify common workflows that need solving dozens or hundreds of times by providing a common starting point.
Questions that I’ve seen commonly asked could include the following – all of which have been answered plenty of times in any existing project:
- How do I ensure that only users with certain permissions can use some functionality?
- How do I convert records between different architecture layers (entity, business, transport objects, frontend)?
- How do I write a scheduled job to run at certain times?
- Or, more generally: How do I do X?
Yet all of these questions have merit on their own. With varying previous experience, you could estimate that each developer has a different idea of what is the most natural solution. Not that they’re wrong, of course: The only way to find out what works and what doesn’t is, essentially, experimenting and learning.
There’s enough variations to make solutions unique, but they don’t have to be. If 80% of your code revolves around the same 3-5 types of problems, then the solution to each of them should be as simple & straightforward as possible and require very little extra effort on the developer’s part on architectural concerns.
How flexible is your architecture, anyway?
The most obvious way to be introduced to ways of writing software is through the language you’re using & the basic frameworks you pick. You’ll have a hard time writing console applications that benefit vastly from the dependency injection model in Spring; just as it is unlikely that a MVVM approach found in WPF translates well to your REST API. It’s not impossible, but you’re often guided towards certain ways of writing software by the frameworks you pick.
Which only leaves you with the task of, well, finding a fitting solution. If you find one, it gives you a good headstart on gravitating towards common solutions. But how, oh how, can you decide between numerous choices? Do you…
- … pick a popular framework such as spring because, duh, what else would you write a backend Java application in?
- … evaluate your use case first because, duh, why would you pick a framework that brings plenty of features you won’t use?
- … write your own framework, because none available fulfills your needs exactly?
- … write a library that supplements an existing framework and supports the missing features?
Every kind of tooling that you use to develop software fulfills a number of functional and non-functional aspects, and each approach can lead to entirely different approaches of how code is written.
Opinionated frameworks can and often are, incidentally, providing you with very clear patterns on how to approach your architecture. You’re bound to implement things in certain ways, and in rare cases need to work around existing conventions. And they are great if they’re helping you getting to even 80% of how you want your software to look like.
Non-opinionated frameworks are a tad different: They can enable you to support your favorite patterns, but require you to know far more about the problem domains of both the business and the underlying technology in advance.
Questions which you could address include, but are not limited to:
- How hard is it for a new developer to understand?
- How easy is it to make mistakes?
- How much guidelines & structure is given to junior developers?
- How easy is it to deviate, if needed be, against technological and organizational resistance by more senior developers?
- Can things be designed in a general way that helps me, or anyone else, in the future?
- There are plenty of different design patterns, but there are no universally applicable patterns.
- Whether a pattern is good or bad depends on your use case, your requirements, your environment on your developers.
- Good patterns work in your favor: They enable you to understand architectures & write software faster.
- Bad patterns make it harder to work towards solutions even for common use-cases.
- Through experience, you can identify common benefits & drawbacks, and can evaluate whether a certain approach is beneficial.
- Commonly found in statements like “This approach is bad, and while it solves your problem, please rewrite your code”. ↩
- Just think of yourself in 3 years in the same company; or in 5 years in another company. Which parts of what you’ve learned will help you, and which parts are irrelevant? ↩