Search

[4 장] 주석 - 주석이 아닌 시스템이 말하게 하기

주석이 아닌 시스템이 말하게 하기

들어가면서

주석에 대해 진지하게 생각해보았던가? 남발은 하지 말고 꼭 필요한곳에 주석을 사용하자는건 알지만 남발의 기준은? 꼭 필요한곳의 기준은? 애매 모호한 부분을 좀 더 알아보려고 한다.
브라이언 w.커니헨, P.J.플라우거는 나쁜 코드에 주석을 달지말라. 새로 짜라.라고 한다. 코드로 그만큼 충분히 의도를 표현하지 못했다고 이해해볼 수도 있다. single of truth가 되는 코드가 이미 잘못되었는데 여기에 덧붙이는 주석은 오히려 문제점을 더 가지고 올 수 있다고 생각한다. 어떤 문제점이냐면 주석은 오래될 수록 코드에서 멀어진다. 그래서 결국 거짓말을 하게 되고 프로그래머들이 주석을 유지하고 보수하기가 현실적으로 어려워 지는 문제점이다.
clean code책과 파이브오브라인을 참고해 케이스들을 정리해보았다.

본문

1.개발 중 유용하게 사용한 주석은 개발 후 배포전에는 제거 할 수 있게 버전 관리 시스템을 이용하는게 좋다.
간혹 새로운 코드를 실험해 보면서 실패하면 신속하게 되돌리기 위해 기존코드를 주석으로 처리하는 경우가 있다. 이 때 문제는 혼자 작업하거나 깃브랜치에 대한 시스템이 팀에 정립되어 있지 않은 경우 혹은 main위에서 작업하는 경우 이미 main에 주석이 병합되고 나면, 개발자가 이를 정리할 타이밍을 놓칠 때가 있다. 주석으로 옛 코드를 남겨두는 대신, Git 브랜치를 활용해 실험과 복구를 관리하는게 좋다. 브랜치를 따서 실험하고 실패하면 버리면 되고, Git이 모든걸 기억하니 따로 주석을 백업할 필요가 없다.
그렇지 않으면 아래와 같이 main 코드가 주석투성이가 되거나, 다른 팀원이 보기엔 “어느 게 진짜 코드인지” 혼란스러워 진다.
const PHI = (1+ Math.sqrt(5))/2 const PHI_ = (1- Math.sqrt(5)) /2 const c = 1/Math.sqrt(5) function fib(n:number){ // if(n<=1) return n // else return fib(n-1)+fib(n-2) return c * (Math.pow(PHI, n))- Math.pow(PHI_, n) }
TypeScript
복사
2. 주석이 코드로 대체될 수 있는지, 아니면 꼭 주석으로 남겨야 하는지 판단하는 기준
2.1 코드가 주석만큼 읽기 쉬울 때 꼭 주석으로 남겨야 하는지 자문해보기
//에러로그 Logger.error(errorMessage, e)
TypeScript
복사
2.2 주석을 메서드로 추출해(리팩토링) 코드에 의미를 드러내기
AS-IS
//요청 url을 만듦 if(queryString) fullUrl +="?" +queryString
TypeScript
복사
TO-BE
import * as querystring from "node:querystring"; fullUrl = buildRequestUrl(fullUrl, qeuryString) function buildRequestUrl(fullUrl, queryString) { if (queryString) fullUrl +="?" +queryString return fullUrl }
TypeScript
복사
2.3 불변속성을 문서화하는 주석은 코드 또는 자동 테스트로 변환을 고려한다.
내용이 조금 어려울 수 있는데 최대한 쉽게 풀어보도록 하겠다. 해당 코드나 주석이 없어졌을 때, 시스템의 근본적인 신뢰성이 무너지는가? 에 대한 질문에 “예”라고 답할 수 있으면, 불변속성을 지키는 코드이다. 만약 해당 코드나 주석이 단지 UX가 좀 불편해진다거나 속도가 느려진다 정도면 그건 불변속성이 아니다. 보안·무결성·일관성을 지키는 코드일수록 불변속성에 가깝다.
예시를 보자.
// 다음 요청 때 강제로 재인증하도록 하는 로그아웃 session.logout();
TypeScript
복사
이걸 판단해볼 때, 다음 질문을 던질 수 있다. 이 코드를 제거하면 시스템이 여전히 정상 동작할까? 그런데 보안적으로 “인증이 만료된 사용자가 요청을 보낼 수 있는가?” 그러면 시스템의 신뢰성이 유지되는가? 를 생각해보면 이 코드가 단순한 “기능 코드”가 아니라, 시스템의 정합성 조건을 유지하기 위한 장치임을 알수있다.
이건 어떨까?
// 더 빠른 로딩을 위해 prefetch 비활성화 fetchData({ prefetch: false });
TypeScript
복사
이건 단지 성능 최적화 선택이지 없다고 해서 시스템이 깨지는 건 아니다. 따라서 불변속성 아니다.
그럼 다시 session예제로 돌아와서, 해당 주석을 이해해보자. session.logout(); 코드만 보면 단순히 로그아웃하는 코드이다. 그런데 주석엔 단순 로그아웃이 아니라, 의도적으로 세션을 끊어서 다음 요청에 재인증을 강제하기 위한 행동을 나타내는 강제로그아웃 임을 유추해볼 수 있다.
이건 인증 시스템이 외부와 연결되어 있을 수도 있고, 테스트하기 어려운 영역일 가능성이 높아. 그래서 이런 주석은 버그를 막는 역할을 한다. 그렇다면 이 주석이 꼭 필요하니 그냥 그대로 놔두면 될까? 문제는 없을까?
문제가 있다. 왜 로그아웃을 하는지라는 의도(= 보안 정책)는 주석으로만 표현돼 있지, 코드 상으로는 전혀 드러나지 않는다. 즉, 컴파일러도 모르고 리뷰어도 한눈에 알아보기 어렵고 테스트에서도 검증되지 않는다. 버그를 만들기 쉬운 주석이다. 이럴 땐 주석이 나타내는 불변속성을 코드나 테스트로 옮기는 방향을 고민해야 한다.
다음 코드는 재인증이 필요한 경우 세션을 무효화하는걸로 의도를 표현해보았다. 이 코드에는 더이상의 주석이 필요없다.
if (requiresReauthentication(user)) { session.invalidate(); }
TypeScript
복사
혹은 이걸 코드로 표현할 수 없다면 테스트라도 보장해야한다.
test("비밀번호 변경 후 다음 요청에서는 재로그인이 필요하다", async () => { await loginAs(user); await changePassword(user); const response = await requestProtectedResource(); expect(response.status).toBe(401); // 재로그인 요구 });
TypeScript
복사
이렇게 하면 주석이 없어도, 테스트 자체가 시스템의 불변속성(비밀번호 변경 시 재인증 필요)을 보장하게 할 수 있다.
마무리
코드가 작성되면 주석이 코드에 그만한 가치를 부여하는지 비판적으로 평가 해야 한다.
그리고 결국 좋은 코드는 “주석으로만 유지되는 의도”를 최대한 줄이고, 시스템이 스스로 그 규칙을 증명할 수 있는 방향으로 진화해야 한다.