[자바 코딩의 기술] 3. 주석 잘 활용하기

봄도둑·2022년 4월 29일
1

자바코딩의기술

목록 보기
3/4

3개월차 신입 비전공 개발자가 읽기 좋은 코드를 만들어보고자 공부하는 내용입니다. 부족하거나 새롭게 공부해보면 좋을 것 같은 추천, 글에서 발견된 문제에 대한 이야기는 언제든 환영합니다!

1. 지나치게 많은 주석 없애기

  • 대부분의 주석은 코드가 전하고자 하는 내용을 반복 → 불필요함
  • 코드 한 줄만 알면 알 수 있는 주석들은 지워주는 게 좋음
  • 클래스 구조(필드, 메소드, 반환)에 관련된 주석도 불필요 → 자바 코드 규칙에서 정한 클래스 구조를 따라간다면 당연히 알 수 있는 내용
  • 주석을 남길 때 첫번째로 생각하면 좋을 것 : 코드만 보아서는 드러나지 않는 정보를 주석으로 달아주자!

2. 주석 처리된 코드 제거

  • 주석 처리된 코드는 심각한 문제 → 일반적인 주석과 코드와 달리 명확하게 설명하지 않음
  • 훗날 다시 사용할까봐, 코드를 잃어버리고 싶지 않은 마음에 코드를 주석처리함.
  • 오늘날 버전 관리 도구(GIT etc...)를 통해 프로젝트가 관리됨 → 즉, 코드에 대한 변경 이력은 이미 다 남아 있음. 사용하지 않을 코드를 굳이 주석 처리해서 가지고 있을 필요가 전혀 없음!
  • 주석 처리된 코드는 그냥 지우면 해결!

3. 주석을 상수로 대체

public enum weightEnum {
    KG,
    G;

    double getConversionRate(weightEnum unit) {
        if (this == unit) {
            return 1; //같은 단위
        }

        if (this == KG && unit == G) {
            return 1000; //1kg 당 g
        } else {
            return 0.001; //1g 당 kg
        }
    }
}
  • 위의 코드에서 주석이 없다면 getConversionRate 에서 return 되는 수들은 매직 넘버가 됨
  • 위의 코드에서 주석은 이러한 매직 넘버에 의미를 부여하는 설명을 해주고 있음
  • 이러한 주석 처리는 매직 넘버에 의미를 부여하지만 주석이 없다면 이러한 매직 넘버의 의미를 이해할 수 없음 → 매직 넘버를 상수로 처리하는 것을 통해 이러한 문제를 해결할 수 있음
  • 아래는 주석 매직 넘버를 수정한 코드
public enum weightEnum {
    KG,
    G;

    static final int SAME_CONVERSION_RATE = 1;
    static final int KG_IN_G = 1000;
    static final double G_IN_KG = 0.001;

    double getConversionRate(weightEnum unit) {
        if (this == unit) {
            return SAME_CONVERSION_RATE;
        }

        if (this == KG && unit == G) {
            return KG_IN_G;
        } else {
            return G_IN_KG;
        }
    }
}
  • 주석을 제거하고 상수명으로 합치는 작업 진행 → 주석을 코드에 임베딩한 결과로 주석이 실제 코드로 변함
  • 주석은 시간이 지나도 변하지 않을 위험성을 내포 → 즉, 코드가 바뀌어도 주석은 무시하거나 설명 없이 새 로직이 추가될 수 있음

4. 주석을 유틸리티 메소드로 변경

public class FuelSystem {

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

    int getAverageTankFillingPercent() {
        double sum = 0;
        for (double tankFilling : tanks) {
            sum += tankFilling;
        }
        double averageFuel = sum/tanks.size();
				//백분율로 반올림
        return Math.toIntExact(Math.round(averageFuel*100));
    }
}
  • 주석을 상수로 변환하는 것은 주석을 처리하는 여러 방법 중 하나 → 모든 값이 고정 값일 수 는 없기 때문에 코드가 더 복잡해질 가능성이 있음
  • 위의 코드는 평균을 계산하고 변환하기 전에 값을 조작하는 코드 → 마지막 return 할 때 무엇을 하는지는 주석을 통해 알아차림
  • 그렇다면 주석 없이 간단하게 알아차릴 수 있는 방법은? → 혹시 return 해줄 때 변수명을 직관적으로 붙여준다면?
int getAverageTankFillingPercent() {
    double sum = 0;
    for (double tankFilling : tanks) {
        sum += tankFilling;
    }
    double averageFuel = sum/tanks.size();
    int roundedToPercent = Math.toIntExact(Math.round(averageFuel*100));
    return roundedToPercent;
}
  • 주석은 제거되면서 코드로 설명이 가능해짐 → 그러나 메소드에 추가한 변수가 불필요함
  • 그렇다면 이 녀석을 메소드로 따로 빼내서 처리 해보자
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));
}
  • 유틸리티 메소드를 사용할 때의 장점
    • 코드가 무엇을 하는지 이름만 가지고 알 수 있음 → 불필요한 주석을 제거할 수 있음
    • getAverageTankFillingPercent() 메소드에 직접 로직을 수행하는 코드를 쓰지 않아도 됨 → 메소드가 짧아짐. 각각의 메소드가 짧아지니 코드를 이해하기 쉬워짐
    • 다른 메소드에서 새 메소드를 재사용할 수 있음. 지금 당장은 쓰지 않더라도 코드가 조금씩 모듈화됨
    • 메소드의 계층 구조가 생김. 최상위 메소드 getAverageTankFillingPercent() 가 하위 메소드 roundToIntegerPercent() 를 호출 → 상위 메소드의 이해도 개선
  • 이상적으로 각 메소드는 비슷한 추상화 정도를 갖는 명령문의 나열 → 새로운 유틸리티 메소드를 통해 텍스트의 줄만 제거되는 것이 아니라 코드가 더 모듈화되고 추상화 수준도 균형을 이룸

5. 구현 결정 설명하기

public class Inventory {

    private List<Supply> list = new ArrayList<>();

    void add(Supply supply) {
        list.add(supply);
        Collections.sort(list);
    }

    boolean isInStock() {
        //빠른 구현
        return Collections.binarySearch(list, new Supply(name)) != -1;
    }
}
  • 위의 코드에서 개발자가 왜 binarySearch를 사용하기로 결정했는지에 대한 설명 부족
  • //빠른 구현 이라는 코드가 binarySearch의 사용 이유를 설명해주지 않고 있음
  • 우리가 알고 싶은 것은
    • 왜 빠른가?
    • 코드는 왜 빨라야 하는가?
    • binarySearch 메소드가 빠른가?
    • 빠른 해법의 비용이나 트레이드 오프는 무엇인가?
  • 그럼 알고 싶은 정보를 기반으로 주석을 추가해보자
public class Inventory {
    // 리스트를 정렬된 채로 유지한다. isInStock()을 참고 한다.
    private List<Supply> list = new ArrayList<>();

    void add(Supply supply) {
        list.add(supply);
        Collections.sort(list);
    }

    boolean isInStock() {
        /*
        * 재고가 남았는지 재고명으로 확인해야 한다면,
        * 재고가 1000개 이상일 때 심각한 성능 이슈에 직면한다.
        * 1초안에 항목을 추출하기 위해
        * 비록 재고를 정렬된 채로 유지해야 하지만
        * 이진 검색 알고리즘을 사용하기로 결정했다.
        */
        return Collections.binarySearch(list, new Supply(name)) != -1;
    }
}
  • 사용 사례, 우려사항, 해법, 지불해야할 트레이드 오프나 비용까지 명시 → 템플릿을 기준으로 주석을 작성
  • 여기에 사용된 템플릿은
    • [사용 사례]의 맥락에서
      직면하는 [우려 사항]과
      우리가 선택한 [해법]으로
      얻게 되는 [품질]과
      받아들여야 하는 [단점]
  • 주석에 관련되어서 사전에 정의된 템플릿으로 작성하면 동료 개발자들의 주석을 보고 코드에 대한 이해도를 높일 수 있음
  • 템플릿은 꼭 이대로 따를 필요 없이 팀 내에서 회의를 통해 결정

6. 예제로 설명하기

public class Supply {
    
    /*
    * 아래 코드는 어디서든 재고를 식별한다
    * 
    * S로 시작해 숫자 다섯자리 재고 번호가 나오고
    * 뒤이어 앞의 재고 번호와 구분하기 위한 역 슬래시가 나오고
    * 국가 코드가 나오는 엄격한 형식을 따른다.
    * 국가 코드는 반드시 참여 국가인 (US, EU, RU, CN) 중
    * 하나를 뜻하는 대문자 두 개여야 한다.
    * 이어서 마침표와 실제 재고명이 소문자로 나온다.
    * */
    static final Pattern CODE = Pattern.compile("^S\\d{5}\\\\(US|EU|RU|CN)\\.[a-z]+$");
}
  • 정규식 코드는 복잡한 만큼 더 쉽게 이해할 수 있도록 설명해주어야 함
  • CODE 라는 이름으로 어디에 쓰이는지 알 수 없지만 주석이 길게 딸려 있음
  • 상세한 주석을 달아 놓은 것은 좋지만 정규식을 읽을 줄 아는 개발자라면 정규식 코드 안에 들어 있는 내용을 줄줄히 풀어쓴 내용임 → 주석은 우리가 코드만 보고 알 수 없는 내용을 정리해주어야 함
  • 이러한 복잡한 정규식은 예제를 주석으로 달아주자
public class Supply {

    /*
    * 아래 코드는 어디서든 재고를 식별한다
    * 
    * 형식 : "S<inventory-number>\<COUNTRY-CODE>.<name>"
    
    * 유요한 예 : "S12345\US.pasta", "S08321\CN.wrench"
    * 
    * 유효하지 않은 예:
    * "R12345\RU.fuel" :  (재고가 아닌 자원)
    * "S1234\US.light" : (숫자가 5개여야 함)
    * "S01234\KO.coconut" : (잘못된 국가 코드. US, EU, RU, CN 중 하나를 사용해야 함)
    * "S88888\EU.coffee " : (마지막에 여백이 있음)
    * */
    static final Pattern SUPPLY_CODE = Pattern.compile("^S\\d{5}\\\\(US|EU|RU|CN)\\.[a-z]+$");
}
  • 예제를 통해 정규식의 문법을 주석을 통해 설명해주지 않아도 됨
  • 물론 모든 경우를 예제를 통해 다루는 주석은 좋지 않을 뿐더러 어려움 → 참조할만한 90%정도의 예제만으로도 충분하며 이해하기 훨씬 편함. 예제를 단위 테스트로 추가해서 사용해도 좋음
  • 추가적으로 Pattern에서 이름을 SUPPLY_CODE로 바꿔줌으로써 의미 있는 변수명을 사용하도록 수정
profile
배워서 내일을 위해 쓰자

0개의 댓글