1P by GN⁺ | ★ favorite | 댓글 1개
  • Chesterton’s fence는 이유를 모르는 코드를 함부로 바꾸지 말라는 조언이지만, 이유를 남기지 않은 코드와 커밋 기록은 후임 개발자에게 같은 부담을 떠넘김
  • 해당 저장소의 지난 13년간 커밋 본문은 명령어 기준 295줄뿐이고, dependabot·revert·typo 관련 본문을 빼면 167줄로 줄어듦
  • 커밋 제목은 큰 변경에도 “fix page A”처럼 맥락을 주지 못하며, 별도 문서와 코드 주석도 거의 없어 변경 이유를 추적하기 어려움
  • 코드에는 끝나지 않은 리팩터링, 제거된 기능의 잔재, 추가됐지만 연결되거나 사용되지 않는 기능, 아무도 쓰지 않는 듯한 기능이 남아 있음
  • 커밋 메시지와 문서는 “무엇을 바꾸는지, 왜 바꾸는지, 왜 좋은 해결책인지”를 최소한이라도 남겨야 하며, 아무것도 남기지 않으면 뒤에 오는 개발자의 비용이 커짐

이유 없는 코드가 남기는 부담

  • Chesterton’s fence는 어떤 것이 왜 그렇게 되어 있는지 이해하지 못했다면 함부로 바꾸지 말라는 비유임
    • 프로그래밍에서도 이상해 보이는 코드를 “고쳤다가” 나중에 그 이상함에 이유가 있었음을 알게 되는 일이 생길 수 있음
  • 이 사례에서는 반대편 문제가 드러남
    • 이상한 코드와 결정은 많지만, 왜 그렇게 되었는지 알 수 있는 기록이 없음
    • 이전 개발자들이 모두 떠난 뒤 고용되어 물어볼 사람도 없음
  • 저장소의 커밋 기록은 실질적인 맥락을 거의 제공하지 못함
    • 지난 13년간 커밋 본문은 총 295줄
    • dependabot, “revert commit”, “fix typo” 커밋 본문을 수동으로 제외하면 167줄
    • 대략 한 달에 한 줄 수준임
  • 커밋 제목도 대개 “fix page A”처럼 큰 변경을 설명하기에는 부족함
  • 별도 문서가 없고, 코드 주석도 거의 없음
  • 이런 상황은 “우리는 이상한 일을 많이 했지만 왜 그랬는지는 말하지 않겠다”는 식의 Chesterton’s middle finger에 가까움

개발자가 남겨야 할 최소한의 기록

  • 코드베이스에는 여러 종류의 미완성·잔여 코드가 남아 있음
    • 끝나지 않은 리팩터링
    • 제거된 기능의 잔재
    • 추가됐지만 연결되거나 사용되지 않는 기능
    • 아무도 사용하지 않는 듯한 기능
  • 전체적으로는 Chesterton’s gap 문제도 심해 보임
    • “아직 울타리가 없으니 만들자”는 식으로, 실제로 울타리가 필요한지 묻지 않는 상황에 가까움
  • 좋은 글쓰기는 어렵지만, 대충 통과 가능한 수준의 설명을 남기는 일은 어렵지 않음
  • 변경 기록은 기본적으로 세 가지 질문에 답해야 함
    • 무엇을 바꾸는가
    • 왜 바꾸는가
    • 왜 이 해결책이 좋은가
  • “Implement new feature X”만으로 충분한 경우도 있지만, 대부분은 기능이 왜 또는 어떻게 그런 방식으로 추가됐는지 적을 내용이 있음
  • 버그 수정, 리팩터링, 실질적인 변경에서는 보통 최소한 한두 문단 정도로 변경 내용과 이유를 남길 수 있음
  • 이런 기록은 선택 사항이 아니라 소프트웨어 개발자의 일에 포함됨
    • 유려할 필요 없음
    • 완벽한 영어일 필요 없음
    • 거창한 에세이일 필요 없음
    • 빠뜨리는 내용이 있어도, 아무것도 없는 것보다는 훨씬 나음
  • 아무것도 남기지 않으면 이후의 모든 사람에게 문제를 떠넘기게 됨

댓글과 토론

Lobste.rs 의견들
  • 나중에 무엇이 중요해질지는 당시엔 분명하지 않을 때가 있음. 커밋까지 이어진 모든 과정이 공개로 기록돼 있으면 큰 도움이 되지만, 관련자 모두가 이미 많은 맥락을 갖고 있어서 “너무 당연하다”고 여겨 빠지는 내용도 생김
    논의가 기록되지 않으면 의사결정 과정을 디지털 고고학처럼 파헤치는 일이 훨씬 어려워짐. 결국 왜 그 울타리가 있는지 모르는 상태를 다뤄야 할 때가 있고, 현재 맥락과 지금 사람들의 시스템 지식을 바탕으로 평가해야 함
    프로젝트에 오래 머물며 전체 시스템에 대한 이해와 직감을 얻은 사람이 있으면 큰 도움이 됨. 개발자를 교체 가능한 톱니바퀴처럼 보는 조직은 아무도 충분히 오래 남지 않아 같은 실수를 반복하며 바퀴를 다시 발명하게 됨

    • 코드 리뷰에서 얻는 가장 큰 이점은 다른 사람이 코드를 읽고, 이유가 명확하지 않은 곳마다 주석을 쓰게 만든다는 점임. 내게 명확하지 않은 부분은 이미 주석을 달기 때문에, 이제 두 사람 중 한 명이라도 설명이 필요하다고 느낀 곳에는 주석이 남음
      다음 사람이 코드를 볼 때 왜 이렇게 되어 있는지 이해할 가능성이 0은 아니게 됨
    • 옛날 음식 레시피에서 “모두가 어디서 찾는지 아는” 재료를 적어두지 않거나, 중세 음악의 리듬과 박자를 “다들 안다”고 전제했던 것과 비슷함. 지금은 그 안에 무엇이 있었는지 더는 모르게 됨
  • 커밋 메시지에 그냥 “fix”나 “WIP commit” 같은 것만 쓰는 개발자를 이해한 적이 없음. 아마 진지한 코드 고고학을 해본 적이 없거나, 그런 일을 할 수 있다는 생각 자체를 못 했던 것 같음
    항상 정보가 너무 많은 쪽으로 실수하려고 함. 그래야 미래의 나, 후임자, 혹은 장애가 터졌을 때 호출받는 불쌍한 사람이 결국 무언가 깨졌을 때 그 이유를 파악할 가능성이라도 생김

    • 그건 아마 작업 단위의 크기가 잘못된 경우일 수 있음. 어떤 개발자에게는 하나의 커밋이 하나의 “판매 가능한” 기능을 만들기 위한 수백 개 작은 단계 중 하나일 수 있음
      그런 경우 매 체크포인트 이후 무엇이 바뀌었는지 머릿속으로 추적하거나 의미 있는 설명을 붙이기 어렵고, 커밋을 잘 정의된 작업 단위로 나누는 수단이 아니라 비디오 게임의 “저장” 버튼처럼 다루는 것에 가까움
      반대로 큰 API 리팩터링 결과로 큰 커밋을 만든다면 설계 문서에 준하는 설명이 아니면 부족할 가능성이 크고, 그걸 하지 않을 거라면 긴 메시지의 의미도 애매해짐. 다만 어떤 사람들은 이것을 훈장처럼 여기며 릴리스 공지에 “버그 수정 및 기능 개선” 같은 문구만 넣기 시작하는데, 그건 명백히 잘못된 결론임
  • 변경의 배경을 읽고 이해해야 할 “다른 사람”이 미래의 자기 자신일 수 있다는 사실을 많은 개발자가 자주 잊음. 코드 블록을 보며 머리를 긁다가 git blame을 했더니 자기 이름이 staring back 하는 상황은 다들 익숙할 것임
    좋은 커밋 메시지가 이슈, 이메일, 채팅 로그를 뒤지며 를 찾는 시간을 몇 시간, 심하면 며칠까지 줄여준 적이 셀 수 없이 많음. 심지어 내가 당연히 바로 답할 수 있어야 하는 경우에도 그랬음
    미래의 자신에게 친절해야 함. 알고 있던 것, 생각하던 것, 논의된 모든 것을 git 로그에 쏟아 넣는 편이 좋음. 5년 뒤에도 무엇이 여전히 당연할지는 절대 명확하지 않음

    • 물론 그런 지식 중 일부는 커밋 로그가 아니라 주석이나 설계 문서에 들어가는 편이 맞을 수 있음
    • git blame에서 내 이름을 보고 당황하는 부분은, 적어도 이유가 있었던 일에 대해서는 잘 겪지 않음. 상대방의 이상한 동작처럼 무작위적인 것은 그쪽 프로세스를 볼 수 없으면 유용한 설명을 하기 어렵지만, 한때 내게 말이 됐던 것이라면 다시 읽으면 대체로 다시 이해됨
      내 학습 방식은 용암층처럼 쌓이는 방식에 가까운 듯함. 고등학교 이후로 크게 바뀌기보다는, 아는 것과 사용할 수 있는 방법을 계속 덧붙여 왔음
  • 최근에 누군가가 커밋 메시지가 한 문장을 넘으면 대체로 시간 낭비라고 주장했는데, 강하게 반박하고 싶었지만 그 반대를 입증하는 데는 생각보다 서툴렀음
    한 가지 질문은 어떤 정보가 커밋 메시지에 들어가야 하고, 어떤 정보가 인라인 주석, ADR, 또는 다른 긴 형식의 문서에 들어가야 하느냐는 것임
    여전히 좋은 커밋 메시지를 쓰려고 하지만, 직장에서는 이미 가망이 없고 개인 장난감 프로젝트에서도 일관되지는 못함

    • 내게 커밋 메시지는 리뷰어를 위한 것임. 이 변경이 왜 필요한지, 무엇을 고치거나 왜 새 기능을 원하는지 알려주고, 변경을 이해할 틀을 제공함
      하지만 최종 코드는 커밋 메시지를 읽지 않아도 이해 가능해야 함. 새 코드 안에서 근거가 필요한 부분이 있다면 그건 주석에 들어가야 함
      다르게 말하면 커밋 메시지는 왜 이 변경을 지금 했는지 설명하고, 주석은 변경이 끝난 시점의 코드가 왜 그런 상태인지 설명함
      더 큰 변경, 특히 새 기능에는 어딘가에 설계 문서가 있어야 함. 리뷰가 필요하면 저장소 안의 실제 문서일 수 있고, 이슈 추적기에 있을 수도 있음
      커밋 메시지는 그 문서를 가리켜야 하며, 설계가 코드로 내려오면서 생긴 세부사항도 설명해야 할 수 있음. 가능하면 설계 문서의 짧은 초록도 포함하는 게 좋음. 리뷰어 후보가 여러 명일 때 누가 가장 관심을 가져야 하는지 신호를 주고, 설계 단계에서 피드백했어야 할 사람이 빠진 경우를 잡는 데 특히 중요함
    • 커밋 메시지에 무엇을 넣을지 정할 때의 내 기준은, 보통 커밋 메시지를 git logjj log로 보는 게 아니라 거의 항상 라인 주석 표시를 통해 본다는 점임
      제목 줄은 더 파고들 필요가 있는지 판단하는 데 보편적으로 매우 유용함. 본문에 변경이 왜 이루어졌는지 정보가 있으면, 그 이유가 직관적이지 않을 때 도움이 됨
      예를 들어 꽤 많은 코드가 들어갔더라도 “admin: add impersonation”만으로도 충분한 경우가 많음. 하지만 “auth: shorten JWT timeouts”라면 왜 제한 시간을 줄여야 했는지 한두 문장 정도는 보고 싶음
      정말 긴 형식의 커밋 메시지는 실제로는 꽤 쓸모없다고 보는 편임. 글에서 지적한 이유와도 대체로 같음. 그런 형식은 커밋 메시지가 곧 PR 설명인 작업 흐름, 예컨대 이메일 기반 흐름이나 Gerrit 등에서 나온 것 같음. 그런 경우 해롭지는 않지만, 반드시 가치를 더한다고 보기도 어려움
    • 예전에 바로 이 질문에 답하려고 을 쓴 적이 있음. 여러 문서 위치를 계층으로 놓고, 정보를 어디에 넣을지 나누는 경험칙을 정리했음
      나도 비슷한 상황임. 직장의 더 넓은 그룹에서 자세한 커밋 메시지를 쓰는 사람은 나와 다른 한 명뿐임
  • 울타리가 왜 만들어졌는지 알아도, 왜 지금 거기 있는지는 모를 수 있음. Chesterton의 울타리를 만든 당사자여도 그것을 허물 수 있는지는 알 수 없음
    시스템이 만들어질 당시 의도된 상호 의존성 트리는 어느 시점의 실제 상호 의존성 트리의 부분집합일 뿐임. 그러니 완벽한 개발자가 울타리를 왜 만들었는지 전부 알려줘도 효용은 제한적임
    그들이 아는 것은 왜 만들었는지이지, “이 울타리를 제거하면 무엇이 깨지는가?”에 대한 답은 아님. https://xkcd.com/1172/ 를 보면 됨. 우스운 사용 사례 중에는 중요하지 않다고 볼 것도 있지만, 원 개발자 자신도 알 수 없던 정당한 사용은 항상 존재함
    원 개발자가 뭘 생각했거나 뭘 피웠는지 아는 건 좋지만, 그 정보는 불완전함과 무관함 사이 어딘가에 있음
    지어낸 예로, Chesterton의 울타리가 원래는 양쪽에 농장이 있던 시절에 물웅덩이에서 아이를 멀리 떨어뜨리려고 세워졌다고 해보자. 지금은 고속도로가 생겼고, 그 울타리가 우연히 사슴과 차의 충돌로 인한 대규모 동물 및 사람 사망을 막는 유일한 장치가 됐을 수 있음
    아무도 이 사실을 모름. 고속도로와 울타리 없음 조합은 테스트되지 않았을 뿐 아니라 존재한 적도 없고, 고속도로 건설자와 자연자원부는 왜 그 도로에 로드킬이 적은지 모름. 몇 년 뒤 농장이 전부 주택으로 바뀌고 주요 동물 이동 통로가 아니게 되면 울타리는 쓸모없어질 수도 있고, 아닐 수도 있음
    이것이 억지스럽거나 자신에게 적용되지 않는다고 느껴진다면 부럽다. 내 경험상 어느 정도 규모가 있고 아주 오래되지 않은 회사를 제외하면 대체로 일이 이렇게 굴러감
    진실은, 우리가 하는 모든 일이 생태계의 일부이며 명시적으로 상호작용하기로 합의한 적 없는 것들에 의존하고 또 의존당한다는 것임. API 표면적을 줄이고 구현 세부사항 전부가 이웃의 일이 되지 않게 할 수는 있지만, 의도치 않은 결합은 엔트로피 증가만큼 피할 수 없는 우주의 법칙에 가까움
    어떤 사람에게는 이 말이 허무주의적이고 패배주의적으로 들림. 엔트로피와 싸워야 하지 않느냐고 할 수 있음. 하지만 가장 좋은 시간 활용과 투자 대비 효과는, 이것이 근본적으로 싸우는 대상이 아니라 관리하는 대상임을 인정하는 데서 나온다고 봄
    언제나 세상의 상태를 알 수 있다고 가장하면 실패와 자기비난을 예약하는 셈임. 100% 가동 시간이 존재하지 않고 대부분의 대상에 잘못된 목표인 것과 같음
    어느 정도 하이젠베르크식 불확실성을 가진 과정을 관리한다고 인정하면, 하루에 주어진 제한된 시간을 효과적으로 써서 더 나은 결과를 만들 방법을 고를 수 있음. 특히 사전 대응과 사후 대응의 지능적인 절충이 가능해지고, 사후 대응을 0으로 만들 수 없으며 때로는 하루짜리 사후 대응을 피하려고 1년짜리 사전 대응을 하는 게 말이 안 된다는 것도 이해하게 됨
    그래서 커밋에 문서를 얼마나 써야 할까? 설계 문서나 테스트 계획은 몇 개가 있어야 할까? 나도 모름. 한 가지 선택지를 던지자면, 모든 문서는 독자를 위해 쓰임
    코드베이스를 바꾼다면 현재 팀원들, 새로 들어온 사람까지 포함해, 조사를 통해 변경이 무엇을 했고 왜 했는지 이해할 수 있어야 하며, 위험한 발판이나 하중을 지탱하는 버그에 대한 몇 가지 경고도 있어야 함
    이것은 장황한 산문이 아니라, 무대를 설정해 주는 추가 맥락으로 향하는 포인터 형태가 대체로 맞음. 예를 들면 “이 단계에 인증을 요구한 것은 모든 변경에 다자 승인 필요 정책의 일부임. see: go/multiparty” 같은 식임

    • 엔트로피가 증가하고 의도치 않은 결합이 계속 생기는 것은 사실 결함이 아니라, 시스템이 영구 상태에 고착되는 것을 막아주는 유용한 특성이라고까지 볼 수 있음
      인간을 상대하면서 완벽을 추구하는 시스템은 정말 불편함. DRM, trusted computing, remote attestation, Faro Plague, smart contracts 같은 것들임
      서비스 모드로 재부팅해서 수정할 수 있는 시스템이 훨씬 좋음. 앞으로 소프트웨어가 사람들에게 실제로 도움이 되려면 어느 방향으로 진화해야 할지 예측할 수 없기 때문임. 100% 단단하게 잠그기보다 수정하기 쉽게 만드는 편이 낫다
  • 우리는 커밋 본문을 거의 쓰지 않지만 제목은 꽤 잘 쓰는 편임. 그게 측정 기준이라면 무엇을 측정하는 기준인지 잘 모르겠음
    큰 코드베이스에서는 명확한 코드와 충분한 테스트 커버리지가 문서보다 훨씬 유용한 경우가 많음

    • 동의하지 않음. 실제 변경과 변경 이유가 모두 미묘할 때가 있음. 이상한 코드가 왜 그렇게 되어 있는지 파악할 때, 그 커밋에서 바뀐 테스트를 보는 건 추가 단계이고 반드시 왜 바뀌었는지를 말해주지도 않음
      완전히 타당한 변경이 이루어지고 테스트도 그에 맞춰 업데이트됐지만, 나중에 보면 왜 그 변경이 필요했는지 전혀 불명확한 경우가 있음. 특히 바뀐 줄이 운영 환경에서 예상치 못한 동작이나 추가 동작을 일으키면 더 그렇다
      단순 되돌리기가 바람직하지 않을 수 있고, 이때 변경이 왜 이루어졌는지 전체 이력이 있으면 정말 도움이 됨
      아이디어는 맞았지만 예상치 못한 결과가 생긴 경우를 많이 봤음. 의도를 알면 새 문제도 고치고 원래 변경 이유도 유지하는 실제로 올바른 변경을 도출할 수 있음
      커밋 한 줄만 고집한다면 적어도 티켓 번호를 넣어서 거기서 이력을 읽을 수 있게 해야 함
  • 이런 코드베이스를 복구하러 5년 동안 이국적인 지역들을 돌아다니며 꽤 괜찮은 돈을 벌었음. @arp242, 항상 요금을 올리고 https://archive.org/details/working-effectively-with-legacy-code 를 베개 밑에 두고 자야 함

  • 다행히 AI 잡동사니 생성기들은 거대한 커밋 메시지를 써줌. 실제 변경과 어느 정도 관련이 있는 경우도 많으니, 적어도 그 부분은 해결된 셈임

    • 정말 그럴까? 무엇이 바뀌었는지 요약하는 데는 괜찮을 수 있지만, 왜 바뀌었는지는 잘 못 쓸 것 같음