-
Notifications
You must be signed in to change notification settings - Fork 2
Chapter 04. Comments
2021.08.07 (SAT) 10:20-12:00 (100mins)
🚀 Lead by. 'KiHoon Kang'
나쁜 코드에 주석을 달지마라. 새로 짜라!
- 브라이언 W. 커니핸, P.J. 플라우거
잘 달린 주석은 유용하지만, 경솔하고 근거없는 주석은 코드를 이해하기 어렵게 만든다. 주석은 사실상 '필요악' 이다.
2장 의미있는 이름을 잘 활용한다면 주석을 쓸 필요가 없다. 원하는바를 표현하지 못해 실패를 만회하기 위해 주석을 사용한다.
코드로 의도표현에 성공했을때 +1 칭찬
주석을 달았을 경우 +1 자책
주석은 고의는 아니지만 거짓말을 자주하고, 오래될수록 관리가 힘들어 코드의 변화를 따라가지 못한다.
잘 정리된 주석은 보기에 좋아 보일 수 있지만, 애초에 주석이 필요 없는 방향을 추구하는게 더욱 옳다.
부정확한 주석은 독자를 현혹하고 오도한다.
코드만이 정확한 정보를 제공하는 진실되고 유일한 출처이므로, (간혹 필요하지만) 주석을 줄이도록 노력해야한다.
- 주석은 나쁜 코드를 보완하지 못한다
주석이 추가되는 일반적 이유는 코드 품질이 나쁘기 때문이다.
지저분한 모듈이란 것을 알아차렸을때 ~~주석달자!~~ 대신 코드를 정리하자!를 실천하자.
- 코드로 의견을 표현하라!
코드 >> 주석
코드만으로 의도를 설명하기가 쉽지 않을 수 있다. 하지만 주석 달 시간동안 고민하면 더 좋은 코드가 될 수 있다.
//직원에게 복지 혜택을 받을 자격이 있는지 검사한다.
// case1.
if ((employee.flags & HOURLY_FLAG ) &&
(employee.age > 65))
// case2.
if (employee.isEligibleForFullBenefits())
필요하거나 유의미한 주석이 존재한다. 하지만 가장 좋은 주석은, 주석을 달지 않을 방법을 찾아낸 주석!
-
법적인 주석
저작권 정보나소유권 정보는 필요하고도 타당하다.// ex1. FitNess 소스파일 첫머리 표준 주석헤더 //Copyright (C) 2003, 2004, 2005 by Object Mentor, Inc. All rights reserved //GNU General Public License 버전 2 이상을 따르는 조건으로 배포한다.
-
정보를 제공하는 주석
// ex1. 추상메서드가 반환할 값을 설명 //테스트 중인 Responder 인스턴스를 반환한다. protected abstract Responder responderInstance(); -> redponderBeingTested() 의 형태였다면 주석이 필요없다. // ex2. 정규표현식 표현 안내 //kk:mm:ss EEE, MMM dd, yyyy형식이다. Pattern timeMatcher = Pattern.compile("\\d*:\\d*:\\d* \\w*, \\w* \\d*, \\d*"); // -> SimpleDateFormat.format 함수가 반환하는 시각과 날짜를 뜻한다. // -> HH:mm:ss / kk:mm:ss 차이 ? '0시 0분' 표현 // => 00:00:00 / 24:00:00
[Java] 자바 정규 표현식 (Pattern, Matcher) 사용법 & 예제
자바 정규 표현식 참고자료
-
의도를 설명하는 주석
public int compareTo(Object o) { if(o instanceof WikiPagePath) //o가 WikiPagePath 형태인가? { WikiPagePath p = (WikiPagePath) o; //StringUtil.join은 앞의 인자들을 뒷 인자의 형태로 연결해준다. String compressedName = StringUtil.join(names, ""); String compressedArguementName = StringUtil.join(p.names, ""); return compressedName.compareTo(compressedArgumentName); // compressedName과 compressedArgumentName을 비교해 숫자로 반환해 나타낸다 // return > 0 : compressedName > compressedArgumentName // return = 0 : compressedName == compressedArgumentName // return < 0 : compressedName < compressedArgumentName } return 1; //오른쪽 유형이므로 정렬 순위가 더 높다. }
public void testConcurrentAddWidgets() throws Exception{ WidgetBuilder widgetBuilder = new WidgetBuilder(new Class[]{BoldWidget.class}); String text = "'''bold text'''"; ParentWidget parent = new BoldWidget( newMockWidgetRoot(), "'''bold text'''"); AtomicBoolean failFlag = new AtomicBoolean(); failFlag.set(false); //스레드를 대량 생성하는 방법으로 어떻게든 경쟁 조건을 만들려 시도한다. 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()); }
-
의미를 명료하게 밝히는 주석
모호한 인수나 반환값을 읽기 좋게 표현하면 좋으나 변경할 수 없는 코드라면 의미를 명료하게 주석을 달 수 있다. 하지만 주석이 올바른지 검증하기 쉽지않기 때문에 위험할 수 있다.
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 }
-
결과를 경고하는 주석
결과를 경고할 목적으로 사용하기도 한다.
!!WARNING!!//여유시간이 충분하지 않다면 실행하지 마십시오 public void _testWithReallyBigFile() { writeLinesToFile(10000000); response.setBody(testFile); response.readyToSend(this); String responseString = output.toString(); assertSubString("Content-Length: 1000000000", responseString); assertTrue(bytesSent > 1000000000); }
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 주석은 필요하지만 당장 구현하기 어려운 업무를 기술한다.
ex) 필요없는기능 삭제하라, 문제를 알아봐달라, 이름을 추천해달라, 도망쳐라 etc
하지만 너무 많은 TODO 주석 역시 나쁜코드가 되므로 주의할 것//TODO-MdM 현재 필요하지 않다. //체크아웃 모델을 도입하면 함수가 필요 없다. protected VersionInfo makeVersion() throws Exception { return null; }
-
중요성을 강조하는 주석
놓칠 수 있는 부분의 강조!
String listItemContent = match.group(3).trim(); //여기서 trim은 정말 중요하다. trim 함수는 문자열에서 시작 공백을 제거한다. //문자열에 시작 공백이 있으면 다른 문자열로 인식되기 때문이다. new ListItemWidget(this, listItemContent, this.level+1); return buildList(text.substring(match.end()));
-
공개 API에서 Javadocs
설명이 잘 된 공개 API는 유용하고, 그 좋은 예가 Javadocs이다.
공개 API를 구현하게 된다면 반드시 훌륭한 Javadocs를 작성할것이며 없으면 프로그램을 짜기 힘들것.-
Javadoc란?.class파일 등에 들어가보면 많이 보인다!
Javadoc는 JDK에 기본으로 포함되어 있는 프로그램으로 소스 코드에 포함된 클래스나
메소드의 주석(comment)으로 부터 자동으로 명세서를 작성 해주는 편리한 도구이다. -
주석에대해서, Javadoc에 대해 알아보자
한줄주석 //주석내용 블록주석 /* 주석내용 주석 주석 */ javadoc 주석 /** */ /** 여기서 엔터를 치면 자동으로 *가 붙는다 */ /** * 주석 * 주석 */ /** 주석 주석 */ 시작이 /**이고 일반적인 주석의 작성 방법인 /*에 비해 *가 하나가 많지만, /* 이후 주석으로 *가 이어서 작성되고 있는 것과 동일하기에 Java에서의 주석인 것에는 변함이 없다. Java에서의 주석에서 특히 /**로 시작하는 것만을 대상으로 한다고 생각하면 된다. 또한 Javadoc에서는 여러 줄의 주석을 작성할 때는 각 줄 앞에 *가 작성되어 있던 경우는 제외하고 그 후에 문자열만을 주석의 대상으로 한다. 그리고 *이전에 작성된 공백이나 탭도 함께 제외된다.
//sample.java /** * 이 클래스는 Javadoc 설명용 클래스이다. * 여러 줄로 작성할 수 있다. * @version 2.0 * @param string 이름 * @param int 연령 * 설명문을 태그 섹션 이후에 작성하지 않는다. */ public class Sample01 { /** * 폭 */ private int w; /** * 높이 */ private int h; /** * 디폴트 생성자 클래스 */ public Sample01() { w = 0; h = 0; } /** * 사이즈 설정 * @param width 폭 * @param height 높이 */ public void setSize(int width, int height) { w = width; h = height; } /** * 폭 반환 * @return 폭 */ public int getWidth() { return w; } /** * 높이 반환 * @return 높이 */ public int getHeight() { return h; } }
$ javadoc -private -d doc Sample01.java # Like this... # Loading source file Sample01.java... # Constructing Javadoc information... # Creating destination directory: "doc/" # Standard Doclet version 1.8.0_161 # Building tree for all the packages and classes... # Generating doc/Sample01.html... # Generating doc/package-frame.html... # Generating doc/package-summary.html... # Generating doc/package-tree.html... # Generating doc/constant-values.html... # Building index for all the packages and classes... # Generating doc/overview-tree.html... # Generating doc/index-all.html... # Generating doc/deprecated-list.html... # Building index for all classes... # Generating doc/allclasses-frame.html... # Generating doc/allclasses-noframe.html... # Generating doc/index.html... # Generating doc/help-doc.html...
$ ls -al # Like this # total 176 # drwxr-xr-x 17 kimkc staff 578 10 8 23:49 . # drwxr-xr-x 4 kimkc staff 136 10 8 23:49 .. # -rw-r--r-- 1 kimkc staff 10937 10 8 23:49 Sample01.html # -rw-r--r-- 1 kimkc staff 633 10 8 23:49 allclasses-frame.html # -rw-r--r-- 1 kimkc staff 613 10 8 23:49 allclasses-noframe.html # -rw-r--r-- 1 kimkc staff 3507 10 8 23:49 constant-values.html # -rw-r--r-- 1 kimkc staff 3457 10 8 23:49 deprecated-list.html # -rw-r--r-- 1 kimkc staff 7892 10 8 23:49 help-doc.html # -rw-r--r-- 1 kimkc staff 5448 10 8 23:49 index-all.html # -rw-r--r-- 1 kimkc staff 2744 10 8 23:49 index.html # -rw-r--r-- 1 kimkc staff 3684 10 8 23:49 overview-tree.html # -rw-r--r-- 1 kimkc staff 740 10 8 23:49 package-frame.html # -rw-r--r-- 1 kimkc staff 1 10 8 23:49 package-list # -rw-r--r-- 1 kimkc staff 3906 10 8 23:49 package-summary.html # -rw-r--r-- 1 kimkc staff 3693 10 8 23:49 package-tree.html # -rw-r--r-- 1 kimkc staff 827 10 8 23:49 script.js # -rw-r--r-- 1 kimkc staff 12842 10 8 23:49 stylesheet.css
-
대다수의 주석은 나쁜 주석이다!
-
주절거리는 주석
주석을 달려면 충분한 시간을 들여 최고의 주석을 달아야한다!
public void loadProperties(){ try{ String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE; FileInputStream propertiesStream = new FileInputStream(propertiesPath); loadedProperties.load(propertiesStream); }catch(IOException e){ //속성 파일이 없다면 기본값을 모두 메모리로 읽어 들였다는 의미다. } }
저자에게는 의미있지만 다른사람에게는 전해지지않는다.
누가 언제 어떤 기본값을 읽어들이는지 설명되지 않아 결국은 연결된 모듈을 뒤져야 이해할 수 있는 코드다. -
같은이야기를 중복하는 주석
헤더의 주석이 코드의 내용과 동일하다. (주석 읽는 시간이 더 걸린다.)
//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"); } }
tomcat에는 쓸모없는 javadocs가 굉장히 많다.
필요하다고 생각되는 주석이지만 과해질 수 있다.public abstract class ContainerBase implements Container, Lifecycle, Pipeline, MBeanRegistration, Serializable{ /** * 이 컴포넌트의 프로세서 지연값 */ protected int backgroundProcessorDelay = -1; /** * 이 컴포넌트를 지원하기 위한 생명주기 이벤트 */ protected LifecycleSupport lifecycle = new LifecycleSupport(this); }
-
이러한 나쁜 주석이 적용된 예시
if (error == 0) { //송금요청이 되었으니 잠시 후 처리결과 확인 바랍니다. Alert create_alert("안내", "송금요청이 정상처리 되었으니 잠시 후 확인해주세요", false, function() { stmtSendFormSearch(0) }); } else { //송금에 실패하였습니다. 관리자에게 연락주세요. Alert create_alert("안내", "송금에 실패하였습니다. 관리자에게 연락주세요.", false, function() { stmtSendFormSearch(0) }); }
-
-
오해할 여지가 있는 주석
this.closed가 true일 때 라는 말에서 오해가 발생할 수 있음
//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를 위한 기계적인 주석은 코드만 지저분하게 만들 뿐이다.
아래 코드는 의무적으로 작성한 주석으로 코드 해석에 전혀 도움을 주지 않는다. 심지어 CopyAndPaste 오류가 발생해 잘못된 정보를 줄 가능성도 있음
/** * * @param title CD 제목 * @param author CD 저자 * @param tracks CD 트랙 숫자 * @param durationInMinutes CD 길이(단위:분) */ 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 = durationInMinutes; cdList.add(cd); }
-
이력을 기록하는 주석
예전에는 필요하긴 했지만 지금은 소스관리프로그램이 너무나 잘되어있다.
/** * 변경 이력 (11-Oct-2001부터) * ------------------------------- * 11-Oct-2001 : 클래스를 다시 정리하고 새로운 패키지로 옮김 * 11.Nov-2001 : getDescription() method를 추가 */
-
있으나 마나 한 주석
너무 당연한 사실을 언급하며 새로운 정보가 없는 주석
/** * 기본 생성자 */ protected AnnualDateRule() { } /** 월 중 일자 */ private int dayOfMonth; /** * 월 중 일자를 반환한다. * * @return 월 중 일자 */ public int getDayOfMonth() { return dayOfMonth; }
혼자하는 헛소리
혼자하는 코드라면 상관 없지만 함께 관리하는 코드라면 정리해야한다.
private void startSending(){ try{ doSending(); } catch(SocketException e) { //정상. 누군가 요청을 멈췄다. } catch(Exception e) { try{ response.add(ErrorResponder.makeExceptionString(e)); response.closeAll(); } catch(Exception e1) { //이게뭐야! } } }
-
이러한 나쁜 주석이 적용된 예시
if ("0".equals(map.get("seq"))) { // 첫 메일발송 임을 나타내는것 같다? if ("01".equals(map.get("pmCd"))) { // 신용카드 전용 템플릿 if ("0".equals(map.get("trxStCd"))) {
-
-
무서운 잡음
무지성 javadocs는 오히려 독이된다. copy/paste도 주의를 기울이지 않으면 중복이 발생한다.
/** The name. */ private String name; /** The version. */ private String version; /** The licenceName. */ private String licenceName; /** The version. */ // <= 복사/붙여넣기한 주석이 실제 코드를 따라가지 못하고 있다. private String info;
-
함수나 변수로 표현할 수 있다면 주석을 달지 마라
// 나쁜 예 // 전역 목록 <smodule>에 속하는 모듈이 우리가 속한 하위 시스템에 의존하는가? if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem())) // 개선한 예 ArrayList moduleDependees = smodule.getDependSubsystems(); String ourSubSystem = subSysMod.getSubSystem(); if (moduleDependees.contains(ourSubSystem))
-
위치를 표시하는 주석
간혹 프로그래머는 특정 위치를 표시하려 주석을 이용한다.
너무 자주사용하지만 않는다면 득이될 수 있지만 정말 필요할때만 사용하는게 좋다.//ACTION!!!!//////////////I'm HERE!!!!/////////////////-
이러한 나쁜 주석이 적용된 예시
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ //sms 결제기한 추가 > LWH(2020-11-27)
-
-
닫는 괄호에 다는 주석
중첩이 심하고 장황하다면 의미가 있을수 있지만 잘 정리된 코드라면 필요가 없을것이다.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; 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 }
-
공로를 돌리거나 저자를 표시하는 주석
이런 주석은 시간이 지나며 방치되어 쓸모없는 정보가 되기 쉽다. (누구한테 상줄것도 아니니 이런건 지양하자)
/* Updated by KKH */-
이러한 나쁜 주석이 적용된 예시
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ //sms 결제기한 추가 > LWH(2020-11-27) // ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ // 동록/변경 소스 정리 하나부터 열까지 > LDH (2020-07-20)
-
-
주석으로 처리한 코드(*)
가장 위험하다고 생각되는 예시!
주석으로 처리된 코드는
다른사람들이 지우기를 주저한다!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()));
-
이러한 나쁜 주석이 적용된 예시
logger.info(">>> WSocket IP= " + hostname); logger.info(">>> WSocket PORT= " + port); SocketAddress socketAddress = new InetSocketAddress(hostname, port); Socket socket = new Socket(); socket.setSoTimeout(30000); //응답 타임아웃 socket.connect(socketAddress, 5000);//연결 타임아웃 BufferedWriter bufWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), charset)); bufWriter.write(data); bufWriter.newLine(); bufWriter.flush(); //BufferedReader bufReader =new BufferedReader(new InputStreamReader(socket.getInputStream(), charset)); //reciveData = bufReader.readLine(); //socket.close(); //bufReader.close(); //bufWriter.close(); InputStream is = socket.getInputStream(); byte [] length = new byte[4]; is.read(length);
-
-
HTML 주석
/** * <p> * 적합성 테스트를 수행하기 위한 과업 * 이 과업은 적합성 테스트를 수행해 결과를 출력한다. * <p/> * <pre> * 용법 : * <taskdef name = "execute-fitnesse-tests" * classname = "fitnesse.ant.ExecuteFitnesseTestsTask" * ... * 그만 알아보자 */
-
전역 정보
주석을 달아야 한다면 근처에 있는 코드만 기술하라, 추후 코드가 변했을때 주석도 함께 변할 수 있어야 한다. (변할거라는 보장도 없다.)
/** * 적합성 테스트가 동작하는 포트: 기본값은 8082 * * @param fitnessePort */ public void setFitnessePort(int fitnessePort) { this.fitnessePort = fitnessePort; }
해당 코드에서 포트의 기본값을 전혀 통제하지 못한다.(기본값이 변했을때 이 주석도 함께 수정되지 않았을 가능성이 존재)
-
너무 많은 정보 (TMT, LA.Park)
너무 장황한 정보는 정보 전체의 집중력을 떨어뜨린다.
/* RFC 2045 - Multypurpost Internet Mail Extensions (MIME) 1부 : 인터넷 메시지 본체 형식 6.8절. Base64내용 전송 인코딩(Content-Transfer-Encoding) 인코딩 과정은 입력 비트 중 24비트 그룹을 인코딩된 4글자로 구성된 출력문자열로 표현한다. 이렇게 만들어진 허구의 헛소리들은 아무도 쳐다보지 않을 것이다. 각각은 base64 알파벳에서 단일 자릿수로 해석된다.base64 인코딩으로 비트 스트림을 인코딩 할 때, 비트 스트림은 MSB 우선으로 정되렬어 있다고 가한정다. 따라서, 스트림에서 첫 비트는 첫 8비트 바이트에서 최상위 비트가 된다. */
위 주석 예제에서 헛소리와 오타가 들어았다는 사실을 눈치 챘는가?
-
모호한 관계
주석과 주석이 설명하는 코드는 관계가 명백해야한다.
/* * 모든 픽셀을 담을 만큼 충분한 배열로 시작한다(여기에 필터 바이트를 더한다) * 그리고 헤더 정보를 위해 200바이트를 더한다 */ this.pngBytes = new byte[((this.width + 1) * this.height * 3 ) + 200];
필터바이트란 무엇일까? +1은 왜 하는걸까, *3은 왜 있을까? 만약 옆에 이 함수 작성자가 있다면
이거 무슨 소리에요?라고 물어볼 것이다. -
함수 헤더
짧은 함수의 설명은 헤더로 남기기보다 의미있는 이름으로 작성한 함수가 훨씬 좋다.
-
비공개 코드에서 사용하는 Javadocs
공개 API에서는 Javadocs가 유용하지만 그렇지 않다면 꼭 쓸필요는 없다. 코드가 더 산만해질 것이다.