All men can see these tactics whereby I conquer, but what none can see is the strategy out of which victory is evolved.
- Sun Tsu
There are many aspects to good software and many different, sometimes conflicting criteria by which you measure it.
You way want to write software that is easy to write automated tests for. This could be in direct conflict to writing fast software. Or maybe you are optimizing for development speed and may not care if a few bugs make it in production. In an airplane or car, bugs in the software could kill lives and writing software in these industries takes a looong time. Maybe you want the software to have more features, or maybe you are trying to avoid feature bloat.
All of these are desirable properties and each deserves a whole blog post of its own. Today however I want to write about maintainable software specifically and show how choosing your goals guides your development process.
But before we start, let's talk about what maintainable software even means. Most software is changing all the time. Writing maintainable software is about making it easy to implement these changes by yourself or your collegues.
This implies writing software that is easy to read, understand and makes it easy to identify where to apply these changes.
Additionally, when you want to apply a change, you want to ideally touch only one place or at least minimize the places you have to touch.
And most importantly, you want your changes to only affect the part of the system that you want to change and not have unintended side effects.
Like most things in software (even though programmers don't like to admit this) judging the maintainability is subjective to some extend.
Examples from the web
Consider the following hypothetical examle of some information displayed in the users profile:
<div class="user-name">Jan Herlyn</div>
<div class="user-street">Happy Street</div>
<div class="user-city">Granada</div>
With the corresponding css
.user-name {
font-size: 1.5rem;
color: #aaaaaa;
margin-top: 1rem;
}
.user-street {
font-size: 1.5rem;
color: #aaaaaa;
margin-top: 1rem;
}
.user-city {
font-size: 1.5rem;
color: #aaaaaa;
margin-top: 1rem;
}
In this case, if we wanted to change the font size for instance we would have to edit all three css rules. In this case it is obvious that this is a problem or at least invites to make mistakes. Especially if these rules were scattered throughout our css file. Though this example is of course made up and simplified, the class of problems happens so often that some people came up with the acronym DRY short for don't repeat yourself. One way to improve this code would be to create a single rule matching all three classes.
.user-name, .user-street, .user-city {
font-size: 1.5rem;
color: #aaaaaa;
margin-top: 1rem;
}
This is better in the sense that you would only have to change the font-size or color in one place. But even this version still has room for improvement: What if you want to add another item later. If for instance you would want to also show the country, you would now have to add it to the css file as well as the html:
<div class="user-name">Jan Herlyn</div>
<div class="user-street">Happy Street</div>
<div class="user-city">Granada</div>
<div class="user-country">Spain</div>
.user-name, .user-street, .user-city, .user-country {
font-size: 1.5rem;
color: #aaaaaa;
margin-top: 1rem;
}
An even better solution would be to create one class which applies to all items with the same behavior.
<div class="user-info">Jan Herlyn</div>
<div class="user-info">Happy Street</div>
<div class="user-info">Granada</div>
<div class="user-info">Spain</div>
.user-info {
font-size: 1.5rem;
color: #aaaaaa;
margin-top: 1rem;
}
This code will make it easy to display additional information and change the existing styles.
The point of this discussion isn't to tell you how to write your css and give you specific instructions that you are to follow dogmatically. The point is to show how we develop specific tactics or practices that achieve our primary goal of maintanability.
And like most things in prorgamming, it is also context sensitive. Consider the following html code from the tailwindcss documentation to display a notification:
<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4">
<div class="shrink-0">
<img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div>
<div class="text-xl font-medium text-black">ChitChat</div>
<p class="text-slate-500">You have a new message!</p>
</div>
</div>
Tailwind is a CSS framework that provides you with utility classes that each affect a specific css properties. The class py-8 for instance applies to properties
padding-top: 2rem;
and padding-bottom: 2rem;
to the div. At first glance this seems to contradict the goals of writing software that is easy to read and change. You obviously can't change the properties of py-8 because it might affect the whole page. The whole div is also kind of hard to read because of all the different classes that are applied to each element.
Additionally if you had to repeat the code in a different place you would have to write or copy paste this whole mess again. And afterwards changing the style of a notification would imply adding or removing classes from all notifications you have throughout your html.
Within the context of using a modern JavaScript framework in which you define components and later reuse them, all of these disadvantages are mitigated:
export default function Notification({ title, text }) {
return (
<div class="p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4">
<div class="shrink-0">
<img class="h-12 w-12" src="/img/logo.svg" alt="ChitChat Logo">
</div>
<div>
<div class="text-xl font-medium text-black">{title}</div>
<p class="text-slate-500">{text}</p>
</div>
</div>
)
}
Later you can reuse this component and don't have to think abuot how a change might affect different parts of the code. Additionally all information is now in one single place. You won't have to modify a css file if all you want to do is change the notification style. All information affecting notifications is local in one place.
Don't get me wrong. I'm not saying that if you are writing react code, you should use tailwindcss and if you write classic html you are supposed to write classic css with well named and well defined classes. It is still a matter of taste, priorities and your teams organization and preferences.
What I'm saying is: Writing maintainable code is about empathy. To write maintainable code, you will have to think about how it will affect you and your collegues in the future. If you know already that you will never touch your code again, you can basically write whatever is fastest and forget about it. If you work in a large team on a mission critical project that will be in use by it's users for many years to come, you will have to think about how to make it easy for others to understand and modify. This may include thinking about conventions that will make following your code easy, how to communicate them, writing a style guide for contributers to the project, writing documentation and how to keep it up to date.