This post has been translated to Chinese by Zhang Wei: Getting the Details Right (Chinese)
On the internet, you can find more definitions for “Software Architecture” than I want to list here. But I hope you agree with me that software architecture is more about the high level structure of a system, and “software design” is more about the details, the low-level stuff.
The more I think about it, the more I realize: If you don’t get those little details right, you cannot have a great high-level structure. If you don’t have a good software design, your software architecture also cannot be that great.
I am talking about the little design decisions we take every day, every hour. Almost every minute.
How do I name this variable, function or class? When should I split a function or class? Can I make this function pure or does it make more sense right here to reference some state? How should other modules access / use this module? Am I missing an abstraction here? Is there another place in the code where a developer has to know the same concepts this code deals with? How do I design the interface of this module to its test? What should my next test do? Is this test small, focused, independent and clear? …
We design all the time when we are writing code. You cannot not design when you program. You cannot not answer the questions from above. But when you do not think about them, your answer will not be the best solution - Maybe even not a good one.
When some members of your team do not care enough to keep the design clean and good, they will also not care enough to keep the architecture in a good shape.
Code does not really “rot”. As long as we do not touch it, it stays exactly like it is. But as soon as we make changes, it seems to get worse. That’s because with every careless design decision you will cause damage. Sometimes just a little damage, sometimes a lot.
It takes a lot of effort and care to keep code clean. But you have to do it, otherwise you will become slower and slower over time. And everybody on the team has to take part - Everybody has to care.
Good Names and other Design Principles
When you cannot find good names for the “little things”, you will have a hard time figuring out where to change code when you have to. No matter how well-documented your architecture is.
If you do not stick to the dependency inversion principle, you’ll have technical details littered over your business code. Your clean, layered architecture alone will not protect you.
If your classes, modules and methods have multiple responsibilities and direct dependencies, changes will ripple through your system. And you won’t see this in your architecture diagrams, with their clear-cut components.
I could go on with other design principles that mostly effect your low-level design and still can undermine your architecture. Well, you might say, “If it can undermine my architecture, then it’s really and architectural decision”. But I think this is a slippery slope - Soon, every design decision will be an architectural one.
Tests and Documentation
Good design is also about good tests - And a well designed interface between your tests and your production code.
Tests - especially “micro tests” - can be a great technical documentation of your software design. But only when you write good, independent tests and give them good names. You need to design the interface of your tests to the production code, and the interface of your tests to the programmer who has to interpret a negative test result!
But when you get this right, the test are better than any other documentation you could write: They cannot be outdated - Either they are still relevant or they will fail. Beware: Even if you have great tests as documentation, you might still need some other forms of technical documentation!
Can you also use tests as architecture documentation? You might be able to document some aspects of your architecture using automated tests. But a good architecture documentation does not only document the high-level structure of your system. It also documents all the decisions you took and why you took them.
So, to really document your architecture, you will also need additional documentation. The Arc42 Template is a good start. But do not overdo it: Keep the document short - Just add what is absolutely necessary!
When you do not get the documentation of your design details - The production modules/functions and their micro tests - right, you will again undermine your architecture: Your architecture documentation will tell you in which general area to look for something. But when you arrive there, you need clean code, documentation and tests to navigate to the exact place.
Why is this Important?
I have worked with many code bases that are hard to maintain (because helping customers to improve their hard-to-maintain codebase is one of the services I provide). And in my experience, it’s not a few, huge architectural problems that cause the majority of the pain. Sure, those are often there, and they cause big problems.
But most of the pain comes from the thousands of small design problems that are there. A method with a bad name. Calling a method from a collaborator that should really be somewhere else. Huge modules and functions. Tests that are not independent. Tests with bad names. Test suites where one little change in the production code can break 10, 20 or more of your tests.
Sure, sometime you just have to get something out on time, and fix it later. But when you neglect your your design for too long, those little problems will slow you down considerably. And this slowdown happens much earlier than most developers would expect. It can happen a few weeks or even days or hours after you took a “short cut”.
Micro Services to the Rescue?
Do we still need a good internal design when we do micro services? We are writing them for replacement, not for reuse, right? We are planning to throw some of them away on a regular basis…
Well, it depends. Mostly on the size of your micro services. And as far as I can tell, the internet did not come to a conclusion about the “right” size for micro services yet…
So, if your micro services are “big” (on a relative scale) and vertically integrated, you should keep their internal design clean. Because the code of each “self contained system” will be big enough that a mess will slow down your future development considerably!
But if your micro services are really small (just a hand full of functions or classes), then you still have to take care about the little things - but some of those little design decisions now affect how your micro services work together, and not the modules within a micro service.
Good Design != Good Architecture
You need to get the details right to have a good architecture. But just because you have a good low-level design does not automatically mean that you have a good architecture.
You have to care about both. But if you want to get started, start with the design: You can easily practice it in a small part of your code or even in a training. You can refactor more and more of your code to have a nice internal design. When you are getting good at this, start thinking about the bigger scheme of things - Start designing your architecture.
If you want, you can start now. Try TDD, and get help if it does not work for you. Learn about good design - for example, the SOLID principles, the four rules of simple design, coupling and cohesion, …
But at some point, you need to get your whole team on the same page. You have to sell them on better design. Otherwise, somebody else will always undermine the design improvements you are trying to make.