2014. 4. 22. 23:32

코멘트 (Comment) 리팩토링에 대해 알아보자.

마틴 파울러는 '코드의 부족을 보충하기 위해 상세한 주석이 있는 것' 또한 나쁜 냄새라고 지적했다.


그렇다면 '코드의 부족(lack of code)'이라는 무슨 의미일까? 

작성된 코드가 '대체로 잘 동작하지만, 완벽하다고 말하기는 어렵다는 의미이다.

프로그래머가 일정에 밀려서 미치 완성하지 못한 것일 수도 있고, 정말 귀찮아서 보완하지 못한 것일 수도 있다.

아니면, 실력이 없거나... 누구나 완벽한 것이 가장 좋다고 말하지만, 어디 현실이 그러한가? 

어쩔 수 없다는 핑계와 양해를 주거니 받거니 하면서 살아가는 것이다.


하지만, 백번 양보를 해도 코드가 완벽하지 못하니 알아서 잘 쓰라고 (조심하라는 의미에서...)

주석(comment)에 설명을 붙이는 것은 당신이 가는 길에 지뢰가 있으니 알아서 피하라는 표지판과 다름이 없다.


  • 표지판을 못 볼 수도 있다. (모든 개발자가 주석을 보고 나서 함수를 가져다 쓴다는 보장은 없고,
    현실적으로 많은 이들이 주석을 읽으려 하지 않는다.
  • 표지판을 보고도 그냥 지나칠 수 있다.
    설마하는 사람도 있고, 주석을 보고도 함수(혹은 API)를 가져다 쓰는 사람은 자신에게는 아무런 책임이 없다고 생각하기도 한다.
  • 주석은 실행에 아무런 영향을 주기 때문에, 경고성 주석이 유지보수 혹은 코드 정리 과정에서 사라질 수도 있다.


결론은 프로그램에 잠재적인 오류가 있다거나, 오용(잘못된 사용) 가능성이 있다면
주석으로 문제를 영원히 덮을 수 없다는 것이다.


코딩 사례를 보고 이해해보도록 하자. 많은 실무 프로젝트에서 공통 API (common API)를 만들어 사용하게 되는데 회원 정보를 대다수의 사이트에서 '나이를 계산하는 공통 함수'를 만들어 사용한다. 나이를 계산하기 위해서는 '생년월일'을 입력 받아야 하고, 개발 편의성을 높이기 위해, 굳이 Date 타입으로 입력받지 않고 문자열로 태어난 날짜를 입력 받도록 한다. 여기서 '코드의 부족(lack of code' 문제가 자주 발생하게 된다. 공통 함수이다 보니 가져다 쓰는 곳이 많고, 오류가 발생하더라도 원인 파악이 손쉬워야 함에도 불구하고 간단한 함수라 여겨 대충 만드는 일이 많다.


아래 코드는 주석에 잘못된 입력 값이 들어왔을 경우, 적절한 예외가 발생한다는 것을 명시하였다.

만일, 공통 API를 사용하는 개발자가 주석을 읽지 않고 API를 호출하더라도 잘못된 입력 값(인자 혹은 parameter) 오류의 원인을 파악할 수 있는 '메시지'가 예외 객체에 포함되기 때문에 문제를 빨리 해결할 수 있다. 


만일, 예외처리하는 로직 없이 메소드 주석에 날짜 형식은 'yyyyMMdd'이라는 안내문구를 적어둔 경우는 '코드의 부족을 위해 상세한 주석이 있는 나쁜 냄새가 나는 코드'가 되는 것이다.


package common.util;

import java.sql.Date;

import org.joda.time.DateTime;
import org.joda.time.Years;

/**
 * 업무구분 : 공통 (cn)
 * 업 무 명 : 공통 API
 * 파 일 명 : AgeUtils.java
 * 작 성 일 : 2014-04-22
 * 설    명 : 현재 혹은 기준 일자를 기준으로 한국 나이, 만 나이를 계산하는 유틸리티 메소드들을 제공한다.
 */
public final class AgeUtils {

	private AgeUtils() {
		// This class does not provider public constructor
	}

	/**
	 * 현재 날짜를 기준으로 생년월일에서 한국식 나이를 구한다.
	 * 
	 * AgeUtils.getAge("19731201"); = 41 (현재 년도가 2013년인 경우)
	 * 
	 * @param birthDate 생년월일 (yyyyMMdd 형식)
	 * @return 현재 일자를 기준으로 계산된 한국식 나이
	 * @throws IllegalArgumentException null 혹은 잘못된 형식의 날짜를 입력한 경우 예외 발생
	 */
	public static int getAge(String birthDate) {
		return getAge(birthDate, DateUtils.getTodayString());
	}

	/**
	 * 기준일자를 기준으로 생년월일에서 한국식 나이를 구한다.
	 * 
	 * AgeUtils.getAge("19731201", "20121225"); = 40
	 * 
	 * @param birthDate 생년월일 (yyyyMMdd 형식 혹은 yyyy 형식)
	 * @param refDate 기준일자 (yyyyMMdd 형식 혹은 yyyy 형식)
	 * @return 한국식 나이
	 * @throws IllegalArgumentException null 혹은 잘못된 형식의 날짜를 입력한 경우 예외 발생
	 */
	public static int getAge(String birthDate, String refDate) {
		if (StringUtils.isEmpty(birthDate) || StringUtils.isEmpty(refDate)) {
			throw new IllegalArgumentException("birthDate or refDate parameter is empty.");
		} else if (birthDate.length() < 4 || refDate.length() < 4) {
			throw new IllegalArgumentException("birthDate or refDate parameter length is too short (must longer than 3).");
		} else if (!StringUtils.isNumeric(birthDate) || !StringUtils.isNumeric(refDate)) {
			throw new IllegalArgumentException("birthDate or refDate parameter is not numeric data.");
		}
		if (birthDate.length() == 4) {
			return Integer.parseInt(refDate) - Integer.parseInt(birthDate) + 1;
		} else {
			return getAge(DateUtils.toDate(birthDate), DateUtils.toDate(refDate));
		}
	}

	/**
	 * 생년월일과 기준 일자를 비교하여 한국식 나이를 반환한다.
	 * 
	 * Date birthDate = DateUtils.toDate("20120101");
	 * AgeUtils.getAge(birthDate, DateUtils.toDate("20120201")); = 1
	 * AgeUtils.getAge(birthDate, DateUtils.toDate("20130101")); = 2
	 * 
	 * @param birthDate 생년월일 (yyyyMMdd 형식)
	 * @param refDate 기준일자 (yyyyMMdd 형식)
	 * @return 한국식 나이
	 * @throws IllegalArgumentException null 혹은 잘못된 형식의 날짜를 입력한 경우 예외 발생
	 */
	public static int getAge(Date birthDate, Date refDate) {
		if (birthDate == null || refDate == null) {
			throw new IllegalArgumentException("Invalid argument value. birthDate = '" + birthDate + "', refDate = '" + refDate + "'");
		} else {
			return new DateTime(refDate).getYear() - new DateTime(birthDate).getYear() + 1;
		}
	}

	/**
	 * 현재 일자를 기준으로 생년월일에서 만 나이를 구한다.
	 * 
	 * AgeUtils.getRealAge("19731201"); = 39 (현재 년도가 2013년인 경우)
	 * 
	 * @param birthDate 생년월일 (yyyyMMdd 형식)
	 * @return 만 나이
	 * @throws IllegalArgumentException null 혹은 잘못된 형식의 날짜를 입력한 경우 예외 발생
	 */
	public static int getRealAge(String birthDate) {
		return getRealAge(birthDate, DateUtils.getTodayString());

	}

	/**
	 * 생년월일과 기준 일자를 비교하여 만 나이를 계산한다.
	 * 
	 * AgeUtils.getRealAge("19731201", "20121125"); = 38
	 * AgeUtils.getRealAge("19731201", "20121225"); = 39
	 * 
	 * @param birthDate 생년월일 (yyyyMMdd 형식)
	 * @param refDate 기준일자 (yyyMMdd 형식)
	 * @return 만 나이
	 * @throws IllegalArgumentException null 혹은 잘못된 형식의 날짜를 입력한 경우 예외 발생
	 */
	public static int getRealAge(String birthDate, String refDate) {
		return getRealAge(DateUtils.toDate(birthDate), DateUtils.toDate(refDate));
	}

	/**
	 * 생년월일과 기준 일자를 비교하여 만 나이를 계산한다.
	 * 
	 * Date birthDate = DateUtils.toDate("20120101");
	 * AgeUtils.getRealAge(birthDate, DateUtils.toDate("20120201")); = 0
	 * AgeUtils.getRealAge(birthDate, DateUtils.toDate("20130101")); = 1
	 * 
	 * @param birthDate 생년월일 (Date 타입)
	 * @param refDate 기준일자 (Date 타입)
	 * @return 만 나이
	 * @throws IllegalArgumentException null 혹은 잘못된 형식의 날짜를 입력한 경우 예외 발생
	 */
	public static int getRealAge(Date birthDate, Date refDate) {
		if (birthDate == null || refDate == null) {
			throw new IllegalArgumentException("Invalid argument value. birthDate = '" + birthDate + "', refDate = '" + refDate + "'");
		} else {
			return Years.yearsBetween(new DateTime(birthDate), new DateTime(refDate)).getYears();
		}
	}
}



AgeUtils.java


Posted by 곽중선