- Every project
- Version Control
- Unit Tests
- Integration Tests
- End-to-end Tests
- Code Test Coverage
- Static Code Analysis
- Runtime Code Analysis
- Dependency Analysis
- Monitoring Errors
- Monitoring Performance
- Environment Management
- Feature Flags
- Development environment setup
- Pre-commit configuration
- Continuous Integration
- Continuous Deployment
- Packages and Libraries
- Multiple language versions
- Multiple OS
- Back-end Projects
- HTTP Server
- Web Sockets
- Data Storage
- File Storage
- Data Serialization
- Server-Side Validation
- Background Jobs
- Payment Processing
- Front-end Projects
- UI Kit
- State Management
- Rich-Text Editors
- Client-Side Validation
- Quick Summary
- Current State of Things
- The Future
If you are about to start a new software project or wish to improve the maintainability of an already existing one, this checklist might be just the resource you need.
How you set up your project will have a considerable effect on how easy it will be to maintain one as time goes on.
With all the new frameworks and libraries popping up on Github on almost a daily basis, it's hard to wrap your head around what exactly are the essential steps and tools you need to pick to have a healthy project for both you as a developer and your users.
This article lists all that as a checklist so that you can always come back to it and check if you are missing any opportunities for an easier time with your project.
It's currently available only for Ruby on Rails, but it will help you get a general idea, which is more or less the same for every other programming framework.
The following applies to every programming project, no matter if it's an open-source NPM package or an internal FastAPI.
Version control is a system that tracks and manages changes to files or code, enabling multiple users to collaborate and revert to previous versions if necessary.
Thankfully, it already became the standard in the software industry.
The code is written in units and unit tests are meant to test those units. Those units can be for example:
We're talking about code that's sole purpose is to make sure our other code works.
If you write a piece of code and do not write tests to it, it instantly becomes legacy code.
While at it, it's also good to address common requirements such as mocking or recording HTTP requests, or testing time-related code.
Combined units of code compose programs, and programs' purpose is to execute often complex procedures.
The way to test the code across multiple units is called integration testing.
End-to-end testing is a software testing approach that validates the entire application's functionality, communication, and integration with external systems, ensuring it performs as expected from start to finish.
Its purpose is to check if the program works as a whole.
It tells you how much percent of your code has executed during the test run.
While 100% test coverage is a pain to achieve and does not guarantee there won't be any errors, it gives you an idea of the code's ease of maintainability.
When there's a code, you can also expect:
- naming conventions,
- formatting styles,
- file and directory structures,
- known performance issues,
- known security vulnerabilities.
Static code analysis tools help address those problems, help to avoid bikeshedding, prevent security breaches from happening, and inform of potential bottlenecks.
The advantages of using those tools are similar to Static Code Analysis. The difference is that it happens during the code execution.
One example of this is avoiding N+1 SQL queries using the Bullet gem in Ruby on Rails.
Nowadays there's plenty of code that already does stuff for us on the low level.
It comes in libraries and packages, and those are often developed independently of our code base.
While it's a good thing, we still need a way to easily check if there are new versions of the dependencies.
With all the tests and static code analysis tools in place, there are still going to be errors in your code.
It is crucial to see when they happen, so you can address them.
There are multiple easy-to-integrate tools to do that, that comes with user-friendly admin dashboards.
Application Performance Monitoring (APM) is the process of tracking and analyzing an application's performance, responsiveness, and resource usage to optimize the user experience and identify potential issues.
This allows you to observe how your code performs in the production environment and pinpoint the bottlenecks.
At the very minimum, there are development and production environments, and some of their settings vary.
You need a way to define those, per environment.
Using Feature Flags, also known as Feature Toggles, is a powerful technique, that allows developers to modify system behavior without changing code.
Ideally, you want a single command to set up the whole development environment for newcomers, once they download the code.
It's nice to have a way to locally (and optionally) run all the tests and static code analysis tools once we add stuff to the code base.
Adding stuff to the code base usually means
git commit -m '...', and there are certain ways to hook into what happens before the commit is created.
When there's a new code integrated into the main code-base, you want to at the very least see if:
- all the tests pass,
- code analysis tools checks pass,
This is often tightly coupled with your chosen version control system and falls into the established git workflow.
Anything that's automated helps to avoid human error.
You don't want errors when deploying new code to the production environment.
It is a very good practice to extract parts of complex systems into smaller packages.
While they're easier to test and understand, the best benefit of doing that is their re-usability.
This way we can keep very different code-bases DRY.
The following is a short list of common requirements while developing a package or library.
The established practice is for the package to work with the lowest maintained version of the programming language it is written for.
This does not necessarily apply to every package but is nowadays still good to test - especially when using the underlying native OS libraries under the hood.
Building an API, or a Back-end, is a common requirement while developing software.
We've identified the following common requirements while developing APIs.
The way to expose your code to be requested via HTTP protocol by either the whole outside world or defined clients.
Next to the simple request-response cycle, there's often a need to enable a real-time, bidirectional data exchange over a single, persistent connection established between the client and the server.
This is what Web Sockets are for.
A common API requirement is to use a database or other type of data store.
Next to storing plain data, we often need a way to store whole files in various formats, such as PDFs or images.
It is not necessarily related to storing the aforementioned data.
A common need to present the selected response data is addressed via data-serialization libraries.
It means controlling what we respond with (vs what we store) and the format we respond in, such as JSON, XML, CSV, or more.
A quite common requirement when reading the data by API client is to search through it.
There are well-established approaches to solving this problem that helps us avoid reinventing this particularly complex wheel.
If you receive data, you need to confirm it is valid and inform the client if it is not.
Establishing a common error structure comes a long way, allowing clients to consume and present errors easily, as well as making it much simpler to test.
A way to manage users accessing your API. This means storing some kind of credentials that only they can prove of being true.
The way to define access policies to the various parts of your system is based on the user's roles or other criteria required to be met.
Tightly coupled with authentication and authorization, the administration is a way for the application admins to perform various operations on the API system.
Multi-tenancy is an architecture that allows a single instance of a software application to serve multiple customers, or tenants while keeping their data separate and secure.
A way for the users to group their data under many shareable accounts.
The way to execute certain parts of the code in the background.
This means asynchronously executing code in the background to provide optimal performance for the end users.
Today's standard of communicating with users. We're used to receiving emails when certain things happen, such as:
- creating an account,
- completing orders.
It's also good to out-front handle working with emails in the development environment.
Another email-related common requirement is processing incoming emails, better known as working with Mail-Transfer Agent.
A way to apply programming paradigms to rendering files' content, such as conditionals and loops.
Best known to for example conditionally render parts of HTML websites.
It's a common requirement to handle responding in different languages based on clients' requests.
There are plenty ready to use ways to both translate and maintain translations in APIs.
Most of the time it all comes down to making money, right?
This is a very common requirement to charge users for various functionalities.
Building a Front-end can mean many different things, such as:
- Web page
- Browser application
- Mobile application
- Native system application
- Browser extension
And following are common requirements when building various front-ends.
A UI Kit is a collection of pre-designed user interface components, such as buttons, forms, and navigation elements, that developers and designers can utilize to create consistent and visually appealing applications.
All kinds of front-end building blocks.
State management in frontend development refers to the organization, storage, and manipulation of an application's dynamic data and its user interactions.
Various inputs often come with chosen UI Kit, but there are also several commonly required other types of inputs, such as:
- currency input,
- country select,
- language select,
- image input with edit capabilities,
and many many more.
A special type of input, a
<textarea /> replacement that allows to easily format long text, including:
and many, many more.
It is good to have a single consistent way to handle the way users manipulate data provided in the frontend application.
Next to the aforementioned server-side validation, it is nice to validate what's possible on the client side.
It means a better user experience as happens in flight and not after the form submission.
Same as in the back-end APIs, there's often a need to present content in a language selected by the end-user.
Animations are visual effects that create the illusion of motion by displaying a sequence of images or transforming elements over time, enhancing user experience and engagement.
Tracking users' engagement and the ways they use the frontend application is crucial to better understanding their needs and problems.
Do not confuse errors and performance monitoring.
That's a handful, right?
Of course, we could make it even more granular - one example would be splitting the Static Code Analysis tools into three categories:
- Style checks
- Security checks
- Performance checks
And it might be useful to think of them in this way, especially when searching for ready-to-use solutions for your existing projects.
Anyway, I'm not saying we shouldn't do that and any suggestions on doing so are welcome.
To this day, there isn't a single go-to solution that:
- Covers all of those requirements.
- Leave you the choice of tools you want to use to do so.
- Stays up to date.
- Does not enforce particular conventions.
Most often, "project starters" lack particular requirements coverage or, even worse, are left unmaintained.
The reason for it is simple: the complexity of maintaining such starter is really big, and it is not used often enough to bother.
If there is a solution worth considering, it decides on using an opinionated approach to address those requirements.
It is not necessarily a bad thing (still might be), but with this much of requirements, it most likely means time spent on either:
- Learning how to use a new tool,
- Replacing the tool with one of our choices.
This might still make sense, but obviously is a trade-off.
I have identified common requirements for various project types.
When there's so much repetition (we're talking about EVERY project), there's room for automation.
I believe we can also agree that there is a need to do that.
When you, as a programmer/entrepreneur, think of starting a new project, you don't want to focus on running your code linter in the continuous integration pipeline.
Or most of the other mentioned stuff, for that matter.
You want to focus on the business logic and need all that working out of the box, so implementing it goes smoothly, and maintaining it is bliss.
This is what I'm doing at hix.dev currently:
- creating it for Ruby on Rails,
- preparing to create it for Next.js,
- have a system in place to launch and maintain other frameworks.
It's early days, still much work to do, but my approach makes it easy to maintain and further develop.
It takes a lot of setup and configuration to create a code-base that is pleasant to work with, maintain and further develop.
Still, it is very much worth it, and the sooner you do that, the less tech debt will pile-up.
I truly hope that this list can help both improve the existing projects, as well as give an overview to start the new ones.
I'm looking forward to your comments on this - thanks for reading!