3P by GN⁺ | ★ favorite | 댓글 1개
  • 주석은 식별자보다 표현력이 큰 인간 언어를 쓸 수 있어, 코드에 없는 선택지와 포기한 대안을 남기기에 적합함
  • “comment the why, not the what”은 정보를 식별자에 최대한 담으려는 접근이지만, 최근에는 이유까지 긴 함수명이나 테스트 이름으로 옮기려는 흐름이 있음
  • Logic for Programmers의 epub 빌드에서는 16개 수학 기호를 문자열마다 순차 치환하는 느린 구현을 썼지만, 현재 수학 문자열이 25개뿐이라 충분히 빠름
  • 이런 주석은 나중에 수학 문자열이 수백 개로 늘어 빌드 병목이 생길 때 고칠 위치를 알려주고, 느린 코드가 의식적 트레이드오프였음을 남김
  • 함수명이나 테스트는 코드가 실제로 하는 일에 붙는 방식이라, 선택하지 않은 대안과 하지 않는 일을 담는 부정 정보를 자기문서화하기 어려움

주석이 코드보다 담기 쉬운 정보

  • 코드는 구조화된 기계 언어이고, 주석은 표현적인 인간 언어로 작성됨
  • “comment the why, not the what”은 가능한 많은 정보를 식별자에 넣으라는 의미로 해석될 수 있음
  • 식별자는 코드 안에 포함된 제한적인 인간 언어에 가깝고, 모든 “what”을 담을 수는 없지만 많은 경우에는 가능함
  • 최근에는 “why”도 주석이 아니라 긴 함수명이나 테스트 케이스 이름에 넣을 수 있다는 견해가 늘어남
  • 자기문서화 코드베이스는 대체로 식별자 추가를 통해 문서화를 늘림
    • 예외적으로, 주석을 로깅으로 바꿔 코드를 더 자기문서화한다는 사례가 있음

“Why Not” 주석이 다루는 것

  • 코드로 표현하기 어려운 정보는 부정 정보
  • 부정 정보는 시스템에 무엇이 없는지, 왜 특정 대안이 선택되지 않았는지에 주의를 끌어줌
  • “why not”은 코드가 하는 일을 설명하기보다, 코드가 하지 않는 선택과 그 이유를 남김

epub 수학 기호 치환 사례

  • Logic for Programmers의 epub 빌드에서 기술적 이유로 \forall 같은 수학 표기가 같은 기호로 변환되지 않았음
  • 이를 해결하기 위해 수학 문자열 안의 토큰을 유니코드 대응 기호로 직접 바꾸는 스크립트를 작성함
  • 가장 쉬운 구현은 필요한 16개 수학 기호 각각에 대해 string = string.replace(old, new)를 호출하는 방식임
  • 이 방식은 각 문자열을 16번 순회하므로 비효율적이지만, 한 번의 순회로 16개 치환을 모두 처리하는 방식은 더 복잡함
  • 남긴 주석의 요지는 다음과 같음
    • 각 문자열을 16번 순회함
    • 책에는 현재 수학 문자열이 25개뿐이고 대부분 5자 미만임
    • 그래서 여전히 충분히 빠름
  • 이 주석은 “왜 느린 코드를 쓰는가”뿐 아니라 “왜 빠른 코드를 쓰지 않는가”까지 설명함

나중의 독자를 위한 표지판

  • 느린 코드가 당장 문제를 일으키지 않아도, 나중에 문제가 될 수 있음
  • Logic for Programmers의 미래 버전에 수학 문자열이 수백 개로 늘어나면 해당 빌드 단계가 전체 빌드의 병목이 될 수 있음
  • 지금 표지판을 남기면 나중에 어떤 부분을 고쳐야 하는지 바로 알 수 있음
  • 코드가 계속 문제없이 동작하더라도, 주석은 작성자가 트레이드오프를 알고 있었다는 사실을 보존함
  • 2년 뒤 epub_math_fixer.py를 다시 열었을 때 느린 코드가 미숙함, 시간 부족, 실수 때문인지 다시 조사할 필요가 줄어듦
  • 부정 주석은 느린 구현을 알고 있었고, 대안을 검토했으며, 최적화하지 않기로 결정했다는 정보를 남김

함수명과 테스트로 대체하기 어려운 이유

  • RunFewerTimesSlowerAndSimplerAlgorithmAfterConsideringTradeOffs 같은 함수명은 너무 길고, 실제 트레이드오프를 충분히 설명하지 못함
  • 나중에 코드를 최적화하면 해당 이름을 여러 곳에서 바꿔야 할 수 있음
  • 더 큰 문제는 이런 이름이 함수가 실제로 무엇을 하는지 알려주지 못해 자기문서화를 오히려 약화시킨다는 점임
  • 함수명과 변수명 같은 식별자는 한 절의 정보만 담을 수 있음
  • 하나의 식별자에 “함수가 하는 일”과 “함수가 감수하는 트레이드오프”를 동시에 담기 어려움

테스트로도 남기기 힘든 부정 정보

  • 책에서 수학 블록을 grep해 개수가 80개를 넘으면 실패하는 테스트를 만들 수는 있음
  • 하지만 그런 테스트는 EpubMathFixer 자체를 직접 테스트하지 않음
  • 함수 내부에는 해당 테스트가 걸 수 있는 지점이 없음
  • 자기문서화는 작성된 코드에 따라 붙어 코드가 하는 일을 설명함
  • 부정 정보는 코드가 하지 않는 일을 다루기 때문에, 자기문서화 방식과 근본적으로 맞지 않음

더 넓은 질문

  • “why not” 주석은 반사실(counterfactual)의 한 사례로 볼 수 있음
  • 인간 커뮤니케이션의 추상적 요소가 일반적으로 자기문서화될 수 있는지에 대한 질문이 남음
  • 비유, 불확실성, 윤리적 주장 같은 정보도 자기문서화가 가능한지는 여전히 의문으로 남아 있음

댓글과 토론

Hacker News 의견들
  • 몇 년 전 Twitter에서 본 듯한 농담이 기억남: “주니어 엔지니어는 코드가 무엇을 하는지 설명하는 주석을 쓰고, 중급 엔지니어는 코드가 왜 그렇게 하는지 설명하는 주석을 쓰며, 시니어 엔지니어는 왜 코드가 다른 방식으로 작성되지 않았는지 설명하는 주석을 쓴다”는 식이었음

    • 정말 맞는 말임. 코드 자체보다 5배 긴 주석을 붙여야 했던 적도 있는데, 그건 코드나 도메인, 큰 동적 범위를 다루는 주의점을 모르는 사람이 선의로 리팩터링하는 걸 막기 위해서였음
      반대로 함수명과 변수명만으로 의미가 명확해질 수 있으면, 꽤 큰 함수도 주석 없이 작성한 적이 있음
    • 세 가지를 다 함. 요약 주석은 엄청 유용하고 실증 연구로도 검증됐는데, 많은 사람이 Clean Code식 사고에 너무 깊이 빠져서 구제하기 어려워 보임
    • 주니어 프로그래머는 보통 아무것도 문서화하지 않거나 전부 문서화함. 경험이 쌓이면 특이한 것만 문서화하면 된다는 걸 알게 되고, 더 경험이 쌓이면 특이한 것이 점점 줄어들어 주석도 줄어듦
      그래서 주석이 적은 쪽이 이기긴 하지만, 주석 없는 코드베이스를 보면 그게 아름답게 다듬어진 걸작인지 초보들이 만든 불안정한 코드인지 직접 살펴봐야 구분 가능함
    • 명백히 멍청해 보이는 동작을 왜 계속 재현해야 하는지 설명하는 주석도 있음. 다른 무언가가 그 멍청한 동작에 의존하고 있기 때문임
    • C레벨 엔지니어라면 “이 코드를 리팩터링하느라 X시간이 낭비됐습니다. 선배들의 전철을 밟기로 했다면 이 카운터를 올리세요” 같은 주석을 남김
      완전한 땜질처럼 보여도, 가끔은 그게 실제로 얻을 수 있는 최선이기도 함
  • 1년 뒤 다시 코드를 볼 때 나에게 유용할 것 같으면 전부 주석으로 남김. 보통은 왜 안 되는지이고, 코드가 복잡할 때는 흐름을 더 잘 보려고 짧게 “무엇”도 적음
    유용하지 않은 건 의무 주석임. 공개 API는 충분히 문서화해야 하지만, 어떤 조직은 private 함수까지 모든 함수에 주석을 강제하고, 목적이 너무 뻔해서 함수명을 다시 말하는 수준이 되기도 함. 이는 시간 낭비일 뿐 아니라 주석에 둔감해지게 만들어 무시하는 습관을 가르침
    도구가 추가하는 낭비성 주석도 싫음. 모든 루프에 //for//try를 붙이는 식은 특히 별로임

    • 이상하게 많은 문법 강조 색상표가 주석을 흐리게 만들어 대비를 낮춤. 아마 의무 주석이나 생성 주석에 정보량이 적기 때문일 것임
      의무 주석과 생성 주석을 없애고, 어두운 테마에서는 주석을 밝은 네온색으로 바꿔 눈에 띄게 만드는 편이 좋음. 주석이 달렸다면 그건 중요하다는 뜻이어야 함
    • 예전에는 “모든 주석은 코드 냄새”라는 입장이었고 지금도 상당 부분 그렇지만, 수백 명이 활발히 개발·유지보수하는 매우 큰 코드베이스에서 일하며 입장이 조금 누그러짐
      촉박한 일정의 비즈니스 환경에서 오래된 대형 시스템을 유지하려면 이상한 일을 해야 할 때가 있고, 그럴 땐 왜 그런지 설명해야 함
      주석에 반대하는 큰 이유는 주석도 코드의 일부가 되어 유지보수가 필요하기 때문임. 하지만 대부분은 막혔을 때만 주석을 읽고, 편집기들도 주석을 회색으로 흐리게 만들어 심리적으로 보이지 않게 함. 그래서 주석은 쉽게 낡음
      “미래의 나”를 위한 맥락이 많은 개발자가 만지는 공유 코드베이스에서도 유용한지, 아니면 소수만 코드를 만질 때 잘 통하는 방식인지 궁금함
    • 완전히 동의함. 주석이 너무 많으면 클래스나 함수 안에 무엇이 있는지 보기 어려워짐. 원래 한 화면에 들어갈 클래스나 함수가 주석 때문에 한 화면을 넘기면 가독성 비용이 생김
    • 어제 개인 프로젝트에서 “이게 정말 유용한가?”라는 주석이 달린 줄을 봤고, 쉽게 제거할 수 있어 보였음. 새롭고 깔끔한 클래스를 써보려 했지만, 특정한 예전 방식이 실제로 필요했음
      그래서 기존 주석에 “=> yes!”를 덧붙였고, 과거의 내가 그 의문을 문서화해둔 게 고마웠음. 업무에서는 특히 버그 수정 때, 명확하지 않은 변경 위에 티켓 번호와 함께 한두 줄 주석을 자주 남김
  • 코드에 남겨본 주석 중 가장 좋아하는 형식은 이 템플릿임: “DEAR MAINTAINER: 이 코드는 [이유] 때문에 이렇게 되어 있습니다. 이걸 ‘고치려’다 끔찍한 실수였다는 걸 깨달으면, 다음 사람을 위한 경고로 total_hours_wasted_here = n 카운터를 올려주세요”
    원작자는 아니지만 한두 번 감사히 써봤고, 카운터만 올리는 한 줄짜리 커밋을 보면 재미있었음

    • 몇 년 전에 이게 있었으면 좋았겠음. 요구사항을 만족하려면 재귀를 꽤 자유롭게 써야 하는 SQL 생성 코드를 만든 적이 있고, 상호 재귀가 많아 코드가 지저분하긴 했지만 필요한 악이었음
      더 시니어인 엔지니어가 코드베이스를 넘겨받아 전부 반복문 방식으로 “고쳤고”, 이메일로 재귀가 왜 나쁜지 훈계하려 했지만, 그의 코드는 실제 요구사항을 다 만족하지 못했고 결국 내가 재귀로 했던 것들을 사실상 다시 만들었음
      나중에 그가 일부 발언에 대해 사과하긴 했지만, 맨 위에 이런 주석을 달아뒀다면 전체 일을 피할 수도 있었겠음
  • 제목이 모호하다는 데 동의하고, 그래서 글을 읽게 됐음. 개인적으로는 전체적으로 주석을 적게 쓰는 쪽을 선호하지만, 글에 나온 설명 주석은 분명 가치 있음. 왜 그렇게 했는지, 왜 다른 방식은 아닌지 설명하라는 좋은 상기임
    특히 5년, 10년, 15년 뒤에도 유지보수해야 하는 자기 코드에 적용됨. 최근 동료의 새 코드를 리뷰하다 “왜 이렇게 했지?”라고 생각했는데, 10줄 위에 8년 전 내가 똑같이 해둔 이유가 있었음. 그 동료는 유지보수의 핵심 규칙, 즉 기존 코드처럼 보이게 만들기를 따른 것임

    • 오래된 코드베이스를 유지보수할 때 기존 코드처럼 보이게 만들기는 너무 과소평가됨. 뒤에 올 사람들의 정신 건강을 위해 제발 기존 코드처럼 보이게 만들어야 함
  • 더 넓게 따르는 원칙의 특수한 경우일 뿐임: 코드를 읽을 때 놀랄 만한 것에 주석을 달라는 것
    코드를 쓸 때 머릿속에서는 계속 “나중에 이 코드를 이해할 수 있을까?”라고 묻고, 매번 본능적으로 “그렇다”고 답하는 사람은 오만하고 자주 틀림. 답이 “확실치 않다”면 다음 질문은 자연히 “왜?”이고, 그 답이 곧 주석에 써야 할 내용임
    때로 답은 “읽는 사람이 왜 다른 방식으로 작성하지 않았는지 궁금해할 수 있어서”이고, 이 글이 다루는 특수한 경우임. 하지만 때로는 “어떻게 동작하는지나 왜 올바른지 명확하지 않아서”이며, 그때는 다른 종류의 주석이 필요함

    • 처음에 한 방식으로 코드를 써봤다가 동작하지 않아 두 번째 접근이 필요했다면, 그 자리에 주석이 필요하다는 강한 신호임. 작성 중에 놀랐다면 1년 뒤 그 놀라움을 잊고 코드를 읽을 때도 다시 놀랄 가능성이 큼
    • 비슷한 원칙으로 “이게 예상대로 동작하지 않으면 어디를 봐야 하지?”를 기준으로 삼음. 답이 문서에 없거나, wiki → 패키지/모듈 → 파일 → 클래스 → 함수/메서드 경로에서 10줄 넘게 떨어져 있으면 인라인 주석을 달거나 문서를 갱신함
      주로 문자열을 잘라내거나 특이한 자료구조를 중간 단계로 탐색할 때 이런 일이 생김
  • 식별자만으로도 아주 멀리 갈 수 있지만, 끝까지 갈 수는 없음. 개인적으로는 공개 메서드나 변수, 필드, 매개변수에는 jsdoc/xmldoc 같은 문서 주석을 요구하는 편을 좋아함
    메서드 이름을 잘 짓는 것도 중요하지만, 무엇을 하는지 짧게 적어보면 더 명확해지고 특히 명백한 결함이 드러남. 첫 문장을 쓰는 순간 더 나은 이름이 떠오르는 경우가 많고, 설명에 “그리고”가 들어가기 시작하면 메서드가 너무 많은 일을 하고 있어 더 논리적으로 쪼갤 수 있다는 신호임
    속성은 너무 명확해서 문서가 필요 없다고 생각하기 쉽지만, /** The API key */ string ApiKey; 같은 건 빠진 게 너무 많음. 이 키가 어디서 오는지, 내부 전용인지 외부 시스템과 주고받는지, 필수인지, null이나 빈 값이 가능한지, 최대 길이가 있는지, 잘못된 값이면 무슨 일이 생기는지, 더 읽을 코드나 문서가 있는지 알 수 없음
    원 작성자는 1~2분이면 쓸 수 있는 정보지만, 새로 온 사람은 수정하거나 사용하거나 몇 년 뒤 버그를 고치러 투입됐을 때 알아내는 데 몇 시간이 걸릴 수 있음

  • 코드 리뷰에서 지나치게 꼬치꼬치 따질 리뷰어가 뭐라고 할지 예상되면 “Y 때문에 X를 하지 않았다” 같은 주석을 자주 씀. 귀찮은 왕복 논의를 줄이려는 목적임

    • 경험상 왕복 논의량은 그대로인데, “주석에 쓰여 있듯이”라고 몇 번 쓸 수는 있음
    • 그런 내용은 PR에 선제적으로 추가하지만, 코드 안에 남길 가치가 큰지는 잘 모르겠음
  • “각 문자열을 16번 순회하지만, 책에 수식 문자열이 지금까지 25개뿐이고 대부분 5자 미만이라 충분히 빠르다”는 식의 주석에는 다른 변형도 있음
    원래 설계 제약보다 입력이 훨씬 커질 때 발동하는 디버그 로그를 넣는 것임. 미래 개발자에게 거의 같은 메시지를 주지만, 더 빨리 발견해서 진단과 디버깅 시간을 더 줄일 수 있음

    • 많은 경우에 좋은 아이디어임. 당장은 동작하지만 매우 느린 루프를 쓸 때가 있는데, 타이머를 두고 “X초 이상 걸리면 경고 로그를 남긴다”를 할 수 있겠음
      이상적인 로깅과 관측성 시스템이라면 앱의 모든 구성요소 시간을 재고 디버그 정보를 자주 남기겠지만, 누가 그런 완벽한 시스템을 실제로 쓰겠나 싶음. 나중에 성능이 나빠질 수 있거나 최적화할 시간이 없었던 부분에 구체적으로 로그를 넣는 노력이 더 중요함
      돌이켜보면 obvious한 아이디어지만, 그동안은 나중에 다시 봐야 할 곳에 주석을 남기고, 일이 나빠졌을 때 그 주석을 기억하길 바라는 식이었음
    • bazel 같은 선의의 도구는 오류가 아닌 출력을 숨겨야 할 잡음으로 취급하는 경우가 많음. 이런 메시지를 가장 가까운 쓰레기통으로 보내버리곤 해서, 일부 영역에서는 로그가 제약을 전달하는 수단으로 매우 불안정해짐
  • 누가 뭐라 하든 코드 곳곳에 주석과 문서 주석을 많이 씀. 다만 거꾸로 접근해서, 애플리케이션의 단계 목록을 먼저 주석으로 대략 작성한 뒤, 개발하면서 큰 단계를 작은 단계로 쪼개고, 원래 주석을 지우기도 하고 남기기도 하며 거의 완성된 알고리즘이 될 때까지 주석을 세분화함
    보통 바깥에서 안쪽으로 코딩하므로 주석을 쪼개는 동안 코드도 같이 씀. 가끔 몰아서 코딩한 뒤 나중에 대부분이 귀찮아할 정도로 주석을 달기도 함. 모든 함수와 변수에 설명을 붙이고, deg_to_rad 함수에도 """Converts degrees to radians."""를 붙임. 저장공간은 싸니까
    대부분이 좋아하지 않는다는 건 알지만 괜찮음. 보기 싫으면 스크립트로 제거하거나 코드 리뷰에서 제거하면 됨. 그래도 주석 없는 남의 코드보다 내 오래된 코드를 읽는 게 훨씬 즐거움. Python에서는 Flask API 같은 단순한 상용구 코드가 대체로 자기 문서화되지만, 오히려 그런 상용구가 변경이 많아 중요한 주석이 붙는 경우가 있음. 업계에서는 알고리즘 쪽은 통째로 다시 쓰는 일이 더 많음
    앞으로도 주석과 문서 주석을 계속 좋아할 것임

    • 비슷하게 하긴 하지만 주로 최상위 구성요소에만 함. 그런 곳에서 주석이 가장 유용하기 때문임
      머릿속에서 전체 개념화를 하는 걸 좋아하고, 초기에는 여러 설계 선택지를 실제로 써보며 실험하는 게 느리다고 느낌. 그래서 설계 선택이 정해지면 반드시 문서화해야 함. 다른 사람은 아직 내 머릿속에 접근할 수 없기 때문임
      세부사항은 다른 사람도 개념화를 마치면 더 명확해질 거라고 기대함. 다만 어떤 이유로 그 개념화가 안 되면 읽기 더 어려워질 수 있어서, 최소한의 기본 가독성을 유지하도록 추가로 다듬음
    • 주석은 잘 유지보수될 때 훌륭함. 하지만 읽는 사람 대비 주석을 관리하는 사람이 매우 많은 오픈소스 같은 경우가 아니면, 결국 모두 잊거나 귀찮아서 유지하지 않게 됨
      코드를 고치는 것만큼 주석 갱신도 일이 되는 경우가 많음. 그래서 현실적으로 주석은 보통 거짓말이 되기를 기다리는 것임. 결국 주석과 코드가 어긋나고, 이는 주석 없는 코드보다 더 나쁠 수도 있음
      차라리 의도를 보여주는 자동화 테스트가 낫다. 테스트는 대체로 거짓말을 할 수 없고, 그렇지 않았다면 병합하지 않았을 것이기 때문임. 코드가 어떻게 쓰이도록 의도됐는지 보여주는 괜찮은 테스트 묶음이 있다면, 그것이 설명적이면서도 거의 진실임이 보장되어 보고 싶음
    • “마음에 안 들면 자기 버전에서 스크립트로 지우거나 코드 리뷰에서 지우면 된다”는 지점까지는 좋았는데, 여기서부터는 팀원으로서 큰 위험 신호로 보임
    • 나도 비슷함. 절반 정도는 먼저 하려는 일을 설명하는 주석부터 쓰고, 이후 예상대로 되지 않은 점과 실제로 동작하게 만들기 위해 해야 했던 일을 덧붙임
      5주 뒤 다시 봐야 할 때 엄청 도움이 됨
    • 모든 경우에 문서 주석이 필요하다고 보지는 않고, 모든 함수의 모든 매개변수와 반환값을 문서화해야 한다고도 생각하지 않음. 하지만 복잡한 API에서는 확실히 유용함
  • “주석은 미래의 나에게 보내는 사과”라는 개념을 따름
    코드가 이상하거나 느리거나, 누군가에게 설명할 때 “좀 엉성하긴 해”라고 말하게 될 부분이면 보통 주석을 남김. 특히 전에 바꿔본 적이 있다면, 동작하지 않았던 경우나 고친 내용 등을 문서화함
    이런 기준으로 접근하면 불필요한 주석은 자연히 사라지고, 보통 정말 필요할 때만 를 문서화하게 됨. 자기 코드베이스에서 한 달 정도 시험해보면 느낌을 알 수 있음