The Maintainable Program Manifesto (MPM) ✍️
TL;DR of the MPM
Is your software change-proof? This manifesto provides you with an easy and practical 13-factor checklist to know if your app is maintainable and reliable, regardless of the frontend, backend, or infrastructure technology.
It focuses solely on the application level, not on the team and organization. You will not see stuff here such as “Have an assigned QA team” or “develop in small teams”. These organizational decisions do not belong here.
It was written to be light and accessible, so we won’t dive very deep into any of the 13 factors. I tried to leave a quote from a renowned developer/former developer in each section, in case you want to do further research on the topic. I’d be also happy to answer any of your doubts in the comments.
There will be a point system at each stage that you can use to have some hard marks. (Look for the 🏀).
To all of the (rightfully) lazy developers I provided a form that you can use to tick your answers and automatically get a grade! (More at the end)
Who is this for?
MPM is for any developer that has ever struggled when maintaining any kind of application. If you want to reduce the friction of a new joiner in your project, or simply want to understand how you can improve your team’s application, then bingo. By the end of this article, you’ll have some fun stuff to talk about in your next standup or tech retro.
Code exchange & access
1 — Source control
If you don’t have source control, you’re going to stress out trying to get programmers to work together.
— Joel Spolsky, The Joel Test
This is a quick and old one. In order for developers to instantly exchange new code, it is essential for them to have a source control system.
Developers NEED a way to push and pull changes into the most up-to-date codebase, making sure that what they’re building upon is what’s other people are also working upon.
This also brings some useful features like restoring to past commits to quickly roll back any sneaky bug or even version tracking.
🏀 Congratulations! If you use a source control system, you just scored 3 points!
2 — Single repo
I don’t know if you can already tell but I’m pretty lazy when developing. The thing I most hate is having to jump around 3 or 4 different sources of truth to get an overview of what already exists when I arrive at a new product.
Notice how I didn’t say “monorepo”, because according to Wikipedia:
“(…) a monorepo is a software development strategy where code for many projects is stored in the same repository”, which is definitely not what I’m evangelizing.
What I’m saying is having a single repo that contains EVERYTHING (but secrets of course) that your project needs to work: database rules, backend, frontend, and infrastructure. This guarantees that no knowledge is kept only by one or two developers.
If your application gets magically wiped out from its hosting data center(s), the repo should contain all that is needed to put it back up the with the exact same scale and performance as it was before.
🏀 3 points for the single repo doers!
3 — CI/CD pipeline — a fast one!
“At an abstract level, a deployment pipeline is an automated manifestation of your process for getting software from version control into the hands of your users.” — David Farley
CI/CD is mandatory if you want to be flexible. Your code & new features should not be away from your users too long. If you constantly deploy and release into production you will more easily create a quick feedback culture, where the user’s actions and needs (rightfully) guide your development.
This should also be fast for proper development to take place, you should be able to act on the misdoings and errors of your pipeline the day the error occurs. If you need to wait long periods of time for even knowing that the pipeline needs to be fixed, then it will be in a constant “red” state, accumulating errors and entering a harmful cascade mode.
🏀 Score 3 points if your pipeline runs under 20min, and 2 if it runs over 20. No points if you do not have a pipeline
4 — Proper testing
“Write tests until fear is transformed into boredom”
― Kent Beck
You should be confident shipping good code. Also, you should be confident shipping bad code. You should have absolute confidence that if your code harms user experience in any way, your pipeline will get it, preventing it from reaching production.
Be aware that having confidence in your pipeline doesn’t mean that sometimes a sketchy bug won’t sneak its way through. But if you properly act upon these (hopefully) scarce events, they will happen less and less.
This is achievable through testing strategies. E2E testing and pact testing are all strategies that you could use to properly try to catch any sneaky bug that might have been mistakenly introduced.
️⚠️ If you do not have more than 95% test coverage, you CAN’T be confident in your tests.
🏀 Are you confident that if your pipeline goes green on a new push, then all of the functionality will be delivered to your users? Congratulations, you just won 5 points and are on your way to providing a delightful product
5 — Deploy in one step
“There should be two tasks for a human being to perform to deploy software into a development, test, or production environment: to pick the version and environment and to press the “deploy” button.” — David Farley on Continuous Delivery
I narrow it down to one since 99% of the time you‘ll want to test the latest version.
git push is all that it should take to initiate the deployment phase. More steps in the process introduce human error. If there are checks that need to be done, just automate them.
This should also not take too long. I’d recommend running only your unit tests on the push phase, thus making it as painless and as easy as possible.
The pipeline should take care of the rest.
🏀 3 points for the 1 step deployers!
6 — Zero downtime deploy
Hitting deploy (or
git push) should be a fearless action. If you are going to provide downtime for your uses it automatically is not.
🏀 3 points if your deploys give 0seconds of downtime. 2 points if under 30 seconds
Understanding & Maintainability
7 — Quick local feedback loops
“Because testing is so essential, we should be doing it all the time as an integral part of the development process.” — Nicole Forsgreen
In order for any app to survive, it needs to have a proper local development environment.
Developers must be able to quickly run isolated checks on the code that they changed before the shipping step. And they need to be fast.
This is the single most valuable metric to evaluate your codebase quality. These tests are the immune system of your codebase figurative body, as so, the faster they “act” the better.
You should be constantly running these scans when you change any piece of code, and the faster they are the more you will run them. Trust me, I’m lazy.
🏀 Score 3 points if your unit tests run under 10 seconds, 2 if under 30, and none otherwise.
📝 If 10 seconds looks like an eternity for your unit tests then chances are you are using an outdated concept of unit test. Unit test should be thought as an integration test on a unit and its dependencies, in a sociable manner. If this is still confusing to you I highly recommend checking this article.
8 — Full test coverage with the refactoring rule
“Refactoring is a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behaviour.” — Martin Fowler
Your unit tests either test behaviour or implementation details. If you’re doing things properly it’s the former, so it naturally follows that refactoring should not make your tests fail.
Behaviour-driven testing is a must to ensure that your codebase reflects your desired functionality. The refactoring rule simply says that refactoring code shouldn’t make ANY test fail.
If you want to learn more about how you can achieve this I’ve written an article that will definitely help you.
🏀 Does 100% of your code obey the refactoring rule? 3 points for you!
9 — Do Test Driven Development
“Tests are stories we tell the next generation of programmers on a project.” — Roy Osherove
You might be wondering why I’ve put this quote in the TDD section. Treating your tests as “stories to be told” is only possible when you think of them in a behavioural oriented way. And Test-driven development is highly tied with behavioural testing because it forces you to write the tests before the implementation, thus abstracting you from it’s details.
In the beginning, it is rather odd and seems like it slows development. It’s a bit like investing, and you will mostly collect the benefits in the long run.
🏀 2 points for the TDD lovers!
10 — No technical documentation
“The unit tests are documents. They describe the lowest-level design of the system.”
― Robert C. Martin, The Clean Coder
Documentation rots. Period. People either forget or are too lazy to update it.
The underlying technology of a product is constantly changing and trying to “hard” document it will most likely be a boomerang that will come back and hit you straight in the face.
You should only document things that you want to be inflexible.
If you are not over-engineering, then your architecture and code structure will be easy to understand for new joiners. And as stated above by Robert, the tests should cover the low-level design.
🏀 Browse your docs. If you did not find anything related to code specifics (code structure, syntax, or architecture) then score 3 points!
11 — Infrastructure as Code
“The enabling idea of infrastructure as code is that the systems and devices which are used to run software can be treated as if they, themselves, are software.” — Keif Morris
I absolutely love the expression “Code is live documentation”. Code (IaC) is impossible to “rot” because it’s not a representation of the infrastructure, it's the infrastructure itself!
Forget drawings, who has time or memory to update it when a minor detail changes? Also, it normally only has an overview, small details like configuration of underlying resources are left in the deep corner of the repo, normally known only to its mystic creator.
You should have a file that contains all of your infrastructure and backing services configurations. With the rise of modern cloud application development, this doesn’t need to be in a traditional markup language, with Python and Typescript emerging as widely used IaC languages.
If you use and properly organize your IaC, you will have an always up-to-date page that states EXACTLY what your app needs to function at its current scale and speed.
🏀 IaC definitely deserves 3 points
12 — Reachable hosts
Sometimes you’ll need to debug. And when that happens, being able to connect to the underlying REPL shells can be the difference between minutes and days of painful digging around.
If it is local development it comes for free, but when you connect to remote machines, you should be able to connect via SSH to execute small scripts with
sudo rights (although you should never (need to) do it in the production environment)
🏀 2 points if you can SSH into your remote machines!
13 — Observability
Following the same train of thought as the last factor. When something breaks you want to get behind it fast. One tool that must be in your belt is an event stream of your backing services. Ideally, you would also have some tracing enabled, giving you easier visualizations and event streamlining. But I won’t be too picky here, I’d rather you focus your efforts on avoiding these situations, rather than coping with them.
🏀 You get 3 points if you have an event stream of your backing services logs.
⭐️ Point system and classifications!
It will take you to a Notion webpage with a simple form and a results table. Here’s a screenshot:
You can also follow the table at the beginning of the article.
That’s all folks! 🥕
Let me know if there’s anything that you’d change in the comments.