책이나 정적 분석 도구처럼 많은 리소스가 프로그래머들을 하여금 좋은 코드를 적게 도와주지만, 더 좋은 주석을 다는 방법을 가르쳐 주는 것은 적다. 프로그램에서 주석의 양을 확인하는 것은 쉽지만, 퀄리티를 확인하는 것은 어려우며, 주석의 양과 퀄리티가 반드시 연관성이 있는 것은 아니다. 안좋은 주석은 아예 없는 것보다 못하다. 이 글에서는 좋은 코드를 작성하도록 도와줄 가이드를 제시한다.
유명한 MIT 교수 Hal Abelson은 "프로그램은 사람들이 읽을 수 있도록 작성되어야 하고 기계가 실행될 수 있도록 부수적으로만 작성되어야 한다"고 말했다. 그가 의도적으로 코드 실행의 중요성을 과소평가했을 수도 있지만, 그는 프로그램에 매우 다른 두 명의 독자가 있다는 것에 주목한다. 컴파일러와 인터프리터는 주석을 무시하고 구문적으로 정확한 모든 프로그램을 똑같이 이해하기 쉽다는 것을 발견한다. 인간 독자는 매우 다르다. 우리는 몇몇 프로그램이 다른 프로그램보다 이해하기 어렵다는 것을 찾고서, 그것들을 이해하는 데 도움이 되는 주석을 기대한다.
안좋은 주석은 아예 없는 것보다 못하다는 것처럼, Peter Vogel이 쓴 것이 있다.
- 주석을 쓰고 유지하는 것은 비싸다.
- 당신의 컴파일러는 너의 주석을 체크하지 않기에, 그 주석이 올바른지 확인할 방법이 없다.
- 반면에, 당신은 컴퓨터가 당신의 코드가 말하는 것과 정확히 같은 일을 하고 있다는 것을 보장한다.
이러한 점이 전부 맞기는 하지만, 주석을 아예 안쓰거나 극단적으로 가는 경우에는 실수를 할 수 있다. 여기서 당신이 조금은 행복하게 주석을 달 수 있는 룰을 소개하려고 한다.
- 주석은 코드를 중복해서 작성하면 안된다.
- 좋은 주석은 명확하지 않은 코드를 변명하지 않는다.
- 만약 당신이 명확한 주석을 쓸 수 없다면, 코드에 문제가 있다는 것이다.
- 주석은 혼란을 해결해야지 야기하면 안된다.
- 비 관용적인 코드를 주석에서 설명해야 한다.
- 복사한 코드의 원본 링크를 첨부해야 한다.
- 가장 도움이 될만한 외부 참조 링크를 포함시켜야 한다.
- 버그를 고쳤을 경우 주석을 달아라.
- 아직 완성되지 못한 구현에 대해 주석을 달아라.
남은 아티클은 각각의 룰에 대해 이야기 하고 예시를 보여주고, 실제 적용되면 어떻게 되는지를 보여주려고 한다.
Rule 1. 주석은 코드를 중복해서 작성하면 안된다.
많은 주니어 프로그래머들이 입문을 할 때 너무 많은 주석을 달도록 훈련되어 왔다. 내가 본 상위권 컴퓨터 공학 수업들은 주석을 달 때 중괄호를 닫는 곳마다 주석을 달았다.
if (x > 3) {
…
} // if
나는 또한 강사가 학생에게 각 코드 라인마다 주석을 달도록 요구했다고 들은 적이 있다. 물론 이것이 아주 초보에 합리적인 정책이 될 수 있지만, 이러한 주석은 자전거의 보조바퀴 같은 것이며 아이가 크면 없애야 하는 것과 같다. 어떤 정보도 없는 주석은 다음과 같은 이유로 부정적인 결과를 초래한다.
- 추가적인 시각적 혼란
- 읽고 쓰는데 드는 시간
- 구식이 될 수 있다
아주 멋진 나쁜 예는 다음과 같다.
i = i + 1; // Add one to i
아무 정보도 주지 않으면서 유지하는데만 비용이 든다. 레딧에서는 이러한 주석을 매 라인마다 다는 것에 대해 조롱하기도 한다.
// create a for loop // <-- comment
for // start for loop
( // round bracket
// newline
int // type for declaration
i // name for declaration
= // assignment operator for declaration
0 // start value for i
Rule 2: 좋은 주석은 명확하지 않은 코드를 변명하지 않는다.
다른 잘못된 주석의 사용법은 코드에 있어야할 정보를 주석에다가 반영하는 것이다. 아주 간단한 예로 누군가가 한글자로 변수명을 짓고 여기다가 그 변수의 목적을 주석으로 추가하는 것이다.
private static Node getBestChildNode(Node node) {
Node n; // best child node candidate
for (Node node: node.getChildren()) {
// update n if the current state is better
if (n == null || utility(node) > utility(n)) {
n = node;
}
}
return n;
}
주석의 필요성은 더 좋은 변수 작명법으로 제거될 수 있다.
private static Node getBestChildNode(Node node) {
Node bestNode;
for (Node currentNode: node.getChildren()) {
if (bestNode == null || utility(currentNode) > utility(bestNode)) {
bestNode = currentNode;
}
}
return bestNode;
}
Kernighan과 Plauger가 쓴 "The Elements of Programming Style"에서처럼 말이다. "나쁜 코드를 주석으로 설명하지 마라, 다시 써라"
Rule 3: 만약 당신이 명확한 주석을 쓸 수 없다면, 코드에 문제가 있다는 것이다.
유닉스 소스코드에 있는 아주 악명높은 주석은 "너가 이걸 이해했다고 기대하지 않는다", 아주 복잡한 context-switching코드가 등장하기 전에 나타나 있는 주석이다. Dennis Ritchie는 후에 이를 두고 "이것은 뻔뻔한 도전이라기보다는 '시험에 나오지 않을 것'이라는 정신으로 의도된 것"이라고 설명했다. 불행히도, 그와 공동 저자인 Ken Thompson은 자신들도 그것을 이해하지 못했고 나중에 다시 써야 했던 것으로 밝혀졌다.
이는 Kernighan의 법칙을 고려하게 한다.
디버깅은 코드를 처음 작성할 때 보다 두배는 더 힘들다. 그래서, 만약 당신이 코드를 기깔나게 작성하면, 정의상으로 당신은 기깔나게 디버그 할 정도가 못됩니다.
독자들에게, 자신의 코드에서 벗어나라고 경고하는 것은 자동차의 비상등을 켜는 것과 같다. 즉, 당신이 알고 있는 것을 하고 있다는 것을 인정하는 것은 불법적인 일이다. 대신, 더 나은 것은, 코드를 설명할 수 있을 정도로 충분히 잘 이해하고 있는 것으로 다시 쓰는 것이며, 그것이 간단하다는 것이다.
Rule 4: 주석은 혼란을 해결해야지 야기하면 안된다.
나쁜 주석에 대해 이야기할 때 Steven Levy의 Hackers: Heores of the Computer Revolution의 스토리를 이야기하는 것 만큼 완벽한 것이 없다.
[Peter Samson]은 주어진 시간에 자신이 무엇을 하고 있었는지 설명하는 그의 소스 코드에 주석을 추가하는 것을 거부하는 것에 대해 특히 의문을 품었다. Samson이 작성한 잘 배포된 프로그램은 수백 개의 어셈블리어 명령어를 위해 진행되었으며, 1750이라는 숫자가 포함된 명령어 옆에 단 한 개의 코멘트만 있었다. 그 논평은 RIPJSB였고, 사람들은 1750년이 바흐가 죽은 해라는 것과 Samson이 Rest in Peace Johann Sebastian Bach의 약어를 썼다는 것을 알게 될 때까지 그 의미에 대해 아주 깊게 생각했다.
나는 다른 사람만큼 좋은 해킹에 대해 감사한 마음을 가지고 있지만, 이러한 것이 모범적인 것은 아니다. 주석이 의문을 해소하는 대신 혼란을 야기할 경우 주석을 삭제하자.
Rule 5: 비 관용적인 코드를 주석에서 설명해야 한다
App Inventor의 다음 코드와 같이 다른 사용자가 불필요하거나 중복된다고 생각할 수 있는 코드에 주석을 다는 것이 좋다(모든 긍정적인 예의 출처):
final Object value = (new JSONTokener(jsonString)).nextValue();
// Note that JSONTokener.nextValue() may return
// a value equals() to null.
if (value == null || value.equals(null)) {
return null;
}
주석이 없다면, 누군가는 코드를 "단순화"하거나 그것을 신비롭지만 필수적인 주문으로 볼 수 있다. 코드가 필요한 이유를 적어 미래의 독자들의 시간과 걱정을 덜어준다.
코드에 설명이 필요한지 판단을 내려야 하기도 한다. 코틀린을 배울 때, 나는 안드로이드 튜토리얼에서 코드를 접하기도 했다:
if (b == true)
나는 곧바로 의문에 들었는데, 위의 코드가 아래와 같이 대체될 수 있지 않을까 여서 였다.
if (b)
마치 자바에서 하던것 처럼 말이다. 그 후 조금의 조사를 통해, 나는 null이 될 수 있는 Boolean 변수가 추악한 null 검사를 피하기 위해 true와 명시적으로 비교된다는 것을 배웠다:
if (b != null && b)
Rule 6: 복사한 코드의 원본 링크를 첨부해야 한다.
대부분의 프로그래머 처럼 온라인에서 찾은 코드를 사용하는 경우도 있습니다. 소스에 대한 참조를 포함하면 향후 독자들이 다음과 같은 전체 맥락 얻을 수 있습니다.
- 어떤, 문제가 해결되고 있었는가
- 누가, 코드를 제공했는지
- 왜, 이 솔루션이 권장되지
- 어떤, 것에 대해 생각했는지
- 과연, 그것이 효과가 있는지 없는지
- 어떻게, 그것이 개선될 수 있는지
예를 들어 다음과 같은 주석을 보자.
/** Converts a Drawable to Bitmap. via https://stackoverflow.com/a/46018816/2219998. */
링크를 따라가면 다음과 같이 나타난다.
- 코드 작성자는 Tomáš Procházka이며, Stack Overflow에서 상위 3%에 든다.
- 댓글 작성자는 이미 레포에 통합된 최적화를 제공했다.
- 다른 댓글 작성자는 edge 케이스를 피하는 방법을 제안했따.
이 의견과 대조해 보자 (죄인을 보호하기 위해 약간 변경됨):
// Magical formula taken from a stackoverflow post, reputedly related to
// human vision perception.
return (int) (0.3 * red + 0.59 * green + 0.11 * blue);
이 코드를 이해하려는 사람은 공식을 검색해야 한다. URL에 붙여넣는 것이 나중에 참조를 찾는 것보다 훨씬 빠르다.
일부 프로그래머들은 그들이 직접 코드를 작성하지 않았다는 것을 표시하는 것을 꺼릴 수 있지만, 코드를 재사용하는 것은 시간을 절약하고 더 많은 안구의 이점을 제공하는 현명한 조치가 될 수 있다. 물론 이해하지 못하는 코드는 절대 붙여넣어서는 안 된다. 사람들은 스택 오버플로 질문과 답변에서 많은 코드를 복사하고, 그 코드는 귀속을 요구하는 크리에이티브 커먼즈 라이선스에 속한다. 참조 주석은 그러한 요구사항을 충족한다.
마찬가지로, 도움이 된 튜토리얼을 주석으로 달면 좋으며 이를 통해 작성자 덕분에 다시 찾을 수 있음을 감사하게 된다:
// Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html
// for a great reference and examples.
Rule 7: 가장 도움이 될만한 외부 참조 링크를 포함시켜야 한다.
물론 모든 참조가 스택 오버플로우인 것은 아니다. 다음을 고려해보자:
// http://tools.ietf.org/html/rfc4180 suggests that CSV lines
// should be terminated by CRLF, hence the \r\n.
csvStringBuilder.append("\r\n");
표준 및 기타 문서에 대한 링크는 코드가 해결하고 있는 문제를 독자가 이해하는 데 도움이 될 수 있다. 이 정보는 설계 문서의 어딘가에 있을 수 있지만, 잘 배치된 주석은 독자들에게 가장 필요한 시간과 장소에 대한 지침을 제공한다. 이 경우 링크를 따라가면 RFC 4180이 RFC 7111에 의해 업데이트되었음을 알 수 있습니다.
Rule 8: 버그를 고쳤을 경우 주석을 달아라.
주석은 처음에 코드를 작성 할 때 뿐만 아니라, 수정할 때 특히 버그를 고쳤을 때 달아야 한다. 다음과 같은 주석을 생각해보자:
// NOTE: At least in Firefox 2, if the user drags outside of the browser window,
// mouse-move (and even mouse-down) events will not be received until
// the user drags back inside the window. A workaround for this issue
// exists in the implementation for onMouseLeave().
@Override
public void onMouseMove(Widget sender, int x, int y) { .. }
주석은 독자가 현재 코드와 참조된 방법으로 코드를 이해하는 데 도움이 될 뿐만 아니라 코드가 여전히 필요한지 여부와 테스트 방법을 결정하는 데 도움이 된다.
또한 issue tracker*깃허브를 참조하는 데 도움이 될 수 있다:
// Use the name as the title if the properties did not include one (issue #1425)
"git blame"은 줄이 추가되거나 수정된 커밋을 찾는 데 사용될 수 있지만 커밋 메시지는 짧은 경향이 있으며 가장 중요한 변경 사항(예: fixing issue #1425)은 가장 최근 커밋의 일부가 아닐 수 있다(예: 한 파일에서 다른 파일로 메소드 이동).
Rule 9: 아직 완성되지 못한 구현에 대해 주석을 달아라.
코드가 이미 알고 있는 한계가 있음에도 불구하고 코드를 체크해야 하는 경우가 있다. 코드의 알려진 결함을 공유하지 않는 것이 유혹적일 수 있지만, TODO 코멘트와 같이 다음과 같이 명시적으로 만드는 것이 좋다:
// TODO(hal): We are making the decimal separator be a period,
// regardless of the locale of the phone. We need to think about
// how to allow comma as decimal separator, which will require
// updating number parsing and other places that transform numbers
// to strings, such as FormatAsDecimal
이러한 의견에 표준 형식을 사용하면 기술 부채를 측정하고 해결하는 데 도움이 된다. Tracker에 문제를 추가하고 주석에 문제를 참조하는 것이 좋습니다.
결론
나는 위의 예들이 주석이 나쁜 코드를 변명하거나 고치는 것이 아니라 다른 유형의 정보를 제공함으로써 좋은 코드를 보완한다는 것을 보여주었기를 바란다. 스택 오버플로우의 공동 설립자 Jeff Atwood가 쓴 것처럼, "코드는 방법을 알려주고, 주석은 이유를 말해준다." 이 규칙들을 따르는 것은 여러분과 여러분의 팀원들의 시간과 좌절을 덜어줄 것입니다.
그렇긴 하지만, 나는 이 규칙들이 완전하지 않다고 확신하며 (다른 곳에서) 주석에 제안된 추가 사항을 보기를 기대한다.
'ETC' 카테고리의 다른 글
[번역] 개발자를 위한 멘탈 케어 방법 6가지 (0) | 2024.03.15 |
---|---|
[도커 알아가기] Hypervisor(하이퍼바이저)와 Virtual Machine (0) | 2023.12.25 |
RDBMS와 NoSQL - ACID, Transaction 등등 (0) | 2023.08.17 |
[클라우드] CKA 합격 및 후기, 혹은 팁 (1) | 2023.06.05 |
[번역] 엘든링에서 배운 프로그래밍 학습에 필요한 7가지 (3) | 2023.01.10 |