일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- directx
- 백준
- 알고리즘
- 운영체제
- 타입 객체
- 멀티쓰레드
- I/O장치
- 동적계획법
- 다이나믹프로그래밍
- 자료구조
- 디자인패턴
- DirectX 12
- 렌더링 파이프라인
- 다이나믹 프로그래밍
- DirectX12
- codility
- 병행성 관련 오류
- 영속성
- Direct12
- 그리디 알고리즘
- 멀티프로세서
- 그리디알고리즘
- 스케줄링
- OS
- 쓰레드
- 파일시스템 구현
- 락
- 병행성
- 프로그래머스
- 컨디션 변수
- Today
- Total
기록공간
[Clean Code] 4. 주석 본문
주석은 양날의 검과 같다. 잘 달면 정말 도움되지만, 그렇지 않으면 오히려 잘못된 정보를 제공하기 때문에 코드 이해를 더 어렵게 만든다.
만약에 깔끔하고 직관적인 코드로 그 의도를 표현할 능력이 있다면, 주석은 거의 필요가 없다. 우리는 결국 이런 의도를 표현하지 못하는 실패를 만회하기 위해 그 의도를 설명하려고 주석을 사용한다. 오히려 의도를 잘못 설명할 수도 있는데 말이다. 이런 점에서 어떻게 보면 주석은 '필요악'이라고 할 수 있겠다.
그렇기 때문에 주석이 필요한 상황에서는 어떻게 의도를 잘 표현하여 주석을 사용하지 않을지 고민을 해봐야한다. 주석을 달때마다 의도를 표현하지 못하는 본인을 한탄하고 이런 실수를 만회하기 위해 노력하자.
주석은 거짓말쟁이이다. 주석은 너무 자주 거짓말을 한다. 주석은 오래될수록 본래 코드와도 멀어질 뿐더러 완전 다른 의도를 표현할 수도 있다. 코드를 유지보수 한다는 건 들어봤어도, 주석을 유지보수 한다는 말을 들어 본적이 있는가?
여러 상황에 맞게 계속해서 변화하는 것이 코드이다. 불행히도 주석은 이런 코드를 항상 따라가지 못한다. 점점 코드에서 분리되어 왕따가 되어 버린다.
MockRequest request;
private final String HTTP_DATE_REGEXP =
"[XMTWF][a-z]{2}\\,\\s[0-9]{2}\\s[JFMASOND][a-z]{2}\\s"+
"[0-9]{4}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\sGMT";
private Response response;
// 생략...
private FitNesseContect context;
// Example: "Tue, 02 Apr 2003 22:18:49 GMT" ==> 뜬금없다???
분명 다른 코드들이 추가되면서 HTTP_DATE_REGEXP 상수와 Example 주석이 분리된게 틀림없다.
주석을 엄격히 관리하여, 정확도가 언제나 높아야 한다고 생각할 수도 있다. 맞는 말이다. 하지만 차라리 좋은 코드로 그 의도를 표현하여 주석을 없애는게 더 효율적이지 않을까?
부정확한 주석은 주석이 없는것보다 더 안좋다. 부정확한 주석은 사용자로 하여금 잘못된 방향으로 이끈다. 분명 잘못된 해석을 통해 잘못된 방향으로 구현하는 결과를 초래할 것이 뻔하다.
코드만이 정확한 정보를 전달하는 유일한 출처이다. 그렇기 때문에 우리는 주석이 필요할지라도 가능한 줄이도록 계속해서 노력하는 자세가 필요하다.
주석은 나쁜 코드를 보완하지 못한다
코드에 주석을 추가하는 이유는 코드 품질이 나쁘기 때문이다. 모듈을 만들고 보니 짜임새가 맞지 않고 이해가 어렵다. 알아먹기 힘든 모듈이라는 사실을 깨닫지만 개발자는 그저 "주석을 달아야겠군!" 이런 생각 뿐이다. 코드를 고쳐야 겠다는 생각은 이미 머릿속에서 사라졌을 것이다.
주석이 없어도 표현력이 풍부하고 깔끔한 코드가 주석이 있는 복잡한 코드보다 훨씬 좋다. 그러므로 복잡한 코드가 있다면 잔말말고 고치는데에 시간을 보내자. 그것이 오래 걸리더라도 말이다.
코드로 의도를 표현하라!
코드만으로 의도를 설명하기 어려운 경우도 없지는 않다. 다음 코드를 보자. 어느쪽이 나은가?
// 직원에게 복지 혜택을 받을 자격이 있는지 검사한다.
if((employee.flags & FOURLY_FLAG) &&
(employee.age > 65))
// 다음 코드는 어떠한가?
if(employee.isEligibleForFullBenefits())
좀만 생각하면 코드로 대다수의 의도를 표현할 수 있다. 주석으로 달려고 했던 설명을 함수로 만들어 표현해도 충분하다.
좋은 주석
어떤 주석을 필요하거나 유익하다. 이제부터 글자 값을 한다고 생각하는 주석 몇가지를 소개한다.
다시 한번 짚고 엄어갈 것이 있는데, '정말로 좋은 주석은, 주석을 달지 않는 것'이다.
법적인 주석
때때로 회사에서는 자사 표준에 맞춰 법적인 이유로 특정 주석 넣으라고 명시한다. 예를 들어 첫머리에 주석으로 들어가는 저작권 정보와 소유권 정보는 필요하다.
다음은 FitNess에서 모든 소스 파일 첫머리에 추가한 표준 주석 헤더이다.
// Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
// GNU General Public License 버전 2 이상을 따르는 조건으로 배포
소스 파일 첫머리에 들어가는 주석이 반드시 법적인 정보일 필요는 없다. 모든 정보를 열거하는 대신에, 가능하다면, 표준 라이선스나 외부 문서를 참조해도 된다.
정보를 제공하는 주석
다음처럼 기본적인 정보를 주석으로 제공하면 편리하다. 다음 코드는 메서드의 반환 반환값을 설명한다.
// 테스트 중인 Responder 인스턴스를 반환한다.
protected abstract Responder responderInstance();
이런 경우, 가능하다면 함수 이름에 정보가 녹아들어가게 구성할 수도 있을 것이다. 함수 이름을 responderBeingTested로 바꾸면 주석이 필요 없어진다.
위에서 살펴봤던 HTTP_DATE_REGEXP 정규식을 설명하는 주석도 이에 포함된다. 하지만 이 경우도 날짜를 변환하는 클래스를 만들어 코드를 옮겨주면 주석이 필요 없어진다.
의미를 명료하게 밝히는 주석
모호한 인수나 반환값은 그 의미를 좋게 표현하면 이해가 쉬워진다. 인수나 반환값이 표준 라이브러리거나 변경 불가능한 코드에 속한다면 그 의미를 정확하게 밝히는 주석이 유용하다.
public void testCompareTo() throws Exception
{
assertTrue(a.compareTo(a) == 0); // a == a
assertTrue(a.compareTo(b) != 0); // a != b
assertTrue(ab.compareTo(ab) == 0); // ab == ab
assertTrue(a.compareTo(b) == -1); // a < b
assertTrue(aa.compareTo(ab) == -1); // aa < ab
assertTrue(ba.compareTo(bb) == -1); // ba < bb
assertTrue(b.compareTo(a) == 1); // b > a
assertTrue(ab.compareTo(aa) == 1); // ab > aa
assertTrue(bb.compareTo(ba) == 1); // bb > ba
}
잘못된 주석을 달아놓을 위험은 굉장히 높다. 또한 올바른지 검증도 쉽지 않다. 그러므로 위와 같은 주석을 달 때에는 더 나은 방법이 없는지 먼저 고민하고 정확히 달려고 하는 노력이 필요하다.
결과를 경고하는 주석
때로 다른 프로그래머에게 결과를 경고할 목적으로 주석을 사용한다. 예를 들어, 다음 특정 테스트 케이스를 꺼야하는 이유를 설명하는 주석이다.
// 여유 시간이 충분하지 않다면 실행하지 마십시오.
public void _testWithReallyBigFile() {
writeLinesToFile(10000000);
response.setBody(testFile);
response.readyToSend(this);
String responseString = output.toString();
assertSubString("Content-Length: 1000000000", responseString);
assertTrue(byteSent > 1000000000);
}
요즘은 JUnit4으로 @Ignore 속성을 이용해 테스트 케이스를 꺼버릴수 있다. 그리고 이 속성 문자열에 구체적인 설명을 넣어줄 수 있다. @Ignore("실행이 너무 오래 걸린다!"). JUnit4가 나오기 전에는 메서드 앞에 '_' 를 붙이는 방법이 일반적인 관례였다.
다음 주석은 아주 적절한 예제이다.
public static SimpleDateFormat makeStandardHttpDateFormat()
{
// SimpleDateFormat은 스레드에 안전하지 못하다.
// 따라서 각 인스턴스를 독립적으로 생성해야 한다.
SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df;
}
여기서는 주석이 아주 합리적이다. 성능을 높히기 위해 해당 함수를 사용하려던 프로그래머가 있다면 주석 때문에 큰 실수를 면하게 될것이다.
TODO 주석
앞으로 해야 할 일을 TODO 주석으로 남겨두면 편하다. 다음 코드를 보자.
// TODO-MdM 현재 필요하지 않음
// 체크아웃 모델을 도입하면 함수가 필요 없음
protected VersionInfo makeVersion() throws Exception
{
return null;
}
TODO 주석은 해당 코드가 필요한 경우이지만 당장 구현이 어려운 경우 기술한다. 또한 필요없는 기능 삭제 알림, 문제를 봐달라는 요청, 더 좋은 이름을 떠올리라는 충고, 발생할 이벤트에 맞춰 코드 수정 요구 등에 유용하다.
하지만 어떤 용도로 사용하든 시스템에 나쁜 코드를 남겨 놓은 핑계가 되어서는 안된다. 요즘 대다수 IDE는 TODO 주석 전부를 찾아 보여주는 기능을 제공하므로 주석을 잊어버릴 걱정은 할 필요가 없다. 그래도 TODO는 주기적으로 사용이 끝났다면 없애야 한다.
중요성을 강조하는 주석
별거 아니라고 느껴질만한 것도 중요성을 강조하기 위해 주석을 사용한다.
String listItemContent = match.group(3).trim();
// 여기서 trim은 정말 중요하다. trim 함수는 문자열에서 시작 공백을 제거한다.
// 문자열에 시작 공백이 있으면 다른 문자열로 인식되기 때문이다.
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end());
나쁜 주석
대다수의 주석이 여기에 속한다. 이 주석들은 허술한 코드를 지탱하거나, 혹은 변명하거나, 합리화 하는 프로그래머의 변명에서 크게 벗어나지 못한다.
주절거리는 주석
어떤 이유 없이 마지못해 주석을 다는 경우 시간낭비를 할 뿐이다. 주석을 달기로 결정했다면 충분한 고려를 통해 완벽한 주석을 달 수 있게 노력해야 한다.
다음 코드를 보도록 하자.
public void loadProperites()
{
try
{
String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
FileInputStream propertiesStream = new FileInputStream(propertiesPath);
loadedProperties.load(propertiesStream);
}
catch(IOException e)
{
// 속성 파일이 없다면 기본값을 모두 메모리로 읽어 들였다는 의미이다.
}
}
catch 블록 내에 주석은 무슨 의미일까? 확실히 어떤 의미를 담고 있겠지만, 그 의미가 다른 사람들에게 까지는 전달되지 않는다. 주석을 통해 유추하자면 IOException은 속성 파일이 없다는 뜻이고, 그러면 모든 기본값을 메모리로 읽어 들인 상태란다. 하지만 모든 기본값을 어디서 읽어들일 것이고, 어떻게 구현할 것인지에 대한 구체적인 설명이 없다.
답을 찾기 위해서는 다른 코드를 찾아볼 수 밖에 없다. 이해가 되지 않아 다른 코드를 살펴봐야 하는 주석은 부족한 주석이다. 이런 주석은 그저 공간만 차지할 뿐이고, 아무런 도움이 되지 않는다.
같은 이야기를 중복하는 주석
다음 코드를 보자.
// this.closed가 true일 때 반환되는 유틸리티 메서드이다.
// 타임아웃에 도달하면 예외를 던진다.
public synchronized void waitForClose(final long timeoutMillis)
throws Exception
{
if(!closed)
{
wait(timeoutMillis);
if(!closed)
throw new Exception("MockResponseSender could not be closed");
}
}
위와 같은 주석을 다는 이유는 무엇일까? 주석이 코드보다 더 많은 정보를 제공하지 못하고 있다. 오히려 코드보다 읽기가 수월하지 않다. 또한 코드보다 부정확하여 함수를 대충 이해하고 넘어가게 만든다.
이렇게 코드보다 못한 주석은 안다는게 훨씬 더 깔끔하다. 만약 서로 같다고 하더라도 주석을 달지 말아야 한다. 그저 중복되는 내용을 주석으로 적었을 뿐이기 때문이다. 주석은 코드의 이해가 어려운 경우 그것의 의도를 부연설명하는 수단이라는 것을 절대 잊어서는 안된다.
의무적으로 다는 주석
모든 함수에 Javadocs를 달거나 모든 변수에 주석을 달아야 한다는 규칙은 어리석기 그지없다. 이런 주석들은 코드를 복잡하게 만들며, 잘못된 정보를 전달하고, 혼동을 초래한다.