혼자 정리
4. 주석 본문
4장 주석
- 주석은 '필요악'이다
- 주석을 유지 보수하는 것은 쉽지 않으니 오래될수록 코드와 동떨어지게 된다
- → 없는 것보다 못하게 될 수 있음
- 가능하다면 코드에 제대로 된 정보를 담아서 해결하자
- 주석을 유지 보수하는 것은 쉽지 않으니 오래될수록 코드와 동떨어지게 된다
주석은 나쁜 코드를 보완하지 못한다
- 나쁜 코드를 주석을 통해 보완하는 것보다 코드를 정돈하는 것이 더 효율적이다.
코드로 의도를 표현하라!
- 물론 확실히 코드만으로 의도를 설명하기 어려운 경우도 있다. 그래서 많은 개발자들이 자신의 코드도 그런 경우라고 생각하는데 잘 생각해보면 해결책이 있는 경우가 더 많으니 코드로 해결해보자.
- ex)
-
// 직원에게 복지 혜택을 받을 자격이 있는지 검사한다. if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
-
if (employee.isEligibleForFullBenefits())
- 첫번째 코드보다 두번째가 낫다.
-
좋은 주석
여기서 소개하는 주석은 사실상 불가피한 주석이라 봐도 된다
법적인 주석
- 회사가 정립한 구현 표준에 맞춰 법적인 이유로 특정 주석을 넣으라고 하는 경우
- ex) 각 소스 파일 첫머리에 저작권 정보와 소유권 정보 표기
- 모든 조항과 조건을 열거하는 대신, 가능하다면 표준 라이센스나 외부 문서를 참조하게 하자.
정보를 제공하는 주석
- 예시를 보자.
-
// kk:mm:ss EEE, MMM dd, yyyy 형식 Pattern timeMatcher = Pattern.compile( "\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*");
- 코드의 정규표현식의 의미를 설명해준다.
- 그렇지만 이왕이면 시각과 날짜를 변환하는 클래스를 만들어서 코드를 옮겨주면 더 좋겠다
-
의도를 설명하는 주석
- 왜 이렇게 구현했는지 개발자의 의도를 설명해줄 수 있다.
- ex1)
-
public int compareTo(Object o) { if (o instanceof WikiPagePath) { WikiPagePath p = (WikiPagePath) o; String compressedName = StringUtil.join(names, ""); String compressedArgumentName = StringUtil.join(p.names, ""); return compressedName.compareTo(compressedArgumentName); } return 1; // we are greater because we are the right type. }
- 자기 객체를 다른 객체보다 높은 순위를 부여하기로 했다는 점을 설명
-
- ex2)
-
public void testConcurrentAddWidgets() throws Exception { WidgetBuilder widgetBuilder = new WidgetBuilder(new Class[]{BoldWidget.class}); String text = "'''bold text'''"; ParentWidget parent = new BoldWidget(new MockWidgetRoot(), "'''bold text'''"); AtomicBoolean failFlag = new AtomicBoolean(); failFlag.set(false); //This is our best attempt to get a race condition //by creating large number of threads. for (int i = 0; i < 25000; i++) { WidgetBuilderThread widgetBuilderThread = new WidgetBuilderThread(widgetBuilder, text, parent, failFlag); Thread thread = new Thread(widgetBuilderThread); thread.start(); } assertEquals(false, failFlag.get()); }
-
의미를 명료하게 밝히는 주석
- argument나 반환값이 표준 라이브러리나 변경하지 못하는 코드에 속한다면 의미를 명료하게 밝히는 주석이 유용
- 그럼에도 주석에 오류가 생길 가능성이 있기에 위험성이 존재한다. 따라서 이를 잘 생각하고 주석을 달자
- ex)
-
public void testCompareTo() throws Exception { WikiPagePath a = PathParser.parse("PageA"); WikiPagePath ab = PathParser.parse("PageA.PageB"); WikiPagePath b = PathParser.parse("PageB"); WikiPagePath aa = PathParser.parse("PageA.PageA"); WikiPagePath bb = PathParser.parse("PageB.PageB"); WikiPagePath ba = PathParser.parse("PageB.PageA"); 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 }
-
결과를 경고하는 주석
- ex1)
-
// Don't run unless you // have some time to kill. public void _testWithReallyBigFile() { writeLinesToFile(10000000); response.setBody(testFile); response.readyToSend(this); String responseString = output.toString(); assertSubString("Content-Length: 1000000000", responseString); assertTrue(bytesSent > 1000000000); }
-
- ex2)
-
public static SimpleDateFormat makeStandardHttpDateFormat() { //SimpleDateFormat is not thread safe, //so we need to create each instance independently. SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z"); df.setTimeZone(TimeZone.getTimeZone("GMT")); return df; }
-
TODO 주석
- 앞으로 할 일을 남겨두면 편하다.
- ex) 당장 구현하기 어려운 업무, 더 이상 필요 없는 기능을 삭제하라는 알림, 누군가에게 문제를 봐달라는 요청, 더 좋은 이름으로 바꿔달라는 요청, 앞으로 발생할 이벤트에 맞춰 코드를 고치라는 주의 등
- 주기적으로 TODO 주석을 점검해서 없애도 괜찮은 주석은 없애자
-
//TODO-MdM these are not needed // We expect this to go away when we do the checkout model protected VersionInfo makeVersion() throws Exception { return null; }
-
중요성을 강조하는 주석
- 그냥 보면 사소하게 여겨질 수 있는 부분을 주석을 통해 강조할 수 있다.
- ex)
-
String listItemContent = match.group(3).trim(); // the trim is real important. It removes the starting // spaces that could cause the item to be recognized // as another list. new ListItemWidget(this, listItemContent, this.level + 1); return buildList(text.substring(match.end()));
-
공개 API에서 Javadocs
- javadocs를 통해 설명을 잘 해놓으면 api 이용자가 프로그램을 짤 때 유용하다.
- 물론 잘못된 정보 주지 않게 조심하자.
나쁜 주석
주절거리는 주석
- 애매하게 단 주석은 오히려 읽는 사람을 헷갈리게 한다.
- ex)
-
public void loadProperties() { try { String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE; FileInputStream propertiesStream = new FileInputStream(propertiesPath); loadedProperties.load(propertiesStream); } catch(IOException e) { // No properties files means all defaults are loaded } }
-
같은 이야기를 중복하는 주석
- 코드에 있는 내용을 굳이 반복하지 말자. 주석을 읽는 데 시간낭비하게 한다.
- 오히려 코드보다 설명이 부실해서 주석만 읽고 가면 더 이해도가 낮게 될 수도 있다.
-
// Utility method that returns when this.closed is true. Throws an exception // if the timeout is reached. public synchronized void waitForClose(final long timeoutMillis) throws Exception { if(!closed) { wait(timeoutMillis); if(!closed) throw new Exception("MockResponseSender could not be closed"); } }
오해할 여지가 있는 주석
- 바로 위의 코드는 this.closed가 true이면 반환되는 것이지 this.closed가 true로 변할 때 반환되지 않는다. 또한 this.closed가 true가 아닌 경우에만 타임아웃에서 예외를 던진다. 하지만 주석은 오해하기 쉽게 써놓았다.
의무적으로 다는 주석
- ex) 억지로 javadocs를 위해 단 것 같은 주석
-
/** * * @param title The title of the CD * @param author The author of the CD * @param tracks The number of tracks on the CD * @param durationInMinutes The duration of the CD in minutes */ public void addCD(String title, String author, int tracks, int durationInMinutes) { CD cd = new CD(); cd.title = title; cd.author = author; cd.tracks = tracks; cd.duration = duration; cdList.add(cd); }
-
이력을 기록하는 주석
- 버전 관리 시스템이 없던 시절에나 필요했던 것이다.
- 없애자.
있으나 마나 한 주석
- 너무 당연한 사실을 알려주는 주석
- 이러한 주석의 문제점은 읽는 사람이 중요한 주석조차 무시하게 만든다는 점이다.
- ex1)
-
/** * Default constructor. */ protected AnnualDateRule() { }
-
/** The day of the month. */ private int dayOfMonth;
-
/** * Returns the day of the month. * * @return the day of the month. */ public int getDayOfMonth() { return dayOfMonth; }
-
무서운 잡음
- javadocs도 의미 있는 내용을 제공하지 않으면 잡음일 뿐이다.
함수나 변수로 표현할 수 있다면 주석을 달지 마라
- 예시를 보자.
- ex1)
-
// does the module from the global list <mod> depend on the // subsystem we are part of? if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
- 위의 코드보다는 주석이 필요하지 않도록 다음과 같이 개선할 수 있다.
-
ArrayList moduleDependees = smodule.getDependSubsystems(); String ourSubSystem = subSysMod.getSubSystem(); if (moduleDependees.contains(ourSubSystem))
-
-
- ex1)
위치를 표시하는 주석
- ex)
-
// Actions ////////////////////////////////////
-
- 반드시 필요할 때만 사용하자. 안 그러면 읽는 사람은 잡음으로만 여길 것이다
닫는 괄호에 다는 주석
- 중첩이 심하고 장황한 함수라면 의미가 있을지도 모르지만 작고 캡슐화된 함수에는 잡음일 뿐이다.
- ex)
-
public class wc { public static void main(String[] args) { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String line; int lineCount = 0; int charCount = 0; int wordCount = 0; try { while ((line = in.readLine()) != null) { lineCount++; charCount += line.length(); String words[] = line.split("\\W"); wordCount += words.length; } //while System.out.println("wordCount = " + wordCount); System.out.println("lineCount = " + lineCount); System.out.println("charCount = " + charCount); } // try catch (IOException e) { System.err.println("Error:" + e.getMessage()); } //catch } //main }
-
공로를 돌리거나 저자를 표시하는 주석
- 소스 코드 관리 시스템이 누가 언제 무엇을 추가했는지 알려주므로 굳이 달지 말자
주석으로 처리한 코드
- 주석으로 처리한 코드는 이유가 있어 남겨놓았으리라고 생각하게 만들기 때문에 다른 사람들이 쉽게 지우지 못한다. 따라서 쌓이기 쉬운 쓰레기다
- 어차피 소스 코드 관리 시스템이 다 기억하므로 지금 쓰지 않는 코드는 다 지우자.
- ex)
-
InputStreamResponse response = new InputStreamResponse(); response.setBody(formatter.getResultStream(), formatter.getByteCount()); // InputStream resultsStream = formatter.getResultStream(); // StreamReader reader = new StreamReader(resultsStream); // response.setContent(reader.read(formatter.getByteCount()));
-
HTML 주석
- javadocs같은 도구로 주석을 뽑아서 웹 페이지에 올릴 거라면 주석에 HTML 태그를 삽입하는 것은 개발자가 아닌 도구가 해야 하는 일이다.
- ex)
/** * Task to run fit tests. * This task runs fitnesse tests and publishes the results. * <p/> * <pre> * Usage: * <taskdef name="execute-fitnesse-tests" * classname="fitnesse.ant.ExecuteFitnesseTestsTask" * classpathref="classpath" /> * OR * <taskdef classpathref="classpath" * resource="tasks.properties" /> * <p/> * <execute-fitnesse-tests * suitepage="FitNesse.SuiteAcceptanceTests" * fitnesseport="8082" * resultsdir="${results.dir}" * resultshtmlpage="fit-results.html" * classpathref="classpath" /> * </pre> */
전역 정보
- 주석을 달거면 근처에 있는 코드에 대해서만 설명해라
- 시스템 전반적인 정보에 대해 설명하지 말아라
- 그런 주석은 해당 정보가 바뀌어도 추적해서 수정될 가능성이 낮다.
- ex)
-
/** * Port on which fitnesse would run. Defaults to <b>8082</b>. * * @param fitnessePort */ public void setFitnessePort(int fitnessePort) { this.fitnessePort = fitnessePort; }
-
너무 많은 정보
- ex)
-
/* RFC 2045 - Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies section 6.8. Base64 Content-Transfer-Encoding The encoding process represents 24-bit groups of input bits as output strings of 4 encoded characters. Proceeding from left to right, a 24-bit input group is formed by concatenating 3 8-bit input groups. These 24 bits are then treated as 4 concatenated 6-bit groups, each of which is translated into a single digit in the base64 alphabet. When encoding a bit stream via the base64 encoding, the bit stream must be presumed to be ordered with the most-significant-bit first. That is, the first bit in the stream will be the high-order bit in the first 8-bit byte, and the eighth bit will be the low-order bit in the first 8-bit byte, and so on. */
-
모호한 관계
- 주석과 주석이 설명하는 코드 사이의 관계가 명확하게 하자. 적어도 주석 과 코드를 읽고 무슨 소리인지는 알 수 있게 하라는 말이다.
- ex)
-
/* * start with an array that is big enough to hold all the pixels * (plus filter bytes), and an extra 200 bytes for header info */ this.pngBytes = new byte[((this.width + 1) * this.height * 3) + 200];
-
함수 헤더
- 짧은 함수는 긴 설명이 필요 없다.
- 짧고 한 가지만 수행하며 이름을 잘 붙인 함수가 주석으로 헤더를 추가한 함수보다 훨씬 좋다.
'클린코드' 카테고리의 다른 글
6. 객체와 자료구조 (0) | 2021.10.10 |
---|---|
5. 형식 맞추기 (0) | 2021.10.06 |
3. 함수 (0) | 2021.09.23 |
2. 의미 있는 이름 (0) | 2021.09.19 |
1. 깨끗한 코드 (0) | 2021.09.14 |