For software engineers, we have two languages. One is the natural language we use to communicate with people (e.g., Chinese, English). The other is the language we use to communicate with machines and collaborate with peers -- code.
In many people's understanding, the main function of code is to "tell the computer what to do." However, in a team collaboration environment, code has an equally important, if not more important, function: to clearly and accurately convey your thoughts and intent to other engineers.
As the computer science luminary Donald Knuth said: "Programs are meant to be read by humans and only incidentally for computers to execute."
In the context of remote collaboration, the attribute of code as a "communication medium" is amplified like never before. When your colleague cannot walk over to your desk to have you explain a piece of obscure logic, when a new member needs to independently understand a complex module they've never encountered before, when the future you, a year from now, needs to maintain the "inscrutable text" you once wrote -- the readability, comprehensibility, and clarity of the historical record of the code itself become the lifelines of collaboration efficiency and project sustainability.
Messy, annotation-deficient code without a clear commit history is like a mumbling, confused communicator. Collaborating with it is a disaster. You will need to spend multiple times more effort guessing, probing, and "deciphering" its true intent, and you may introduce new errors due to misunderstanding at any time.
Conversely, clean, well-structured code with good documentation and commit history is like an excellent communicator. It clearly tells its own story: why was it created? What problem does it solve? What evolution has it undergone? Collaborating with it is a pleasure. You can quickly understand its logic and safely modify and extend it.
Therefore, a high-level engineering team must enshrine the philosophy of "code as communication." The way we treat our codebase should be the same as the way we treat the team's public knowledge base. Every code commit, every branch merge, every version release should not be seen merely as a "technical operation" but as an important, carefully organized "team communication."
In this chapter, we will start from the three core aspects of an engineer's daily workflow -- branch management, commit management, and version release -- and explore how to embed the principles of communication, transparency, and responsibility into every byte of code, making our codebase a "living" knowledge system that can self-explain and self-grow.
Branch Management: One Task, One Branch
In modern software development, using a Version Control System (VCS), especially Git, has become an industry standard. One of Git's most powerful and core features is its lightweight "branch" model.
However, a powerful tool does not automatically bring process clarity. If a team lacks a commonly agreed-upon and strictly followed "branch management strategy," the Git repository will quickly become a disorganized "maze" full of mysterious branches and conflicting merges.
A good branch management strategy has two core goals:
- Isolate changes: Ensure that ongoing, unstable development work does not affect the stability and release-readiness of the main branch code.
- Clear traceability: Allow us to easily trace every line of code in production back to its original requirement, task, and developer.
To achieve these two goals, our team follows an extremely simple yet extremely effective core principle:
One Task, One Branch
This principle means that any development task, regardless of size, must be carried out on a dedicated, independent "feature branch" created specifically for it. It is strictly forbidden for any developer to commit code directly to the main branch (such as master or develop).
The naming of this feature branch should also establish a clear link to its corresponding task. A common, good naming convention is: [type]/[task-id]-[short-description]
[type]: The type of branch, usuallyfeature(new feature),fix(bug fix), orchore(routine task).[task-id]: The task ID in the kanban system corresponding to this branch (e.g., JIRA-123).[short-description]: A brief, human-readable description of the task, connected by hyphens.
Examples:
feature/PROJ-123-user-login-with-phone
fix/PROJ-456-fix-payment-callback-error
Why Is "One Task, One Branch" So Important?
It Provides Perfect Change Isolation
When working on your own feature branch, you can experiment freely, commit half-finished work, and even make mistakes, without any worry of "contaminating" the main codebase. The main branch always remains clean and ready for release at any moment. This provides the most basic guarantee for the "continuous integration" and "continuous delivery" we discussed earlier.
It Binds "Code" to "Tasks" Tightly
This is the core value of this principle. By including the task ID in the branch name, we establish an unshakable link from "code changes" to "task cards."
When a colleague is conducting a code review, they no longer need you to verbally explain what this branch is for. They can simply look at the PROJ-123 in the branch name and immediately jump to the kanban system to see all the context for this task: requirements docs, design mockups, relevant discussions...
When a future maintainer is troubleshooting an online issue, they can use the git blame command to find the commit that introduced the problem, and from the branch name of that commit, find the original task card. This provides priceless historical clues for understanding "why it was written this way back then."
This link perfectly connects Git (code history) and Kanban (task history), two separate systems, forming a complete, end-to-end "value traceability chain."
It Makes Parallel Development Simple and Safe
In a team, multiple tasks are usually underway simultaneously. By creating independent branches for each task, different developers can work in their own "sandboxes" in parallel without interfering with each other.
When a feature is complete, it can be cleanly and controllably merged back into the main branch through a "Merge Request" or "Pull Request" mechanism.
A typical simplified Git Flow based on feature branches:
Create Main Branches
We typically maintain two long-lived main branches:
main(ormaster): This branch represents the current most stable code running in production. The code on this branch should be release-ready at any time.develop: This is the "integration branch" for all development work. All new feature branches should be created from thedevelopbranch.
Start a New Task
Pull a task from the "Ready for Dev" column on the kanban, e.g., PROJ-123.
Ensure your local develop branch is up to date: git checkout develop && git pull.
Create a new feature branch from develop: git checkout -b feature/PROJ-123-user-login.
Develop on the Feature Branch
On this branch, code, modify, and make frequent local commits.
During development, periodically merge the latest develop branch into your feature branch (git merge develop) to detect and resolve conflicts early.
Create a Merge Request (MR / PR)
When your work on the feature branch is complete and has passed local testing, push the branch to the remote repository: git push origin feature/PROJ-123-user-login.
Then, create a Merge Request from your feature branch to the develop branch on the Git hosting platform (e.g., GitLab, GitHub).
This is a crucial "communication" step. In the Merge Request description, you must again provide saturated context:
Link to the task card: Automatically associate or manually link to PROJ-123.
What problem is solved? Briefly describe the core purpose of this MR.
Why this approach? Explain your design thinking and technical choices.
How to test? Provide clear test steps or screenshots for the reviewer and QA to verify.
Conduct Code Review
@mention at least one relevant colleague in the Merge Request to review your code.
The reviewer checks the code's logic, style, robustness, and suggests improvements.
You make modifications on your feature branch based on the review feedback and commit new changes. This process may go through multiple rounds.
Merge and Clean Up
When the code review passes and all CI checks pass, the Merge Request can be merged into the develop branch.
After merging, to keep the repository clean, this remote feature branch should be deleted. Its historical mission is complete.
This process may seem more complicated than "committing directly to the main branch." But what it brings is unparalleled clarity, safety, and traceability. It packages each change as an independent "delivery unit" with complete context and collective review.
It is a way of engineering, repeatable, and highly transparent practice of the "default public," "shared responsibility," and "high-quality delivery" principles we have repeatedly emphasized in previous chapters.
Branch management is the "traffic rules" of team collaboration. Clear, universally followed rules allow countless "cars" (development tasks) to travel in parallel, efficiently, and safely on a complex "road network" (codebase). The absence of rules only leads to endless "traffic jams" and "accidents."
Commit Management: Let Every Commit Tell a Clear Story
If a "branch" defines the "boundary" of a task, then the "commits" within that branch record the detailed "thought process" of the task "from nothing to something."
In many developers' eyes, git commit is just a "save" action to store code in the local repository. Their commit messages are often casual and meaningless, such as:
"fix bug"
"update"
"wip" (Work in Progress)
"asdfghjkl" (pure gibberish)
Such commit history is utter "information garbage." It not only provides no valuable context but creates huge "information noise." When your colleague, or future you, needs to use git log to understand the evolution of a piece of code, they will see a pile of meaningless "gibberish." They will have to laboriously read the diff of each commit one by one to barely guess "what happened here back then?"
A professional engineer must treat every git commit as a small piece of "documentation." Your goal is to use clear, atomic, well-described commits to jointly tell a compelling "story" about "how this feature was built."
This "story" is written for your readers -- your colleagues, your reviewers, and future maintainers. A good "commit story" should have the following characteristics:
Atomicity
One commit should do one thing and do it well.
Don't mix multiple unrelated changes (e.g., fixing a bug, adding a new feature, adjusting code formatting) into a single commit. This makes it hard for the reviewer to grasp the main point and easy to hide subtle errors.
Before starting a new task, ensure your working area is clean. Make a commit after completing a logically independent change.
A good practice: before you submit a Merge Request, review your local commit history. If you find commits that are too trivial like "wip" or "fix typo," use the git rebase -i command to "squash" or "reword" them into a few logically clearer, atomic commits.
Good Commit Message Format
To ensure consistency and readability in commit messages, the team should agree on a common "commit message format." A widely adopted, excellent format is the "Conventional Commits" specification.
The format is as follows:
<type>[optional scope]: <description>
[optional body]
[optional footer]
<type>: Must be one of the following keywords:feat: A new feature.fix: A bug fix.docs: Documentation only changes.style: Changes that do not affect the meaning of the code (e.g., formatting, semicolons).refactor: A code change that neither fixes a bug nor adds a feature.perf: A code change that improves performance.test: Adding or modifying tests.chore: Other changes that don't modify source or test files (e.g., build process, dependency management).
[optional scope]: A scope may be provided to describe the area of the codebase affected (e.g., a module name).<description>: A short description of the change, no more than 50 characters, written in the imperative mood (e.g., "add", "fix", not "added", "fixed").[optional body]: A more detailed description of the commit. Should explain "why" the change was made and how it differs from previous behavior.[optional footer]: For additional information, most commonly to reference an external issue tracking system. For example:Closes: PROJ-123.
An excellent Commit Message example:
feat(auth): allow users to login with phone number
Previously, we only supported login with email and password.
This change introduces a new login method using phone number and SMS verification code.
This is part of the new user acquisition strategy for Q3.
The verification code service is provided by Twilio API.
Closes: PROJ-123
Compare this to a terrible Commit Message:
"add login feature"
The difference is stark. The former is an information-saturated, self-explanatory "knowledge unit." The latter is a worthless "information black hole."
Why do we make such a "big deal" out of standardizing Commit Messages?
- It greatly improves the readability of code history: When you run
git log --oneline, you no longer see a bunch of meaningless words, but a clear, structured "changelog" of the project's evolution. You can quickly scan and locate thefeatorfixyou are interested in. - It can be automatically utilized by tools: This is the most powerful aspect of "Conventional Commits." Because its format is machine-readable, we can build powerful automation processes based on it.
- Automatic Changelog generation: Tools (like
standard-version) can, when releasing a new version, automatically extract allfeatandfixcommits from the messages between two releases and generate a beautiful, user-facing "Changelog." We will discuss this in detail in the "Version Releases and the Changelog" section later in this chapter. - Triggering semantic versioning: We can automatically decide whether the next version should be a major (
BREAKING CHANGE), minor (feat), or patch (fix) version based on the committype. - Integration with CI/CD processes: We can trigger different automation processes based on commit messages. For example, a
docstype commit might only need to rebuild the documentation site, without running the full test suite.
It Forces Us to Think Deeper
When you are required to write a clear "why" for every commit, it forces you to conduct a small "self-review" before committing. "Do I really understand why I'm making this change? Can I explain it clearly to someone else?" This small "ritual" can help us avoid many logically unclear, poorly considered changes at the source.
Commit management is an engineer's "writing exercise." It tests not only your technical ability but also your communication skills, summarizing ability, and respect for detail.
An excellent engineer, like an excellent writer, carefully polishes their "work" -- both the code itself and the Commit Messages that record the history of its evolution. They know that these words will be read and reflected upon repeatedly by their readers. They have the responsibility and the pride to make the reading process clear, smooth, and enjoyable.
This is the essence of "code as communication."
Version Release and Changelog: Syncing Internally, Announcing Externally
The lifecycle of software is composed of a series of "versions." From v1.0.0 to v1.0.1 to v1.1.0, each version number change marks a small step forward for our product.
A version release is a "gathering" and "delivery" of the team's work results. It is the sacred moment when we package the scattered features and fixes developed, reviewed, and tested on the develop branch into a stable, clearly valuable "increment" and deliver it to users.
However, in many teams, the "release" process is chaotic, opaque, and even filled with fear.
No one can accurately say what specific changes are included in the upcoming release.
The decision to release is based on someone's "gut feeling," not on clear, measurable quality standards.
After release, other internal roles (product, operations, customer support) cannot timely and accurately learn about the new version's changes, making it impossible for them to communicate effectively with users.
Users, too, know nothing about product updates, or can only see a vague "fixed some bugs and optimized the experience."
A high-quality release process must address two core issues:
- Internally: How to ensure the "process" of release is reliable, transparent, and traceable?
- Externally: How to communicate the "results" of the release clearly and effectively to all relevant stakeholders (including end users)?
Semantic Versioning and Changelogs are the best practices for solving these two issues.
Semantic Versioning: Making Promises with Version Numbers
How should we name our software versions? v1, v2? Or using release dates like v2023.10.27?
These approaches all lack a key attribute: semantics. You cannot tell the "relationship" and "degree of change" between different versions from the version number itself.
Semantic Versioning (SemVer) is a widely accepted specification that gives clear meaning to version numbers. Its format is: MAJOR.MINOR.PATCH
MAJOR: Increment when you make incompatible API changes.MINOR: Increment when you add functionality in a backward-compatible manner.PATCH: Increment when you make backward-compatible bug fixes.
Adopting SemVer means you are making a clear "compatibility promise" to your users (whether external users or internal API consumers) through the version number.
When users see a version upgrade from 1.2.5 to 1.2.6, they can confidently upgrade, knowing it's just a bug fix that won't break their existing usage.
When they see a version upgrade to 1.3.0, they are happy, knowing there are new features and it is backward compatible.
When they see a version upgrade from 1.x to 2.0.0, they become very alert. They know it's a "breaking change," and they must carefully read the migration guide and modify their code to complete the upgrade.
Internally, combined with the "Conventional Commits" tool mentioned earlier, we can easily automate version number management.
If only fix type commits are included between two releases, the version number automatically increments the PATCH digit.
If feat type commits are included, it automatically increments the MINOR digit.
If commits with a BREAKING CHANGE: footer are included, it automatically increments the MAJOR digit.
This automated, code-history-based semantic versioning makes our release decisions objective, consistent, and effortless.
Changelog: Not Just a Log, But the Team's "Wall of Fame"
A new version has been released. How do we tell people what we've updated?
The answer is to maintain a clear, human-readable "Changelog."
A good Changelog should follow the "Keep a Changelog" principles:
- Create a separate section for each version.
- The latest version is always on top.
- Include the release date.
- Group all changes by category: "Added," "Changed," "Fixed," "Removed," etc.
An excellent Changelog entry example:
## [1.1.0] - 2023-10-27
### Added
- Users can now log in using their phone number and an SMS verification code.
- Added a "Forgot Password" link to the login page.
### Fixed
- Fixed an issue where the login button would sometimes remain disabled after entering correct credentials. (#456)
### Changed
- Improved the error message display for failed login attempts.
The value of a Changelog is multifaceted:
- For end users: It is our most direct and sincere channel of communication with users. It tells users that we have been working hard to improve the product for them. A continuously updated, detailed Changelog is itself a powerful tool for user trust and retention.
- For the internal team (product, operations, customer support):
- The Changelog is their "single source of truth" for product update information.
- Product managers can use the Changelog to review each version's deliverables and plan the future roadmap.
- Operations staff can design and execute corresponding campaigns based on the new features in the Changelog.
- Customer support staff can accurately respond to user inquiries based on the list of bug fixes in the Changelog.
- For the development team itself:
- The Changelog is a "monument" and "wall of fame" for the team's hard work. When we look back at the Changelogs of the past few months at the end of a quarter, the sense of accomplishment is the best morale booster.
- It also provides new members with a "time capsule" to quickly understand the product's evolution history.
Automated Changelog generation:
Manually maintaining a Changelog is tedious and error-prone. Fortunately, if we strictly follow the "Conventional Commits" specification, this process can be fully automated.
Tools (like standard-version or semantic-release) can, at release time, automatically scan all Git commits between two releases, extract all feat and fix type commits, and automatically generate or update the CHANGELOG.md file according to the "Keep a Changelog" format.
Integrated release workflow:
A complete, automated, high-quality release workflow should tie together everything we've discussed in this chapter:
- Developers complete one or more "feature branches" on the
developbranch through individual "Conventional Commits." - When ready to release, an authorized developer (or automated script) runs a release command (e.g.,
npm run release). - This command automatically:
- Analyzes the Git commits from the last version to now.
- Determines the new "SemVer" version based on commit types.
- Automatically updates the
CHANGELOG.mdfile. - Creates a new Git Tag (e.g.,
v1.1.0), and commits theCHANGELOG.mdand version number changes to the codebase. - Merges the
developbranch changes into themasterbranch. - Triggers the CI/CD pipeline to package and deploy the
masterbranch code to production.
This workflow transforms the release process, originally full of uncertainty and manual operations, into a repeatable, one-click, highly transparent automated ritual.
Summary: Code Is the Ultimate Carrier of Team Culture
In this chapter, we delved into the most minute details of an engineer's work. We found that these seemingly "pure technical" choices -- how branches are divided, how commits are worded, how versions are named -- all profoundly reflect the team's communication philosophy and collaboration culture.
"One task, one branch" embodies "default public" and "shared responsibility" in code management.
"Conventional Commits" embodies "saturated information delivery" and "asynchronous communication" in code history.
"Semantic Versioning and Changelog" embodies "high-quality delivery" and "internal and external transparency" in the release process.
A team's collaboration level will ultimately accumulate and be reflected in its codebase. You cannot fake a healthy codebase with polished PPTs or loud slogans. Code is honest. It will faithfully record every bit of our rigor and carelessness, every instance of our clarity and ambiguity.
Therefore, treat your codebase like your most important communication channel. Conscientiously organize its structure. Lovingly write its history.
Because code is communication. And excellent communication is the foundation on which we can transcend the barriers of time and space to collectively create great products.