자바/자바 코딩의 기술

자바 코딩의 기술 - 3장 ( 슬기롭게 주석 사용하기 )

끄적끄적 2022. 4. 8. 13:32

훌륭한 코드는 그 자체로 최고의 설명서다. 주석을 추가하기 전에 "주석이 필요없도록 코드를 향상시킬 방법이 없을까?"하고 자문해 보라. 
 - 스티브 맥코넬

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을 구조화하기

생성자별 용도를 설명해줘야 한다. 

반응형