TL;DR

  • 커밋 메시지를 코딩 전에 작성하면 작업자의 활동이 아니라 소프트웨어의 목표 행동을 약속하게 되며, 이를 통해 작업의 초점과 설계 합의, 커밋 단위의 명확성이 높아짐.
  • 테스트 주도 개발(TDD)의 테스트는 검증이 아니라 시스템의 행동을 설명하는 요구사항이며, 구현 전에 작성하는 것이 자연스러운 접근임.
  • Use Set instead of List 같은 메시지는 작업 기록인 반면, “장바구니에 항목이 1만 개를 넘어도 충돌하지 않음”은 소프트웨어의 행동을 설명함.
  • Jujutsu의 스쿼시 워크플로는 파일을 수정하기 전에 설명이 담긴 빈 리비전을 만들며, Git에서는 git commit --allow-empty와 git commit --amend로 같은 방식을 적용할 수 있음.
  • 2012년 시작된 이 방식은 페어 간 합의, 집중력, 작은 목표, 리뷰, 설계 대화, 시간 제한, 브랜치 흐름, 커밋 이력의 자연스러운 세분화를 이끌어냄.

행동, 테스트가 아닌

  • Daniel Terhorst-North의 저서 *Introducing BDD*는 행동 주도 개발(BDD)이 TDD를 단순한 테스트로 보지 않는 직관에서 시작됐다고 설명함.
  • “테스트”라는 말은 개발자를 검증과 과거로 향하게 하므로, Daniel은 “행동”이라는 표현을 선호함.
  • 이 관점에서 테스트는 클라이언트나 사용자의 시점에서 시스템의 행동을 외부에 드러내며, 시스템이 해야 할 일을 서술형 문장으로 설명함.
  • 따라서 테스트는 구현이 존재하기도 전에 비즈니스 언어로 작성하는 비즈니스 요구사항임.

왜 먼저 테스트하는가

  • 테스트 대상 시스템이 존재하기도 전에 테스트를 작성하는 일을 직관에 반하거나 터무니없다고 여기는 사람도 있음.
  • 하지만 “테스트”를 “요구사항”으로 바꾸면, 목적지를 모른 채 코드를 작성한 뒤 완료 시점에 요구사항을 알아내는 방식이 오히려 이상해짐.
  • TDD를 요구사항 주도 개발(Requirement-Driven Development)이라고 불렀다면 직관에 반한다고 여기는 사람은 없었을 것임. “테스트”라는 말이 이미 완성된 것을 검증한다는 인상을 주는 것이 문제임.

집중을 유지하는 데 정말 유용한 방법은 “시스템이 아직 하지 못하는 일 중 다음으로 중요한 것은 무엇인가?”라고 묻는 것임.

  • 이 질문은 다음 테스트를 정하고, 테스트는 의도와 약속을 표현함. 테스트를 작성하면 제품의 미래 행동에 구체적으로 전념하게 됨.

커밋은 약속임

  • 커밋 메시지도 같은 관점으로 바라보면 Git 용어의 “커밋”이 우연한 표현이 아닐 수 있음. 커밋을 만든다는 것은 약속을 하는 일이기도 함.
  • Git에서는 코드가 이미 완성된 뒤 커밋하므로 약속이 다소 늦게 이뤄짐.
  • 반면 Jujutsu에서는 관용적인 순서가 정반대임. 먼저 jj commit을 실행한 다음 코드를 작성함.

약속을 어떻게 설명할 것인가

  • Use Set instead of List는 약속도 행동 설명도 아니며, 수행한 작업을 보고하는 문장임.
  • BDD 방식으로는 “장바구니에 항목이 1만 개를 넘어도 충돌하지 않음”처럼 쓸 수 있음.
  • 두 표현의 차이는 꾸밈의 차이가 아님. 첫 문장은 프로그래머가 수행한 과거의 작업과 방법을 다루고, 두 번째 문장은 기능이 현재 보이는 행동과 결과를 다룸.

내가 한 일이 아니라 소프트웨어가 하는 일을 설명하기

  • 두 번째 형식은 Git 이력에서 읽고 싶은 내용이며, 변경 로그 자동 생성을 약속하는 Conventional Commits의 취지와도 맞닿아 있음.
  • 커밋을 체크아웃하면 누군가 소프트웨어가 올바르게 행동하도록 작업했다는 점은 알 수 있음. 하지만 그 커밋 위에서 작업할 때 중요한 질문은 “프로그래머가 작업 시간에 무엇을 했는가?”가 아니라 “지금 프로젝트는 어떻게 행동하는가?”임.
  • Ferdinando Santacroce가 자주 말하듯, 커밋 메시지는 코드만으로 추론할 수 없는 맥락과 동기, 해결책에 이른 사고 과정을 담아야 함.
  • 이 점만으로도 커밋 메시지 작성을 대형 언어 모델(LLM)에 맡기지 않을 이유가 충분함.

먼저 작성하기

  • Eric Willeke의 게시물은 버전 관리에 테스트 우선 접근을 적용하는 간결한 관점이며, 즉시 공감대를 형성함.
  • 구현 전에 테스트를 작성하는 데 익숙하다면 코딩 전에 커밋 메시지를 쓰는 일도 자연스러움. 메시지는 의도를 표현하고 커밋은 약속이 됨.
  • Jujutsu의 스쿼시 워크플로는 파일을 하나도 건드리기 전에 설명이 담긴 새 빈 리비전을 만드는 방식임. 이를 수행하는 명령이 jj commit인 것도 우연이 아님.
  • 커밋 메시지는 의도이자 작업 단위임. 먼저 작성한 뒤, 그것을 사실로 만듦.

이 방식을 적용하면 생기는 일

  • 2012년 팀과 함께 Git에서 이 기법을 시작함. 빈 커밋을 git commit --allow-empty -m <message>로 만든 뒤 작업을 마치면 git commit --amend를 실행하는 방식임. 일부 Git 그래픽 사용자 인터페이스(GUI)는 이를 쉽게 처리함.
  • 페어 작업자의 방향을 맞춤. 드라이버든 내비게이터든 상대와 메시지 및 목표에 합의해야 하므로 대화가 촉진됨. 프로그래밍을 시작할 때는 달성할 목표에 실제로 합의한 상태가 됨. 혼자 코딩할 때도 자신의 이해와 합의를 이뤄야 하며, 그 과정은 마찬가지로 어렵고 보람이 있음.
  • 집중하기 쉬워짐. 테스트와 커밋 메시지가 코딩 세션의 중심이 되며, 다른 데로 빠져도 메시지에 적힌 약속이 다시 방향을 잡아줌.
  • 작업 범위가 작고 명확해짐. 언제 코딩을 멈춰야 하는지, 어느 정도면 충분한지, 완료했는지를 판단하기 쉬워짐.
  • 커밋 리뷰가 단순해짐. 변경사항을 확정하기 전에 검토할 때 “무엇을 했더라?” 대신 “약속한 것을 모두, 그리고 그것만 했는가?”라고 물을 수 있음.
  • 메시지의 정확성과 충실도가 높아짐. 코딩 전에 메시지를 쓰면 구체적으로 표현하기 쉬우며, Fix나 Update Repository.cs 같은 모호한 문장을 피하게 됨. 처음부터 메시지가 목표이므로 메시지와 결과물도 자연스럽게 일치함.
  • 메시지를 정하는 과정마다 작은 설계 세션이 열림. 페어 상대와 원하는 행동을 설명할 단어, 도메인 개념의 이름, 행동을 소유할 컴포넌트에 합의해야 함. 이러한 설계 질문을 해결해야 메시지를 작성할 수 있음.
  • 자연스러운 시간 제한이 생김. 안정된 상태에서 다음 작은 목표로 나아가는 작은 단계를 약속하면, 변경사항이 목표에 속하는지 판단하기 쉬움.
  • 수명이 짧은 기능 브랜치와 잘 맞음. 메시지가 작은 목표를 정의하고, 브랜치 이름이 더 큰 목표의 맥락을 제공함. 브랜치 이름 역시 설계상 사전에 정해짐. 두 수준의 목표가 방향을 잡아줌.
  • 커밋 이력의 세분성이 자연스럽게 높아짐. 각 커밋은 하나의 목표를 가지며 단일 책임 원칙(SRP)을 따른다고 볼 수 있음. 기능 브랜치는 행동을 읽기 쉬운 순서로 보여주고, 각 커밋은 더 큰 이야기의 작은 단계가 됨. 자연스러운 변경 로그도 만들어짐.
  • 이 실험은 2012년 Arialdo Martini, Mattia Piccinetti, Gian Marco Gherardi, Guglielmo Brasile, Francesco Pichierri, Giuseppe Mariano가 시작했으며, 처음에는 *Pre-emptive Commit Comments*에서 설명됨.
  • 이 방식은 Ferdinando Santacroce의 책 *Git Essentials*에서 자세히 설명됨.

참고 자료

  • Jujustu
  • Conventional Commits
  • Pre-Emptive Commit Comments
  • Ferdinando Santacroce Git Essentials - Ferdinando Santacroce