훌륭한 코드는 그 자체로 최고의 설명서다. 주석을 추가하기 전에 "주석이 필요없도록 코드를 향상시킬 방법이 없을까?"하고 자문해 보라.
- 스티브 맥코넬
3-1. 코드만 봐도 알 수 있는 당연한 내용은 주석으로 작성하지 마라.
class Inventory {
// Fields (we only have one)
List<Supply> supplies = new ArrayList<>(); // The list of supplies.
// Methods
int countContaminatedSupplies() {
// TODO: check if field is already initialized (not null)
int contaminatedCounter = 0; // the counter
// No supplies => no contamination
for (Supply supply : supplies) { // begin FOR
if (supply.isContaminated()) {
contaminatedCounter++; // increment counter!
} // End IF supply is contaminated
}// End FOR
// Returns the number of contaminated supplies.
return contaminatedCounter; // Handle with care!
}
} // End of Inventory class
3-2. 많은 개발자들은 나중에 혹시 사용할지 몰라서 코드에 주석 형태로 남겨 놓는다. 그런데, 주석으로 남겨놓은 코드를 사용할 경우는 거의 없으며, 필요하면 git에서 이력을 보면 된다. 주석으로 된 코드는 지워라.
List<String> checks = Arrays.asList(
"Cabin Leak",
// "Communication", // Do we actually want to talk to Houston?
"Engine",
"Hull",
// "Rover", // We won't need it, I think...
"OxygenTank"
//"Supplies"
);
Status prepareLaunch(Commander commander) {
for (String check : checks) {
boolean shouldAbortTakeoff = commander.isFailing(check);
if (shouldAbortTakeoff) {
//System.out.println("REASON FOR ABORT: " + item);
return Status.ABORT_TAKE_OFF;
}
}
return Status.READY_FOR_TAKE_OFF;
}
3-3. 주석이 필요한 내용은 상수로 대체하면 더 좋다.
double getConversionRate(SmallDistanceUnit unit) {
if (this == unit) {
return 1; // identity conversion rate
}
if (this == CENTIMETER && unit == INCH) {
return 0.393701; // one centimeter in inch
} else {
return 2.54; // one inch in centimeters
}
}
상수(static final)로 변경해서 굳이 주석이 필요없게 하였다.
static final double INCH_IN_CENTIMETERS = 2.54;
static final double CENTIMETER_IN_INCHES = 1 / INCH_IN_CENTIMETERS;
static final int IDENTITY = 1;
double getConversionRate(SmallDistanceUnit unit) {
if (this == unit) {
return IDENTITY;
}
if (this == CENTIMETER && unit == INCH) {
return CENTIMETER_IN_INCHES;
} else {
return INCH_IN_CENTIMETERS;
}
}
3-4. 주석(설명)이 필요하면 유틸리티 성 메소드로 대체가능하다.
int getAverageTankFillingPercent() {
double sum = 0;
for (double tankFilling : tanks) {
sum += tankFilling;
}
double averageFuel = sum / tanks.size();
// round to integer percent
return Math.toIntExact(Math.round(averageFuel * 100));
}
아래와 같이 별도 메소드로 분리할 수 있다. IntelliJ에서는 Ctrl+Alt+M으로 빼내기.
int getAverageTankFillingPercent() {
double sum = 0;
for (double tankFilling : tanks) {
sum += tankFilling;
}
double averageFuel = sum / tanks.size();
return roundToIntegerPercent(averageFuel);
}
static int roundToIntegerPercent(double value) {
return Math.toIntExact(Math.round(value * 100));
}
3-5. 구현 결정에 대해 설명하라.
아래는 binarySearch를 사용하는데 왜 사용하는지 설명이 없다.
private List<Supply> list = new ArrayList<>();
void add(Supply supply) {
list.add(supply);
Collections.sort(list);
}
boolean isInStock(String name) {
// fast implementation
return Collections.binarySearch(list, new Supply(name)) != -1;
}
빠른 구현(fast implementaion)으로는 설명이 부족하다. 사용사례(use case)와 우려사항, 해법, 트레이드 오프나 비용까지 명시적으로 설명이 필요함
// Keep this list sorted. See isInStock().
private List<Supply> list = new ArrayList<>();
void add(Supply supply) {
list.add(supply);
Collections.sort(list);
}
boolean isInStock(String name) {
/*
* In the context of checking availability of supplies by name,
* facing severe performance issues with >1000 supplies
* we decided to use the binary search algorithm
* to achieve item retrieval within 1 second,
* accepting that we must keep the supplies sorted.
*/
return Collections.binarySearch(list, new Supply(name)) != -1;
}
3-6. 예제로 설명하기
/**
* The code universally identifies a supply.
*
* It follows a strict format, beginning with an S (for supply), followed
* by a five digit inventory number. Next comes a backslash that
* separates the country code from the preceding inventory number. This
* country code must be exactly two capital letters standing for one of
* the participating nations (US, EU, RU, CN). After that follows a dot
* and the actual name of the supply in lowercase letters.
*/
static final Pattern CODE =
Pattern.compile("^S\\d{5}\\\\(US|EU|RU|CN)\\.[a-z]+$");
정규식 사용에 대한 설명이 있으나, 사용 예시를 유효한 예시, 유효하지 않은 예시를 추가하면 훨씬 이해도가 높아진다.
/**
* The expression universally identifies a supply code.
*
* Format: "S<inventory-number>\<COUNTRY-CODE>.<name>"
*
* Valid examples: "S12345\US.pasta", "S08342\CN.wrench",
* "S88888\EU.laptop", "S12233\RU.brush"
*
* Invalid examples:
* "R12345\RU.fuel" (Resource, not supply)
* "S1234\US.light" (Need five digits)
* "S01234\AI.coconut" (Wrong country code. Use US, EU, RU, or CN)
* " S88888\EU.laptop " (Trailing whitespaces)
*/
static final Pattern SUPPLY_CODE =
Pattern.compile("^S\\d{5}\\\\(US|EU|RU|CN)\\.[a-z]+$");
3-7. 패키지를 JavaDoc으로 구조화하기
주석에 버전관리도구만 봐도 알 수 있는 버전정보, 단순 클래스 나열 등 불필요 정보보다는 클래스로 무엇을 할 수 있는지, 주요 사용 사례 등을 추가하라.
3-8. 클래스와 인터페이스를 JavaDoc으로 구조화하기
짧고 간결한 요약으로 시작하고, 메서드 서명을 되풀이 하기보다는 예제나 용법을 추가하라.
3-9. 메서드를 JavaDoc으로 구조화하기
파라미터에 대한 설명과 함께 null일 경우 동작 등을 명시하라.
3-10. 생성자를 JavaDoc을 구조화하기
생성자별 용도를 설명해줘야 한다.