Conventional Commits가 잘못된 점에 집중하도록 만든다
출처: Hacker News
아마도 Conventional Commits(https://www.conventionalcommits.org/en/v1.0.0/)를 한 번쯤은 보셨을 겁니다.
오픈소스 프로젝트의 changelog에 눈에 띄었을 수도 있고,
당신이 기여한 오픈소스 프로젝트에서 커밋 형식으로 강제되었을 수도 있습니다. 많은 사람들이 이를 강력히 옹호하지만, 저는 그것에 강하게 반대합니다.
비록 이 방식이
a
대규모
다수의
인기 있는
오픈
소스
프로젝트에서 사용된다 하더라도, Conventional Commits는
잘못된 것에 초점을 맞추게 만들고
그 약속을 지키지 못하는 활발히 나쁜 표준입니다.
Focus Failure
Conventional Commits는 커밋 메시지에 의미론적 의미를 부여해 개발자와 최종 사용자가 커밋에서 어떤 변화가 있었는지 이해하도록 돕겠다고 약속합니다. 하지만 Conventional Commits는 이 목표를 엄청난 방식으로 실패합니다. 이를 보여주기 위해 Conventional Commit의 구조를 살펴보겠습니다.
Conventional Commit 웹사이트에 따르면 커밋 메시지는 다음과 같이 포맷됩니다:
[optional scope]: <type>(optional scope): <description>
[optional body]
[optional footer(s)]
커밋의 제목 줄에는 fix, feat, chore, docs, refactor 등 변경 유형을 나타내는 <type>이 들어갑니다1. 그 뒤에 선택적인 scope가 오고, 이어서 설명이 붙습니다.
이 형식에는 큰 결함이 있습니다: type이 scope보다 우선시됩니다. 이는 완전히 뒤집힌 순서입니다.
Scope > Type
Type”>변경의 scope(변경 대상)는 커밋에서 가장 중요한 부분입니다. 이를 증명하기 위해, 다음 이해관계자들이 왜 type보다 scope에 더 관심을 가져야 하는지 살펴보겠습니다.
기여자: 프로젝트에 기여할 때, 특정 코드 영역과 관련된 변경을 찾기 위해 커밋 로그를 읽어야 할 경우가 많습니다. 이유는 다음과 같습니다.
- 마지막으로 기여한 이후에 무슨 일이 있었는지 파악하고 싶을 때.
- 프로젝트 전체의 관성을 이해하려 할 때.
- pull 혹은 rebase 중에 진행 중인 작업과 충돌할 수 있는 커밋을 찾고 싶을 때.
커밋 로그를 살펴볼 때, 여러분은 어떤 영역이 건드려졌는지 보고 싶습니다. 변경 유형은 전혀 신경 쓰지 않으며, scope만이 관심사입니다.
디버거: 버그를 조사할 때, 해당 버그가 발생한 컴포넌트와 연관된 영역을 건드린 커밋을 찾고 싶습니다. 여기서도 scope가 가장 중요한 정보이며, type은 전혀 쓸모가 없습니다. (버그를 고치다 또 다른 버그를 만든 경험, 모두가 한 번쯤은 있죠.)
인시던트 대응자: 서비스가 다운되면, 장애 발생 시점 전후의 커밋 로그를 스캔해 문제를 일으킬 가능성이 있는 영역을 찾습니다. 이때도 scope가 가장 중요한 정보입니다. 예를 들어, auth scope와 관련된 커밋이 API 오류 급증 시점에 나타난다면, 이는 문제의 유력한 원인일 가능성이 높습니다. type은 무관합니다.
그렇다면 Conventional Commits는 무엇을 할까요? scope를 선택 사항으로 만들 정도로 낮게 평가합니다! 왜 scope가 선택 사항일까요? scope가 없는 커밋은 주어 없는 문장과 같습니다! 게다가, Conventional Commits는 type을 커밋 메시지의 앞부분에 배치해 더욱 상황을 악화시킵니다. 즉, scope와 type의 우선순위를 완전히 뒤바꿔 버린 것이죠.
Type is Redundant and Restrictive
“역시 type이 뒤집혔을지는 몰라도, 적어도 type은 여전히 중요하지 않을까?” 라고 생각하실 수도 있습니다. 하지만 답은 아니오입니다. 커밋 설명만으로도 변경 유형을 알 수 있기 때문입니다. 예를 들어, 다음 커밋 메시지를 보세요:
fix(compiler): prevent namespaced SVG elements from being stripped
설명만 봐도 이것이 버그 수정임은 명확합니다! 커밋 제목에 이미 공간이 부족한 상황에서 type에 문자를 낭비하는 것은 도움이 되지 않습니다. 오히려 type은 때때로 제한적일 수 있습니다. 다음 커밋을 살펴보세요:
refactor(core): Update webmcp support to use document.modelContext
이 커밋은 core 컴포넌트의 webmcp 기능을 document.modelContext와 navigator.modelContext 모두를 지원하도록 업데이트했습니다. 그렇다면 이것은 버그 수정, 리팩터링, 혹은 새로운 기능일까요? 저는 모두라고 주장합니다! 하지만 실제로 중요한 것은 core/webmcp 컴포넌트가 변경되었다는 점뿐입니다.
Conventional Commits는 근본적으로 잘못된 것(커밋 type)에 초점을 맞추고, 사람들이 실제로 관심을 갖는 scope를 폄하합니다.
Broken Promises
우리는 Conventional Commits 형식이 형편없다는 결론에 도달했지만, 그래도 어떤 이점이라도 제공해야 하지 않을까요? 아래의 왜 Conventional Commits를 사용해야 하는가 섹션을 읽어 보며 이유가 타당한지 살펴보겠습니다.
자동으로 CHANGELOG 생성
Conventional Commits의 가장 큰 약속은 git-cliff(https://git-cliff.org/)이나 conventional-changelog(https://github.com/conventional-changelog/conventional-changelog) 같은 도구를 사용해 마지막 릴리즈 이후의 커밋을 기반으로 changelog를 자동 생성할 수 있다는 것입니다. 정말 좋은 아이디어일까요? 전혀 아닙니다!
- 대상 독자 차이: changelog는 최종 사용자에게 보여지는 문서이며, 사용자는 비즈니스/기능적인 관점에서 버전 간 차이를 이해하고 싶어합니다.
- 커밋 로그는 개발자를 위한 것이며, 개발자는 scope 관점에서 코드베이스가 어떻게 변했는지를 알고 싶어합니다.
두 대상은 완전히 다른 입맛을 가지고 있기 때문에, 이를 하나의 문서에 합치려 하면 품질이 떨어집니다. 이유는 다음과 같습니다.
- 복잡한 프로젝트에서는 눈에 띄는 기능 하나를 구현하기 위해 여러 커밋이 필요합니다. 커밋 로그에 기록된 구현 과정은 개발자와 기여자에게는 유용하지만, 최종 사용자에게는 전혀 의미가 없습니다. 사용자는 “새 기능이 추가되었다”는 사실만 원합니다.
- Revert(되돌리기) 커밋은 Conventional Commits에 큰 문제를 일으킵니다. 개발자 입장에서는 커밋 로그 스토리에서 중요한 의미를 갖지만, 최종 사용자에게는 되돌린 변경이 아예 없었던 것과 동일합니다. (Rich가 지적한 바와 같이: https://richvdh.org/conventional-commits-considered-harmful.html#reverts-are-hard)
자동으로 Semantic Version bump 결정
커밋 유형에 따라 자동으로 버전 번호를 올린다는 아이디어는 매력적이지만, 실제 소프트웨어 엔지니어링에서는 여러 현실적인 제약 때문에 정확히 수행하기 어렵습니다. 예를 들어:
- Reverts: 여러분이 도입한 breaking change가 나중에 되돌려졌다면, 자동 버전 상승 로직은 이를 어떻게 처리해야 할까요?
- Hotfixes, feature flags, experimental branches 등 다양한 상황이 얽히면 단순히 type만 보고 버전을 올리는 것은 위험합니다.
결국, Conventional Commits가 제시하는 “자동 버전 관리”와 “자동 changelog”는 현실과 동떨어진 기대에 불과합니다.
각주
[1] type은 fix, feat, chore, docs, refactor 등으로 정의됩니다.