Introduction
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.
In case you'd like to have a more visual representation of this checklist, along with a deeper explanation of options, check out this project builder I have made as a part of Hix 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.
Let's roll.
Table of Contents
- Introduction
-
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
- Back-end Projects
- Front-end Projects
- Quick Summary
- Current State of Things
- The Future
- Conclusion
Every project
The following applies to every programming project, no matter if it's an open-source NPM package or an internal FastAPI.
Version Control
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.
Unit Tests
The code is written in units and unit tests are meant to test those units. Those units can be for example:
- functions,
- classes,
- modules.
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.
Integration Tests
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 Tests
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.
Code Test Coverage
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.
Static Code Analysis
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.
Runtime Code Analysis
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.
Dependency Analysis
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.
Monitoring Errors
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.
Monitoring Performance
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.
Environment Management
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.
Feature Flags
Using Feature Flags, also known as Feature Toggles, is a powerful technique, that allows developers to modify system behavior without changing code.
Development environment setup
Ideally, you want a single command to set up the whole development environment for newcomers, once they download the code.
Pre-commit configuration
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.
Continuous Integration
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.
Continuous Deployment
Anything that's automated helps to avoid human error.
You don't want errors when deploying new code to the production environment.
Packages and Libraries
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.
Multiple language versions
The established practice is for the package to work with the lowest maintained version of the programming language it is written for.
Multiple OS
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.
Back-end Projects
Building an API, or a Back-end, is a common requirement while developing software.
We've identified the following common requirements while developing APIs.
HTTP Server
The way to expose your code to be requested via HTTP protocol by either the whole outside world or defined clients.
Web Sockets
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.
Data Storage
A common API requirement is to use a database or other type of data store.
File Storage
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.
Data Serialization
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.
Searching
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.
Server-Side Validation
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.
Authentication
A way to manage users accessing your API. This means storing some kind of credentials that only they can prove of being true.
Authorization
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.
Administration
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
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.
Background Jobs
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.
Emails
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.
File-Templating
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.
Internationalization
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.
Payment Processing
Most of the time it all comes down to making money, right?
This is a very common requirement to charge users for various functionalities.
Front-end Projects
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.
UI Kit
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
State management in frontend development refers to the organization, storage, and manipulation of an application's dynamic data and its user interactions.
Inputs
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.
Rich-Text Editors
A special type of input, a <textarea />
replacement that allows to easily format long text, including:
- headers,
- lists,
- quotes,
- links,
- embeds,
- images,
- tables,
and many, many more.
Forms
It is good to have a single consistent way to handle the way users manipulate data provided in the frontend application.
Client-Side Validation
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.
Translations
Same as in the back-end APIs, there's often a need to present content in a language selected by the end-user.
Animations
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.
Analytics
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.
Quick Summary
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.
Current State of Things
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.
The Future
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.
Conclusion
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!
Top comments (5)
I completely agree with remarks on the significance of setting up a software project correctly. It not only makes it effortless to maintain but also enhances the overall experience.
I love to hear that! Is there any area in particular that you find especially important to setup at the very beginning?
I believe that good code quality and architecture are crucial areas to focus on at the very beginning of a project. By establishing clear coding standards, practices, and guidelines, it becomes easier to maintain the codebase throughout the project's lifecycle. Additionally, designing a scalable and efficient system architecture can prevent future technical difficulties as the project grows.
In my view, perhaps we should start with a software project with clearly defining the project's goals and setting up the development team (in-house, outsourced or hybrid).
Teams change, code stays - and that's exactly why, from the very beginning, we must clearly define the things that make keeping the code in check and easy to maintain - no matter the team. I do agree with defining the goals tho.