컨벤셔널 커밋 사용 중단
출처: Hacker News
당신은 거의 확실히 Conventional Commits를 한 번쯤은 접해봤을 것입니다.
오픈 소스 프로젝트의 changelog에 그 형식이 눈에 띄었을 수도 있고,
당신이 기여한 오픈 소스 프로젝트에서 커밋 형식이 강제됐을 수도 있습니다. 많은 사람들이 이를 강력히 옹호합니다. 나는 그것을 강력히 비난합니다.
비록 그것이
a
대규모
수많은
인기
오픈
소스
프로젝트에서 사용되더라도, Conventional Commits는
잘못된 것에 초점을 맞추게 만들고
그 약속을 지키지 못하는 활동적인 나쁜 표준입니다.
초점 실패
Conventional Commits는 커밋 메시지에 의미론적 의미를 부여해 개발자와 최종 사용자가 커밋에서 어떤 변화가 있었는지 이해하도록 돕겠다고 약속합니다. 그러나 Conventional Commits는 이 목표를 극도로 실패합니다. 이를 보여주기 위해 Conventional Commit의 구조를 살펴보겠습니다.
Conventional Commit 웹사이트에 따르면 커밋 메시지는 다음과 같이 포맷되어야 합니다:
[optional scope]: <type>
[optional body]
[optional footer(s)]
커밋의 제목 줄에는 fix, feat, chore, docs, refactor 등과 같은 type(변경 종류)을 나타내는 <type>이 들어갑니다1. 그 뒤에 선택적인 scope(변경 대상)와 설명이 옵니다.
이 형식에는 큰 결함이 있습니다: type이 scope보다 우선시된다는 점입니다. 이는 완전히 역전된 사고방식입니다.
Scope > Type
Scope vs. Type
변경의 scope(변경 대상)는 커밋에서 가장 중요한 부분입니다. 이를 입증하기 위해, 다음 이해관계자들이 왜 type보다 scope에 더 관심을 가져야 하는지 살펴보겠습니다.
기여자: 프로젝트에 기여할 때, 특정 코드 영역과 관련된 변경을 찾기 위해 커밋 로그를 읽어야 할 경우가 많습니다. 그 이유는 다음과 같습니다.
- 마지막으로 기여한 이후에 무슨 일이 있었는지 파악하고 싶을 때.
- 프로젝트 전체의 관성을 이해하려 할 때.
- 풀(Pull)이나 리베이스(Rebase) 중에 진행 중인 작업과 충돌할 수 있는 커밋을 찾고 싶을 때.
커밋 로그를 읽을 때는 어떤 영역이 건드려졌는지를 살펴봅니다. type은 전혀 신경 쓰지 않고, scope에만 관심이 있습니다.
디버거: 버그를 조사할 때, 해당 버그가 발생한 컴포넌트와 연관된 영역을 건드린 커밋을 찾고 싶습니다. 여기서도 scope가 가장 중요한 정보이며, type은 전혀 쓸모가 없습니다. (버그를 고치면서 또 다른 버그를 만든 경험, 다들 한 번쯤은 있겠죠.)
사고 대응자: 서비스가 다운됐을 때, 장애 발생 시점 전후의 커밋 로그를 스캔해 문제를 일으킬 가능성이 있는 영역을 찾는 것이 효과적입니다. 여기서도 scope가 가장 중요한 단서이며, type은 무관합니다. 예를 들어, auth 스코프와 관련된 커밋이 API 오류 급증 시점에 나타난다면, 이는 문제의 유력한 원인일 수 있습니다.
그렇다면 Conventional Commits는 무엇을 할까요? scope를 너무 낮게 평가해 선택 사항으로 만들었습니다! 왜 scope가 선택 사항일까요? 스코프가 없는 커밋은 주어가 없는 문장과 같습니다! 게다가, Conventional Commits는 type을 커밋 메시지 앞쪽으로 끌어올려서 상황을 더욱 악화시킵니다. 결국 scope와 type의 우선순위를 완전히 뒤집어 놓은 것이죠.
Type은 중복되고 제한적이다
“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를 폄하합니다.
깨진 약속
우리는 Conventional Commits 형식이 형편없다는 결론에 도달했지만, 적어도 무언가는 제공해야 할 것입니다. 이제 왜 Conventional Commits를 사용해야 하는가 섹션을 읽어 보면서 이유들이 설득력이 있는지 확인해 보겠습니다.
자동으로 CHANGELOG 생성
Conventional Commits의 가장 큰 약속은 git-cliffhttps://git-cliff.org/이나 conventional-changeloghttps://github.com/conventional-changelog/conventional-changelog 같은 도구를 사용해 마지막 릴리즈 이후의 커밋을 기반으로 changelog를 자동 생성할 수 있다는 것입니다. 과연 좋은 아이디어일까요? 아니오! changelog의 독자는 커밋 로그의 독자와 완전히 다릅니다.
- changelog는 사용자에게 보여지는 문서이며, 사용자는 버전 간 비즈니스/기능 차이를 이해하고 싶어합니다.
- 커밋 로그는 개발자에게 보여지는 기록이며, 개발자는 scope 관점에서 코드베이스가 어떻게 변했는지를 알고 싶어합니다.
두 대상은 전혀 다른 입맛을 가지고 있기 때문에, 이를 하나로 합치려 하면 품질이 떨어지는 결과만 나옵니다. 그 이유는 다음과 같습니다.
- 어느 정도 복잡한 프로젝트든, 눈에 띄는 기능 하나를 구현하려면 여러 커밋이 필요합니다. 기능 구현 과정(커밋 로그)은 개발자와 기여자에게는 가치가 있지만, 최종 사용자에게는 무의미합니다. 사용자는 새로운 기능만을 원하고, 그것이 어떻게 구현됐는지는 신경 쓰지 않습니다.
- Rich가 지적했듯 revert 커밋은 Conventional Commits에 큰 문제를 일으킵니다. 개발자 입장에서는 커밋 로그 스토리 관점에서 revert가 중요한데, 최종 사용자에게는 “되돌린 변경”은 아예 없었던 변경과 동일합니다.
자동으로 의미론적 버전 증가 결정
커밋 타입에 따라 자동으로 semver 버전을 올린다는 약속도 있습니다. 겉보기엔 멋져 보이지만, 실제 소프트웨어 엔지니어링에서는 이를 정확히 수행하기가 매우 어렵습니다. 다음 상황들을 생각해 보세요:
Revert: 당신이 도입한 breaking change가 곧 되돌려졌다면, 자동 버전 계산 로직은 어떻게 처리해야 할까요? (이하 생략)