본문 바로가기
자바

슬기롭게 주석 사용하기

by 이상한나라의개발자 2023. 12. 12.

지나치게 많은 주석 없애기 

 

주석이 얼마나 중요한자 한번쯤 들어습니다. 맞는말이지만 정보(이유)를 설명할 때만 그렇습니다. 그렇지 않으면 방해만 될 뿐이죠.

아래 코드는 Inventory 클래스를 보여줍니다. 텍스트를 보면 알 수 있듯이 주석이 너무 많이 들어 있습니다.

가장 중요한 주석은 아마다 TODO: 필드가 이미 초기화 되었는지(널이 아닌지) 검증한다일텐데요

 

public class Inventory {
    
    List<Supply> supplies = new ArrayList<>();
    
    int countContaminateSupplies() {
       // TODO: 필드가 이미 초기화 되었는지(널이 아닌지) 검증한다.
       
       int contaminatedCounter = 0; // 카운터
       // 제품이 없으면 변질도 없다는 뜻이다.
        for (Supply supply : supplies) {
            if (supply.isContaminated()) { // FOR 시작 
                contaminatedCounter++; // 카운터를 증가시킨다!
            } // 제품이 변질되었으면 IF 끝
        } // FOR 끝
        
        // 변질된 제품 개수를 반환한다.
        return contaminatedCounter; // 유의해 처리한다!
    }
} // 클래스 끝

 

 

아래코드는 훨씬 더 간결합니다.

 

public class Inventory {

    List<Supply> supplies = new ArrayList<>();

    int countContaminateSupplies() {
       if ( supplies == null || supplies.isEmpty()) {
           // 제품이 없으면 오염도 없다는 뜻이다.
           return 0;
       }
        
       int contaminatedCounter = 0;
        for (Supply supply : supplies) {
            if (supply.isContaminated()) {
                contaminatedCounter++;
            }
        } 
        return contaminatedCounter;
    }
}

 

 

남은 주석은 코드만 보아서는 드러나지 않는 정보가 들어간 주석 뿐 입니다. supplies가 null 이거나 비었으면 0을 반환하는 온화한 지속 방법을 선택했습니다. 온화한 지속 방법 대신 예외를 발생시키는 방법을 선택할 수도 있었기 때문에 주석을 넣은 이유가 충분합니다.

 

 

주석 처리된 코드 제거

 

또한, 대규모 코드 기반(code base) 에는 주석 처리된 코드 영역이 항상 있습니다. 이러한 종류의 주석은 잡동사니일 뿐입니다.

대부분 프로그래머는 특정 기능이 동작하지 못하게 하려고 주석 처리합니다. 다른 측면에 더 집중해 쉽게 주석 처리해버리죠. 때로는 훗날 다시 사용할지 모를 코드를 잃어버리고 싶지 않아 주석 처리 하기도 합니다.

 

다행이 주석 처리된 코드는 처리하기 무척 쉽습니다. 그냥 지우면 됩니다.

 

주석 처리된 코드는 항상 이해도를 떨어뜨립니다.

 

public class LaunchCheckList {

    List<String> checks = List.of(
            "Cabin Pressure",
            // "Communication", // 휴스턴과 정말 통신하고 싶은가
            "Engine",
            "Fuel",
            // "Rover", // 내 생각에는 필요없는데...
            "Instruments",
            //"Power Plant"
    );
    Status prepareLaunch(Commander commander) {
        for (String check : checks) {
            boolean shouldAbortTakeoff = commander.isFailing(check);
            if (shouldAbortTakeoff) {
                //System.out.println("check = " + check);
                return Status.ABORT_TAKE_OFF;
            }
        }
        return Status.READY_FOR_TAKE_OFF;
    }


    public static void main(String[] args) {
        LaunchCheckList launchCheckList = new LaunchCheckList();
        Status status = launchCheckList.prepareLaunch(question -> question != null && question.contains("Engine"));
    }
}

 

 

주석을 상수로 대체

 

public enum SmallDistanceUnit {

    CENTIMETER,
    INCH;

    double getConversionRate(SmallDistanceUnit unit) {
        if ( this == unit ) {
            return 1; // 동등 변환율
        }

        if ( this == CENTIMETER && unit == INCH ) {
            return 0.393701; // 1센티미터당 인치
        }
        else {
            return 2.54; // 1인치당 센티미터
        }
    }

    public static void main(String[] args) {
        SmallDistanceUnit unit = SmallDistanceUnit.INCH;
        System.out.println("unit = " + unit);
        System.out.println("unit.name() = " + unit.name());
        System.out.println(unit.getConversionRate(SmallDistanceUnit.CENTIMETER));
    }
}

-- 상수로 대체

public enum SmallDistanceUnit {

    CENTIMETER,
    INCH;

    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;
        }
    }

    public static void main(String[] args) {
        SmallDistanceUnit unit = SmallDistanceUnit.INCH;
        System.out.println("unit = " + unit);
        System.out.println("unit.name() = " + unit.name());
        System.out.println(unit.getConversionRate(SmallDistanceUnit.CENTIMETER));
    }
}

 

 

위 코드에서 상수를 몇개 추가함으로써 코드가 휠씬 깔끔해 졌습니다. 상수의 장점은 이름으로 의미를 드러낸다는 점입니다. 그러니 더 이상 주석으로 설명할 필요가 없습니다. 

주석은 시간이 지나도 변하지 않을 위험성을 내포합니다. 일반적으로 프로그래머는 코드만큼 주석에 엄격하지 않습니다. 코드는 바꾸어도 주석은 무시하거나 설명서 없이 새 변환률을 추가하기도 하죠

이름 등으로 코드를 설명하면 코드 변경 시 무시할 일이 거의 없습니다. 주석을 상수나 변수, 필드, 메서드 이름으로 넣을 수 있다면 그렇게 하세요!

 

주석을 유틸리티 메서드로 대체

 

public class FuelSystem {

    List<Double> tanks = new ArrayList<>();

    int getAverageTankFillingPercent() {
        double sum = 0;
        for (double tank : tanks) {
            sum += tank;
        }
        double averageFuel = sum / tanks.size();
        // 정수 백분율로 반올림
        return Math.toIntExact(Math.round(averageFuel));
    }
}

 

위 예제는 평균을 계산하고 변환하기 전에 그 값을 조작하는 코드 입니다. 마지막 명령문에 무엇을 하려는 지 바로 알아차렸나요? 만약 그렇다면 작성자가 마지막 줄을 주석으로 설명한 덕분이죠.

 

주석을 제거 하려면 어떻게 해야 할까요? 

한가지 방법은 명명된 변수를 생성하는 것입니다.

 

코드는 명확해지고 주석은 제거됩니다.

 

int roundedToPercent = Math.toIntExac(Math.round(averageFuel));
return roundedToPercent;

 

 

하지만 메서드에 추가한 변수가 꽤 불필요해 보입니다. 바로 변환하니까요. 아래처럼 변환 합니다.

 

 

double averageFuel = sum / tanks.size();
return roundToIntegerPercent(averageFuel);

static int roundToIntegerPercent(double value) {
    return Math.toIntExac(Math.round(value * 100));
}

 

 

주석이나 변수를 추가하는 것 보다 유틸리티 메서드를 이용할 때 몇 가지 장점이 있습니다.

 

첫째, 코드가 무엇을 하는지 이름만으로 설명할 수 있이니 메서드에서 주석을 제거할 수 있습니다. 변수 이름에서 얻을 수 있는 장점과 같죠. 하지만 그것이 전부가 아닙니다.

 

둘째, 첫 번째 메서드에 줄을 추가하지 않아도 됩니다. 대신 메서드가 두 개로 늘었죠. 각 메서드가 짧아지니 각각 이해하기 더 쉽습니다.

 

셋째, 다른 메서드에서 새 메서드를 재 사용할 수 있습니다. 지금 당장은 쓰일 일이 없더라도 이러한 식으로 코드를 조금씩 모듈화하게 됩니다.

 

넷째, 메서드에 계층 구조가 생겼습니다. 최상위 메서드 getAverageTankFillingPercent() 는 하위 메서드 roundToIntegerPercent() 를 호출합니다. 이로써 상위 계층 메서드의 이해도가 개선됩니다.

 

'자바' 카테고리의 다른 글

Stream & Optional & Parallel Stream  (0) 2023.12.12
Stream  (0) 2023.12.12
순회 하면서 컬렉션 수정하지 않기  (0) 2023.12.12
SOLID  (0) 2023.12.12
의존성 ( dependency )  (0) 2023.12.12