[ Clean Code ] 클린코드 시리즈 - 주석

2023. 12. 23. 22:22Computer Science

  • 주석을 신뢰하면 안된다.
  • 오래될수록 완전히 그릇된 가능성도 있다.
  • 이유? 프로그래머들이 주석을 유지하고 보수하기란 현실적으로 불가능
  • 코드는 변화하고 진화한다.
  • → 일부가 여기서 저기로 위치가 변경되기도 함
  • 부정확한 주석은 아예 없는 주석보다 훨씬 더 나쁘다.

그리고 주석은 독자를 현혹하고 오도함.

⚠️ 더 이상 지킬 필요가 없는 규칙이나 지켜서는 안되는 규칙을 명시한다.

💡 진실은 한 곳에 존재 → 바로 코드

 

[ 좋은 주석 ]

정말로 좋은 주석은, 주석을 달지 않을 방법을 찾아낸 주석

1. 법적인 주석

  • 회사가 정립한 구현 표준에 맞춰 법적인 이유로 특정 주석을 넣으라고 명시
  • 소스 파일 첫머리에 주석으로 들어가는 저작권 정보와 소유권 정보는 필요하고, 타당함
  • FitNess에서 모든 소스 파일 첫머리에 추가한 표준 주석 헤더. 요즘 IDE 는 주석 헤더를 잦동으로 축소해 코드만 깔끔하게 표시함
// Copyright (C) 2003, 2004, by Object Mentor, Inc. All rights reserved.
// GNU General Public License 버전 2 이상을 따르는 조건으로 배포

2. 정보를 제공하는 주석

// 테스트 중인 Responder 인스턴스를 반환한다.
protected abstract Responder responderInstance();

Tip: 함수 이름에 정보를 담는 편이 더 좋다. 위 코드는 함수 이름을 responderBeingTested 로 바꾸면 주석이 필요 없어짐.

// kk:mm:ss EEE, MMM dd, yyyy 형식
Pattern timeMatcher = Pattern.compile(
	"\\\\d*:\\\\d*:\\\\d* \\\\w*, \\\\w*, \\\\w* \\\\d*, \\\\d*"
);

코드에서 사용한 정규표현식이 시각과 날짜를 뜻함.

대안:

이왕이면 시각, 날짜 변환하는 클래스 만들어 클래스 타입 상속받은 인스턴스 생성해 코드를 가지고 오는게 깔끔함.

3. 의도를 설명하는 주석

주석은 구현 이해를 넘어 결정에 깔린 의도까지 설명해야함.

e.g. 두 객체 비교시 다른 어떤 객체보다 자기 객체에 높은 순위를 주기로 결정한 코드 예시

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; // 오른쪽 유형이므로 정렬 순위가 더 높다.
}
public void testConcurrentAddWidgets() throw 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);

  // 스레드를 대량 생성하는 방법으로 어떻게든 경쟁 조건을 만들어 시도한다.
  for (int i = 0; i < Thread.activeCount(); i++) {
    WidgetBuilderThread widgetBuilderThread = new WidgetBuilderThread(
      widgetBuilder, text, parent, failFlag
    );
    Thread thread = new Thread(widgetBuilderThread);
    thread.start();
  }
  assertEquals(false, failFlag.get());
}

4. 의미를 명료하게 밝히는 주석

5. 결과를 경고하는 주석

6. TODO 주석

‘앞으로 할 일’ 을 // TODO 주석으로 남겨두면 편하다.

// TODO-MdM 현재 필요하지 않다.
// 체크아웃 모델을 도입하면 함수가 필요없음
protexted VersionInfo makeVersion() throws Exception 
{
	return null;
}

함수를 구현하지 않은 이유와 미래 모습을 // TODO 주석으로 설명

  • TODO 주석은 프로그래머가 필요하다 여기지만 당장 구현하기 어려운 업무를 기술한다.
    • 더 이상 필요 없는 기능을 삭제하라는 알림
    • 누군가에게 문제를 봐달라는 요청
    • 더 좋은 이름을 떠올려달라는 부탁
    • 앞으로 발생할 이벤트에 맞춰 코드를 고치라는 주의 등에 유의

7. 중요성을 강조하는 주석

대수롭지 않다고 여겨질 뭔가의 중요성을 강조하기 위해서도 주석을 사용함.

String listItemContent = match.group(3).trim();
// 여기서 trim 은 정말 중요하다. trim 함수는 문자열에서 시작 공백을 제거한다.
// 문자열에 시작 공백이 있으면 다른 문자열로 인식되기 때문
new ListItemWidget(this, listItemContent, this.level + 1);
return buildList(text.substring(match.end()));

8. 공개 API 에서 JavaDocs

설명이 잘 된 공개 API 는 참으로 유용하고 만족스럽다. 표준 자바 라이브러리에 사용한 JavaDocs 가 좋은 예다.

공개 API 구현을 한다면, 반드시 훌륭한 Javadocs를 작성한다. Javadocs 역시 독자를 오도하거나, 잘못 위치하거나, 그릇된 정보를 전달할 가능성이 존재함.

나쁜 주석

밥아저씨 왈:

대다수 주석이 이 범주에 속함. 나쁜 주석들은 허술한 코드 지탱하거나, 엉성한 코드를 변명, 미숙한 결정을 합리화 하는 등 프로그래머가 주절거리는 독백

주절거리는 주석

같은 이야기 중복하는 주석

오해할 여지가 있는 주석

의무적으로 다는 주석

이력을 기록하는 주석

있으나 마나한 주석

무서운 잡음

함수나 변수로 표현할 수 있다면 주석을 달지마라.

닫는 괄호에 다는 주석

( 헉,,SwiftUI 할때 선언형에 빌더패턴이라 그런지 세로로 길어져서 닫는 괄호에 주석을 달고 표시하곤 했었다. 사실 그건 유데미 SwiftUI 강의를 보고 그 개발자 분이 하신게 끝 부분하기 좋다고 생각해서 나도 따라하곤 했었다..근데 이게 나쁜 주석이구나..??)

아하,, 작고 캡슐화된 함수에는 좋지 못함..

그렇담 나는 써도 되겠네..세로롤 길어지다 보니 끝부분과 중괄호들 구분하기에 좋아서 쓰긴 해야겠다.

차선책: 닫는 괄호에 주석을 달아야겠다는 생각이 든다면 대신에 함수를 줄이려 시도한다.

import SwiftUI

struct CustomCirlcleView: View {
	
	@State private var isAnimateGradient: Bool = false
	
    var body: some View {
         ZStack {
             Circle()
                 .fill(
                     LinearGradient(
                         colors: [
                            .customIndigoMedium,
                            .customSalmonLight
                         ],
                         startPoint: isAnimateGradient ? .topLeading : .bottomLeading,
                         endPoint: isAnimateGradient ? .bottomTrailing : .topTrailing
                     )
                 )
                 .onAppear {
                     withAnimation(.linear(duration: 3.0).repeatForever(autoreverses:true)) {
                         isAnimateGradient.toggle()
                     }
                 }
             MotionAnimationView()
         } //: ZSTACK
         .frame(width: 256,height: 256)
		 
    }
}

struct CustomCirlcleView_Previews: PreviewProvider {
    static var previews: some View {
        CustomCirlcleView()
    }
}

이런 경우 닫는 주석 허용 가능하지 않을까?

나쁜코드 예시 (내 코드)

import SwiftUI

struct MotionAnimationView: View {
	// MARK: - PROPERTIES
	@State private var randomCircle: Int = Int.random(in: 6...12)
	@State private var isAnimating: Bool = false
	// MARK: - FUNCTIONS
	
	// 1. RANDOM COORDINATE
	func randomCoordinate() -> CGFloat {
		return CGFloat.random(in: 0...256)
	}
	// 2. RANDOM SIZE
	func randomSize() -> CGFloat {
		return CGFloat(Int.random(in: 4...80))
	}
	// 3. RANDOM SCALE
	func randomScale() -> CGFloat {
		return CGFloat(Double.random(in: 0.1...2.0))
	}
	// 4. RANDOM SPEED
	func randomSpeed() -> Double {
		return Double.random(in: 0.05...1.0)
	}
	// 5. RANDOM DELAY
	func randomDeley() -> Double {
		return Double.random(in: 0...2)
	}
	
    var body: some View {
		 ZStack {
			 ForEach(0...randomCircle, id: \.self) { item in
				 Circle()
					 .foregroundColor(.white)
					 .opacity(0.25)
					 .frame(width: randomSize())
					 .position(
						x: randomCoordinate(),
						y: randomCoordinate()
					 )
					 .scaleEffect(isAnimating ? randomScale() : 1)
					 .onAppear {
						 withAnimation(
							.interpolatingSpring(stiffness: 0.25, damping: 0.25)
							.repeatForever()
							.speed(randomSpeed())
							.delay(randomDeley())
						 ) {
							 isAnimating = true
						 }
					 }
			 }
		 } //: ZSTACK
		 .frame(width: 256, height: 256)
		 .mask(Circle())
		 .drawingGroup()
    }
}

struct MotionAnimationView_Previews: PreviewProvider {
    static var previews: some View {
		 MotionAnimationView()
			 .background(
			 Circle()
				.fill(.teal)
			 )
			 
    }
}

수정

import SwiftUI

struct MotionAnimationView: View {
	@State private var randomCircle: Int = Int.random(in: 6...12)
	@State private var isAnimating: Bool = false
	
	func randomCoordinate() -> CGFloat {
		return CGFloat.random(in: 0...256)
	}
    
	func randomSize() -> CGFloat {
		return CGFloat(Int.random(in: 4...80))
	}
    
	func randomScale() -> CGFloat {
		return CGFloat(Double.random(in: 0.1...2.0))
	}
    
	func randomSpeed() -> Double {
		return Double.random(in: 0.05...1.0)
	}
    
	func randomDeley() -> Double {
		return Double.random(in: 0...2)
	}
	
    var body: some View {
		 ZStack {
			 ForEach(0...randomCircle, id: \.self) { item in
				 Circle()
					 .foregroundColor(.white)
					 .opacity(0.25)
					 .frame(width: randomSize())
					 .position(
						x: randomCoordinate(),
						y: randomCoordinate()
					 )
					 .scaleEffect(isAnimating ? randomScale() : 1)
					 .onAppear {
						 withAnimation(
							.interpolatingSpring(stiffness: 0.25, damping: 0.25)
							.repeatForever()
							.speed(randomSpeed())
							.delay(randomDeley())
						 ) {
							 isAnimating = true
						 }
					 }
			 }
		 } //: ZSTACK
		 .frame(width: 256, height: 256)
		 .mask(Circle())
		 .drawingGroup()
    }
}

struct MotionAnimationView_Previews: PreviewProvider {
    static var previews: some View {
		 MotionAnimationView()
			 .background(
			 Circle()
				.fill(.teal)
			 )
			 
    }
}

공로를 돌리거나 저자를 표시하는 주석

주석으로 처리한 코드

HTML 주석

주석안에 HTML 태그 요소들을 다는 행위 🙅🏻‍♀️

전역 정보

모호한 관계

주석과 주석이 설명하는 코드는 둘 사이의 관계가 명백해야 함.

독자가 주석과 코드를 읽어보고 무슨 소린지 알아야 함.

e.g. apache - commons

/*
 * 모든 픽셀을 담을 만큼 충분한 배열로 시작한다(여기에 필터 바이트를 더함).
 * 그리고 헤더 정보를 위해 200바이트를 더한다.
*/
this.pngBytes = new byte[((this.width + 1) * this.height *3) + 200];

함수 헤더

짧은 함수는 긴 설명이 필요 없다.

짧고 간결, 한 가지만 수행 → ⭐️ 이름을 잘 붙인 함수가 주석으로 헤더를 추가한 함수보다 훨씬 좋다.

비공개 코드에서 Javadocs

예제 ( 90p 4-7, 4-8 코드 읽고 비교하기)