RicoCheese기술 뉴스와 기록
← 목록으로
뉴스2026-09-2116분

클린 코드와 명확한 코드는 다르다 — 주석은 애초에 문제가 아니었다

코드가 깔끔하면 주석은 필요 없다는 통념을 조목조목 반박한 Dev.to 기고문. 상수 47에 담긴 사연처럼 코드가 담지 못하는 '왜'를 기록하는 주석의 네 가지 역할과, 반대로 절대 쓰지 말아야 할 주석 유형을 정리했다.

#programming#webdev#discuss#productivity
Clean Code Is Not the Same as Clear Code: Comments Were Never the Problem

개요 #

"코드가 설명을 필요로 한다면 그 코드가 부족한 것이다." 언젠가부터 개발 업계에 자리 잡은 이 명제를 Giorgi Kobaidze가 정면으로 반박했다. 요지는 간결하다. 클린 코드는 "이 코드가 무엇을 하는가"에 답하지만, 실제 코드베이스는 "왜 이렇게 되어 있는가"를 계속 묻는다는 것이다.

저자는 산길 표지판을 예로 든다. 아무리 잘 설계된 산길에도 "급커브 주의" 표지판은 붙어 있다. 설계자가 실패해서가 아니라, 커브에 들어서기 전까지는 커브가 보이지 않기 때문이다.

급커브 표지판

스스로를 설명할 수 없는 코드 #

그는 현실의 코드베이스에 늘 세 종류가 섞여 있다고 본다. 애초에 구조를 고려하지 않고 작성돼 아무도 이해하지 못하는 코드, 아름답게 짜였지만 문제 자체가 복잡해 함수 하나를 다섯 시간 들여다봐야 하는 코드, 그리고 단순하고 읽기 좋지만 그렇게 만든 이유가 코드 어디에도 남아 있지 않은 코드다.

세 번째 사례를 보여주는 C# 예시가 등장한다.

csharp
public static partial class FlightNumbers
{
    [GeneratedRegex(@"^([A-Z]{2}|[A-Z]\d|\d[A-Z])(\d{1,4})([A-Z]?)$")]
    private static partial Regex FlightNumber();

    public static bool IsValid(string input) =>
        FlightNumber().IsMatch(input.Replace(" ", "").ToUpperInvariant());
}

이름도 괜찮고, 소스 제너레이터 기반 최신 정규식 문법을 썼다. 리팩터링할 것도, 이름을 바꿀 것도 없다. 그런데 "BA123", "U21234", "9W5A", "99123" 중 무엇이 유효한 값인지 이 코드만 보고 판단할 수 있는 사람은 드물다.

물론 찾아보면 된다. 문서를 읽어도 되고, 무엇이 유효하고 무엇이 아닌지 설명하는 단위 테스트도 있을 것이다. 적어도 이론상으로는. 저자는 이론과 현실의 간극을 광고 사진 속 버거와 포장지 속 버거에 비유한다.

광고와 현실의 버거

문서도 테스트도 없는 경우가 있다. 설령 둘 다 있더라도, 코드 바로 위에 주석 한 줄만 있으면 끝날 일을 왜 탭 세 개를 열어 가며 추적해야 하느냐는 반문이다.

csharp
// IATA flight number, e.g. "BA123", "U21234", "9W5A".
// Airline code is 2 chars: two letters, or a letter-digit mix (U2 = easyJet).
// Then a 1-4 digit flight number and an optional operational suffix letter.
// Uppercase only, no spaces: normalize input before matching.
[GeneratedRegex(@"^([A-Z]{2}|[A-Z]\d|\d[A-Z])(\d{1,4})([A-Z]?)$")]
private static partial Regex FlightNumber();

덤으로 U2가 이지젯 항공사 코드라는, 코드가 결코 알려줄 수 없는 사실까지 얻는다.

상수 47이 남긴 교훈 #

정규식 사례가 번역 문제였다면, 두 번째 유형은 아예 코드로 옮길 방법이 없는 영역을 건드린다. 저자가 더 가치 있게 보는 쪽도 이쪽이다.

csharp
private const int MaxConcurrentRequests = 47;

교과서적이다. 루프 안에 박힌 매직 넘버가 아니라 이름 붙은 상수다. 하지만 이걸 본 개발자는 모두 같은 생각을 한다. 왜 47인가. 반올림한 숫자도 아니고 2의 거듭제곱도 아니다. 누군가의 행운의 숫자이거나 오타처럼 보인다. 게다가 공급자 문서에는 한도가 50이라고 적혀 있다.

그래서 누군가 50으로 바꾼다. 몇 주 뒤 실제 트래픽에서 공급자가 요청을 무작위로 거부하기 시작하고, 아무도 재현하지 못한다.

빠져 있던 건 이것이었다.

csharp
// 47, not 50. The provider documents 50 requests per second, but their
// limiter measures bursts over a 1.2s window, so retries at 50 trip it.
// Recheck if they publish new limits.
private const int MaxConcurrentRequests = 47;

코드는 결정을 기록하고, 주석은 그 결정에 이른 근거를 기록한다. 근거가 사라지면 결정은 버그처럼 보인다.

코드가 띄워진 작업 공간 Photo by Daniil Komov on Pexels

좋은 주석이 하는 네 가지 일 #

실무에서 쓸모 있는 주석은 네 갈래로 나뉜다는 게 저자의 정리다.

  1. 번역 — 정규식, 비트 연산, 수식처럼 코드가 완벽해도 "무엇을 하는지"가 드러나지 않는 밀도 높은 코드를 풀어 쓴다.
  2. 설명 — 틀린 것처럼, 임의적인 것처럼, 불필요한 것처럼 보이는 선택의 이유를 남긴다.
  3. 경고 — 이걸 바꾸면 무엇이 깨지는지, 얼마나 심각하게 깨지는지 알린다.
  4. 기록 — 유효 기간이 있는 결정을 남긴다. 무엇을 시도했고, 무엇이 실패했고, 언제 다시 검토할 만한지.

네 가지 중 어디에도 해당하지 않는 주석은 없는 편이 낫다. 반대로 해당한다면, "클린 코드"를 명분으로 지우는 건 청소가 아니다.

"커밋 메시지에 쓰면 되지 않나" #

여기서 흔히 나오는 반론이 있다. 근거는 git 히스토리에 속하니 커밋 메시지를 잘 쓰고 소스는 깨끗하게 유지하라는 것이다. 저자의 대답은 간단하다. 1년 넘은 파일에 git log를 한번 돌려 보라는 것이다.

plaintext
...
a91f3c2  apply editorconfig
7d2e8b1  fix
3c4f9a0  fix again
e81b7d4  PR feedback
b02c6f5  final fix
f5a1e93  final fix (actually)
...

이 안에 MaxConcurrentRequests가 47인 이유가 있다. 찾으면 알려 달라는 게 저자의 응수다.

팀이 커밋 메시지를 훌륭하게 쓴다 해도 이 접근은 세 가지 지점에서 무너진다.

첫째, 멀쩡해 보이는 코드에는 아무도 git blame을 돌리지 않는다. 이유가 있다는 사실 자체를 모르면 이유를 찾지 않는다. 47이라는 상수는 미스터리가 아니라 오타로 보인다. 주석은 실수를 저지르기 전에 독자를 멈춰 세우지만, git 히스토리는 이미 떠올린 질문에만 답한다.

둘째, blame은 부패한다. 포맷 한 번, 이름 변경 한 번, 파일 분할 한 번, squash 머지 한 번이면 해당 줄은 "apply editorconfig"라는 커밋을 가리킨다. 원래 이유는 여전히 어딘가에 있지만 찾아내는 일은 조회가 아니라 발굴 작업이 된다.

셋째, 사람은 떠난다. 진짜 문서가 커밋 히스토리가 아니라 동료 Bill인 경우도 있다. Bill은 왜 47인지 안다. 그리고 Bill은 2년 전에 다른 회사로 옮겼고, 이런 질문으로 귀찮게 할 걸 알기 때문에 전화를 받지 않는다.

커밋 메시지는 변경을 설명하는 데 뛰어나지만 현재 상태를 설명하는 데는 형편없다. 현재 상태는 수십 번의 변경이 쌓인 결과이고, 아무도 그걸 순서대로 읽어 재구성하지 않는다. 지금 이 순간의 코드를 지금 보고 있는 자리에서 설명하는 곳은 주석뿐이다.

"코드를 바꾸면 주석도 바꿔야 한다"는 반론 #

주석 반대 논거로 가장 흔한 게 이것이다. 저자의 답은 "그래서 뭐가 문제냐"다.

주석은 위키도, 2021년 이후 아무도 열지 않은 Confluence 페이지도, 별도 저장소도 아니다. 지금 편집하고 있는 코드 바로 한 줄 위에 있다. 47을 50으로 바꿀 수 있으면 그 위의 문장도 바꿀 수 있다.

이 반론은 변경이 코드만 건드리고 주변은 손대지 않아야 한다는 규칙을 전제하는데, 그런 규칙은 없다. 메서드 동작을 바꾸면 테스트를 고치고, 파라미터 이름을 바꾸면 호출부를 고친다. 방금 바꾼 것을 설명하는 주석을 갱신하는 일도 같은 종류의 작업이다. 오버헤드가 아니라 원래 해야 할 일이다.

저자는 이를 더 큰 패턴의 한 사례로 본다. 좋은 지침을 너무 엄격하게 따라서, 도우려던 코드를 오히려 망치는 패턴이다. DRY도 그렇다. 비슷한 코드 두 덩이를 보고 "중복"을 없애려 공용 함수를 뽑는다. 두 사용처가 조금씩 갈라지면서 파라미터가 하나 붙고, 또 하나 붙고, 플래그 몇 개가 붙는다. 여섯 달 뒤 이런 코드를 읽게 된다.

csharp
ProcessOrder(order, true, false, null, customer, true, 3, "legacy", false, skipValidation: true);

코드는 DRY해졌다. 유일한 단점은 아무도 이게 무슨 일을 하는지 모른다는 것이다. 저자 본인도 주니어·미드레벨 시절에 이런 함수를 썼고 그 때문에 고생했다고 털어놓는다.

중복이 자동으로 나쁜 것도, 추상화가 자동으로 좋은 것도 아니다. 어느 쪽을 택할지 아는 감각이 체크리스트를 따르는 사람과 좋은 개발자를 가른다. 주석도 같다. "주석을 절대 쓰지 말라"와 "전부 주석을 달라"는 둘 다 체크리스트다.

노트북과 커피가 놓인 홈오피스 Photo by Daniil Komov on Pexels

이렇게는 쓰지 마라 #

나쁜 주석이 실재한다는 건 저자도 인정한다. 주석의 평판을 여기까지 끌어내린 주범이라는 것이다. 그가 꼽은 유형들은 이렇다.

메아리 — 코드를 그대로 되풀이하는 주석.

csharp
// Increment the retry count
retryCount++;

아무 정보도 없는 XML 문서 주석 — 여섯 줄의 형식과 0의 정보량. 대부분 경고를 없애려고 도구가 자동 생성한 것들이다.

csharp
/// <summary>
/// Gets the user
/// </summary>
/// <param name="id">The id.</param>
/// <returns>The user</returns>
public User GetUser(int id)

거짓말쟁이 — 코드와 내용이 어긋난 주석.

csharp
// Retry up to 3 times
private const int MaxRetries = 5;

영원한 "임시" 조치 — 2019년에 쓰였고 우리 모두보다 오래 살아남을 주석이다. 임시라면 무엇을 기다리는지 적어야 한다. 티켓 번호든, 버전이든, 날짜든.

csharp
// TODO: temporary workaround, remove later

묘지 — 주석 처리된 코드. 누군가 지우기를 두려워했다는 사실 외에 아무것도 알려주지 않는다. 지워라. 다시 필요해질 일은 없다.

장편소설 — 서른 줄 메서드 위에 붙은 세 단락짜리 설명. 필요한 경우도 있지만, 코드를 다시 써야 한다는 신호일 때가 많다. 먼저 리팩터링하고, 남은 복잡성에만 주석을 달라는 조언이다.

클린 코드는 절반까지만 데려다준다 #

저자는 클린 코드를 부정하지 않는다. 좋은 이름, 작은 메서드, 명확한 구조는 계속 추구해야 한다. 다만 클린 코드가 답하는 질문은 하나, "이게 무엇을 하는가"뿐이다. 실제 코드베이스는 계속 더 묻는다. 왜 이렇게 되어 있는가. 바꾸면 어떻게 되는가. 이 이상한 부분은 버그인가 흉터인가.

그 답은 문법에 없다. 코드를 쓴 사람의 머릿속에 있고, 머리는 저장 장치로서 형편없다. 이직하고, 휴가를 가고, 화요일이면 잊는다.

그래서 저자가 실제로 따르는 규칙은 한 문장이다.

한 줄을 쓰기 전에 멈춰서 생각해야 했다면, 그 생각한 내용을 적어 둔다.

명백한 줄은 그냥 둔다. 하지만 "잠깐, 여기 조심해야 하는데" 하는 순간이 있었다면 그 순간을 주석으로 남긴다. 다음 독자도 똑같이 "잠깐" 하고 멈출 텐데, 그에게는 답이 없기 때문이다.

클린 코드는 독자에게 당신이 무엇을 했는지 알려준다. 좋은 주석은 당신이 무엇을 알고 있었는지 알려준다. 둘 다 필요하다.


이 글은 위 출처를 바탕으로 한국 독자를 위해 재작성한 기사입니다. 원문의 사실과 수치에 근거하며, 별도의 견해를 포함하지 않습니다.

댓글GitHub Discussions