停止使用 Conventional Commits
Conventional Commits encourages focus on the wrong things

原始链接: https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/

本文认为“Conventional Commits”(约定式提交)标准是一种存在根本缺陷的反模式,因为它将错误的优先级置于首位。作者指出,该标准对提交“类型”(如 feat、fix)的强调是多余且具有限制性的,因为提交描述本身就应自然地传达变更目的。 作者认为,对于开发者、调试人员和事故响应人员而言,“作用域”(scope)才是最重要的元数据。由于约定式提交将作用域视为可选内容,并强制将类型置于消息开头,导致其无法提供导航代码库历史记录所需的必要上下文。 此外,作者批评了该标准对用于生成变更日志和版本控制的自动化工具的依赖,并指出提交日志与面向用户的变更日志具有不同的用途,无法有效合并。文章总结称,这些自动化流程往往脆弱或具有误导性。作为替代方案,作者提倡使用“作用域提交”(Scoped Commits)——这是一种在 Linux 和 Go 等成功项目中采用的格式,旨在优先考虑项目特定的上下文。作者建议放弃所谓的“行业标准”约定式提交,转而采用更直观、以作用域为前缀的提交信息格式。

Hacker News 上关于“Conventional Commits”(约定式提交)的争论,凸显了自动化工作流支持者与该规范刚性语法批评者之间的分歧。 **支持约定式提交的理由:** 支持者认为,像 `feat:`、`fix:` 和 `feat!` 这样的前缀对于 CI/CD 流水线至关重要。它们能够实现自动化的语义版本控制(SemVer),并有助于通过分类变更来生成更新日志。一些人发现,标准化的提交结构提高了可读性,并有助于一眼识别出变更的“影响范围”。 **批评意见:** 反对者认为该系统难看且不灵活,且往往忽略了最重要的上下文,例如问题工单编号。批评者指出: * **元数据应放在别处:** 像工单 ID 这样的信息更适合通过“Git trailers”(页脚)来处理,而不是污染提交主题行。 * **对象错误:** 更新日志应为用户精心策划,而非直接从面向开发者的提交日志中自动生成。 * **设计不佳:“**类型”前缀(如 `feat`)与清晰的“范围”相比通常是多余或无益的,且“Conventional Commits”这一命名被视为对 Linux 或 FreeBSD 等项目长期采用的实践进行的一种品牌霸占。 最终,许多人建议,书写工整的纯英语摘要比强制执行、充斥标点符号的模板更为有效。
相关文章

原文

You’ve almost certainly encountered Conventional Commits before. It may have reared its ugly head in the changelog of an open source project you’ve used. It may have been the enforced commit format for an open source project you contributed to. A lot of people swear by it. I swear at it.

Even though it is used by a large number of popular open source projects, Conventional Commits is an actively bad standard which encourages focus on the wrong things and fails to deliver on its promises.

Focus Failure

Conventional Commits promises to add semantic meaning to commit messages to aid developers and end-users in understanding the changes made in a commit. However, Conventional Commits fails to do this in spectacular fashion. To demonstrate this, let’s look at the anatomy of a conventional commit. According to the Conventional Commit website commit messages should be formatted as follows:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

The commit’s subject line has a <type> (something like fix, feat, chore, docs, or refactor) describing the type of change. Following that, there is an optional scope, and then a description.

This format has a major failing: type is prioritised over scope. This is exactly backwards.

Scope > Type

The scope of a change (the subject of the change) is the most important part of a commit. To demonstrate this, let’s consider why each one of the following stakeholders care about the scope of the change more than the type of the change:

  • Contributors: when you are a contributor to a project, you often need to read the commit log to identify changes in the codebase relevant to a certain area of the code. There are many reasons for this including:

    • Wanting to catch up on what has happened since the last time you contributed.
    • Trying to understand where the project’s overall inertia is.
    • Looking for commits that might conflict with your in-progress work when pulling or rebasing.

    As you read the commit log, you’re looking at what areas were touched. You really do not care about the type of change happening, you care about the scope of the change.

  • Debuggers: when investigating a bug, you often want to look through the commit log to see what changes might have touched areas related to the component where the bug manifested. Once again, the scope is the most important piece of information. The type of change is entirely useless because bugs can be introduced in any change regardless of type. (I’m sure we’ve all experienced writing a bugfix that caused another bug.)

  • Incident responders: when production is down, scanning the commit log for changes that were made around the time of the outage is an effective way to identify what areas may be causing the problem. Scope is once again the most important piece of information you can have at this point. For example, if you see a commit related to the auth scope at the tip of the spike of inbound API errors, it’s a likely culprit for the problem. And once again, type is irrelevant because bugs could have been added by any change.

So what does Conventional Commits do? It deprioritises scope so much that it’s optional! Why the hell is scope optional? Having a commit without a scope is like having a sentence without a subject! Then, to add insult to injury, Conventional Commits elevates type to the front of the commit message. Conventional Commits gets the priority of scope and type entirely wrong.

Type is Redundant and Restrictive

You might be thinking “so it may be backwards, but commit type is at least still important, right?” and to that I say “no”. A commit’s description should almost always tell you the type of the change! Consider this commit message as an example:

fix(compiler): prevent namespaced SVG <style> elements from being stripped

Even if you only had the description, it’s obvious that it was a bugfix! Space on the subject line of a commit is already at a premium, wasting characters on the type is not helpful! But it’s often even worse than useless; it’s often restrictive. Take this commit message as an example:

refactor(core): Update webmcp support to use document.modelContext

This commit updated the webmcp functionality in the core component to support both document.modelContext and navigator.modelContext, so was that a bugfix, refactor, or new feature? I would argue it’s all of them! But again, the only thing that really matters is that it was a change to the core/webmcp component.

Conventional Commits fundamentally focuses on the wrong thing (the commit type) and devalues the scope (which is what people actually care about).

Broken Promises

So we have determined that the format of Conventional Commits sucks, but it must provide some benefit. Let’s read the Why Use Conventional Commits section to see if any of the reasons make any sense.

  • Automatically generating CHANGELOGs.

    This is the biggest promise of Conventional Commits: you can run a tool like git-cliff or conventional-changelog to generate a changelog from the commits since your last release. Is this even a good idea? No! The audience of a changelog is entirely different than the audience for a commit log!

    A changelog is user-facing, and the user cares about understanding the functional differences between versions. They care about what changed from a business/functional perspective.

    A commit log is developer-facing, and the developers care about reading a story of how the codebase has changed over time. They care about what changed from a scope perspective.

    As you can see, these are two entirely different grains, and any efforts to combine them result in subpar results. The reasons for this are multiple:

    • In any moderately complex project, it takes multiple commits to land any notable feature. The process of landing the feature (as documented by the commit log) is valuable for developers and contributors, but it’s useless for the end-user. The end-user only cares about the new feature, not how it was built!
    • As Rich pointed out, reverts are problematic for Conventional Commits. Revert commits are important from a commit log story perspective for developers, but to the end user, a change that is reverted is equivalent to a change not made.
  • Automatically determining a semantic version bump (based on the types of commits landed).

    This sounds nice, but the realities of software engineering often interfere significantly with the viability of accurately accomplishing this task. Consider the following situations:

    • Reverts: imagine a situation where the breaking change you introduced was actually so breaking that you have to revert it? Your tooling will pick up a breaking change and increment the major version even though the breakage was actually reverted and there is no breaking change.
    • Accidental breakages: maybe the breakage is subtle and you don’t realise a change is a breaking change when you make the change. Only in retrospect realise that it’s breaking. You will incorrectly increment a minor/patch version when a major version bump is necessary.
    • Retroactive unbreakages: say you later add a commit which, in composition with a previously breaking commit, results in a diff which is not breaking. Similar to the revert situation, tooling would incorrectly identify a breaking change.

    In such situations, you could rewrite history with a rebase, but that often breaks or is prevented by workflows. It also presents a revisionist history to the contributors trying to contribute to the project, reducing the reliability of the story the commit log is telling.

  • Communicating the nature of changes to teammates, the public, and other stakeholders.

    As we have established up to this point, teammates and the public have very different needs from a changelog and commit log. Conventional Commits manages to solve neither.

  • Triggering build and publish processes.

    This is just a bad idea. Say you only run automated security checks on commits that touch code and then someone creates a Trojan-horse commit titled docs: fix typos which actually introduces vulnerabilities into the authentication subsystem? Obviously, that sort of malicious activity would hopefully be caught in code review, but the automated tooling is bypassed, putting the onus on a human to identify the problem.

    Compute is cheap, just use git diff to identify changed files (scope, once again) and run build/publish processes based on that.

  • Making it easier for people to contribute to your projects, by allowing them to explore a more structured commit history.

    More structured, sure. Making it easier to contribute? Not at all (as we have already demonstrated at length).

Not a single one of the “selling points” for Conventional Commits actually holds water.

Conventional Commits is also extremely difficult to apply to a project. You are supposed to define your own set of “types”, but pretty much everyone just takes the defaults from commitlint which often don’t fit well with the particulars of individual projects. This problem is especially acute in corporate environments where change management and audit requirements often mandate a ticket number in every commit message. The <scope> field is the obvious place to put it, but this ends up replacing the only useful metadata in a Conventional Commit with a completely useless ticket number.

A Better Way

So what should you do instead? Follow the lead of truly successful software projects like Linux, FreeBSD, Git, Go, and NixOS! What do these projects have in common? They all use scope-prefixed commit messages (where “scope” is defined to be relevant to the actual project). Usually, the scope to use on a given project is self-evident. For the Linux kernel, the subsystem is the natural scope. For Go projects, the package path is the natural scope. For a project using a microservice architecture, the microservice name is the natural scope.

Here are some examples of projects and their commit format guidelines.

Unfortunately, despite being used by some of the most successful open source projects ever created, this commit style seems to have lost the branding war. I intend to change that. Introducing scopedcommits.com. The website is dedicated to advocating for a return to commit message sanity, and separating the concern of changelog generation from commit log management.

Conclusion

Conventional Commits’ purported advantages are actually illusory and the industry has seen no tangible benefit from using it as a standard. However, Conventional Commits unfortunately seems to have become fairly popular in open source projects, and due to this it seems like AIs have a habit of defaulting to using it for commit messages. This has caused propagation of anti-pattern-ridden commit messages across projects.

My goal in this article is to fight against Conventional Commits’ dominance, and demonstrate that there better ways to structure commit messages. But if this article has not convinced you to stop using Conventional Commits, I look forward to the flame war in the comment section.

联系我们 contact @ memedata.com