It's that time again when you are starting a new project, perhaps leading a team or being a part of the founding team. Earlier in your experience as a software engineer, you would dive into code and start building, building up standards and patterns as you go. But now, you are better, wiser, stronger, more vicious, and with a greater sense of that paranoia and pragmatism that comes with experience and shark fights.
Now, you know that you dare not start without a standard or a checklist for this project, or else things will go to hell. You have searched highway and byway and you want something opinionated, justifiable, and easy to replicate as the years go by.
I have put together a checklist I use when working in a team. To be honest, this checklist has a collective experience of 2 years and 5 projects, so you can trust it. It has also been vetted by software engineers of > 5 years of experience.
Checklist
This is an exhaustive checklist and I will update things and add justifications when I remember to do so. In the meantime, please work with this.
Tooling
First, great team work is facilitated by having the right tools: collaboration tools, task management and dev tooling.
I highly recommend the following:
Code collaboration: GitHub
Note-taking and specs writing: Google Docs. It's just too simple to use!
Project management: Linear. Again, simplicity meets great features.
All of these must find some link into the code collaboration tool - GitHub. Linear provides integration for GitHub.
Code review
For code review, 2 people for passes is good enough. Let reviews be constructive and not contain phrases like, "My 2-year-old brother would cringe at this" or "Your code made me regret not being a carpenter, you drill too many props!".
Non-trivial comments can be prefixed with "nit" as in "nit: you should add a log here" or "nit: Lambda functions generally shouldn't have to be named, but I understand why you needed that in a function".
Ensure every team member writes code to prevent godly see-finish.
I highly recommend 10 commandments for navigating code reviews.
Branch existence
Prayer: God help me explain this without having to drop these diagrams usually used by articles on Git 🥹
TL; DR
Irregular release schedule:
feature-branches
-> dev
-> staging
-> main
Regular release schedule:
feature-branches
-> dev
-> staging
-> release-branches
-> main
The following branches must exist:
dev
: This should be the default branch set for the repository and all PRs should point to it. There are exceptions I'll explain below. The development branch should have its deployment environment set up.staging
: When features are ready to be tested by the team or the QA team, thedev
branch is periodically merged into thestaging
branch. The staging branch should have its deployment environment set up.main
: This branch contains the code deployed and faces the end users. Code fromstaging
branch is usually merged into this branch when it is time to release a feature to the end users.
Other branches
Feature branches are branches created by developers when working on features or making general code changes.
Hotfix branches are generally pointed to the
main
branches and are intended to fix bugs in production that require urgent attention. Bugs that are instaging
can have their hotfix branch pointed to staging. They should have the formathotfix/<ticket_id>
.release
branches existence depends on the product development methodology. In cases where features are released to users in batches and at the end of sprints, release branches are used. When release branches exist,staging
no longer point tomain
but resolves to a branch,release-<version_number>
where the version number is auto-incremented. Whenstaging
is good to go by the QA team, a new branch is created out of it for that release and this branch is usually called the beta branch. Note thatmain
still serves end users, hence the use of the term, "beta". Beta is deployed and users who opt for beta features get to use this to play around. When this branch is stable, it gets merged to themain
branch and everyone is happy.
God answered my prayer, haters!
Commits
Commit convention should follow semantic commit guidelines which I'll break down in brevity:
A great format is <branch_name or ticket_id> | <commit_type>: <commit_message>
. The advantage of this is that the commit messages when viewing logs gives you an idea of:
The branch this commit was made on
The type of change introduced
The actual change introduced
in one short glance. A good commit message looks like so:
MMM-419 | Chore: Added tests for login feature
Commit type is one of:
Feature: For new features, breaking changes
Fix: Bug fixes
Refactor: Like replacing needless abstract classes with interfaces
Chore: For simple changes that don't impact features. Such as formatting a file, installing a dependency, and renaming variables. Use this if you don't know where to categorize your change.
Buggy: This should be rarely used, but in cases where a commit deliberately has bugs. This happens when you desperately need to save your working state
Doc: Addition to documentation.
Commits should be granular as much as possible. Avoid committing 25+ files as a single commit.
Naming conventions
Naming must be predictable and consistent. It is like the battle plan that everyone knows.
Branch names
Branch names should be directly tied to the task. You can use <ticket_id> which is usually made up of the project three letter code and the ticket number such as, "MMM-419". For additional information, it could be prefixed with the type of ticket: "feature/MMM-419". Types include feature, bug, hotfix, and improv. Personally, I love the format: <first_name>/<ticket_id>
like so: lordsarcastic/MMM-419
. This gives at first glance who "owns" that ticket without having to consult the project management tool.
PR/MR names
PR titles should use the format: <ticket_id> | <ticket title>
. This is a sweet spot between project management collaboration tool integration and knowing what a PR does.
Note: I'll use "PR" moving forward
PR specifications
PR specification
I don't have much to say here since I don't enforce it. I think PRs should only contain explanations as to why a certain decision was taken when it can't be a comment in the code. PR description is already in the ticket description. If for frontend, should contain screenshots of features added. Breaking or deprecation changes should also be noted in the PR description.
Merging the PR
When a PR is merged, the branch should be deleted, this way you don't have stale branches. Only major branches: dev
, staging
and main
are never deleted.
You can try deleting them and lose your job.
Merge strategy
Merge strategies come in handy for certain teams when the codebase becomes large. At this point, you want to choose the "Squash" option when merging. This compresses all commits in the PR into a single commit. This makes rollbacks easy when a branch introduces some serious bugs that cannot be resolved peacefully but must be rolled back.
Keeping track of history and merge conflicts
The author of a branch is responsible for keeping the branch up to date and resolving conflicts.
git rebase
should be avoided at all costs to resolve merge conflicts or to update the state of a branch. git merge origin/<parent_branch
should be used instead. Else when branches diverge, it will be a nightmare ensuring everyone is in sync. Rebase simply ensures a single, linear commit history regardless of what everyone is working on and this means commits across several branches can be erased in favour of what the branch author prefers. I don't see any advantage of such in a large codebase. Rebase is only good when working alone.
I recommend this section of Git Rebase vs Merge: A complete guide to see why you don't want to rebase in a large codebase.
RFCs
Also called "Tech specification" (hello Matchday).
For giant features with questionable changes to the codebase, it is good that someone write a tech spec concerning the feature which documents the technical side of the feature before it is worked on. It serves as a reference document when writing code. It will contain schema propositions, contracts between two communicating systems, and so on.
I highly recommend The Art of Writing RFCs to understand this in detail. It helps you make the decision on when you need RFCs and when you don't.
CI
Ensure you've got GH actions going. Ideally, it should do the following:
Check that the code is formatted properly
Check that the code is linted
Check that all tests pass
CD
It is good to set up preview URLs for branches so that each branch has a live URL that can be used to test it out before being merged. Vercel has this feature baked in for the frontend folks. Heroku also has the same feature. If you're using something more custom, you'll have to set this up yourself.
Makefile
Please use Makefiles for repeated commands in the application. Makefiles help shorten long commands into short ones you can remember. All you have to do is create a file in the root:
command:
@long command
The "@" symbol ensures that the command text is not spilled on the terminal when you call the command with make <command>
. Refer to the Makefile section of an article of mine to see use cases.
Conclusion
This is a highly opinionated checklist I have used over time. Feel free to reuse and customize to meet your team needs. Ensure to recommend this article to your friends and teammates at work and drop a comment if you think there is something that can be improved on.
Eat well, sleep well, rest well and join me on DevMeets on Sunday!