본문 바로가기

공부/JAVA 클린코드

Chapter2-코드스타일 레벨업

728x90

Chapter2

2.1 매직 넘버를 상수로 대체

  • 매직넘버: 표면상 의미가 없는 숫자이지만 프로그램의 동작을 제어
  • 매직 넘버를 static final 대문자로 만들어준다.
  • 문제 코드
class CruiseControl {

   void setPreset(int speedPreset) {
        if (speedPreset == 2) { //매직넘버
            setTargetSpeedKmh(16944);
        } else if (speedPreset == 1) { //매직넘버
            setTargetSpeedKmh(7667); 
        } else if (speedPreset == 0) { //매직넘버
            setTargetSpeedKmh(0);
        }
    }
   void setTargetSpeedKmh(double speed) {
        targetSpeedKmh = speed;
    }
}
  • 개선 코드
class CruiseControl {

    static final int STOP_PRESET = 0;
    static final int PLANETARY_SPEED_PRESET = 1;
    static final int CRUISE_SPEED_PRESET = 2;

    static final double CRUISE_SPEED_KMH = 16944;
    static final double PLANETARY_SPEED_KMH = 7667;
    static final double STOP_SPEED_KMH = 0;

   private double targetSpeedKmh;

    void setPreset(int speedPreset) {
        if (speedPreset == CRUISE_SPEED_PRESET) { //유의미하고 이해하기 쉬운 이름으로 변경
            setTargetSpeedKmh(CRUISE_SPEED_KMH); 
        } else if (speedPreset == PLANETARY_SPEED_PRESET) {
            setTargetSpeedKmh(PLANETARY_SPEED_KMH);
        } else if (speedPreset == STOP_PRESET) {
            setTargetSpeedKmh(STOP_SPEED_KMH);
        }
    }

    void setTargetSpeedKmh(double speed) {
        targetSpeedKmh = speed;
    }
  }

2.2 정수 상수 대신 열거 형(Enum)

  • 위의 코드에서는 음수와 같은 유효하지 않는 값이 speedPreset으로 들어올 있음.
  • 자바 타입 시스템을 통해 유효하지 않은 입력값을 막는다.
  • 개선 코드
class CruiseControl {

  private double targetSpeedKmh;
  
  void setPreset(SpeedPreset speedPreset){
    
    Object.requireNonNull(speedPreset); // 유효하지 않으면 자바 컴파일러가 중지시킴.
    
    setTargetSpeedKmh(speedPreset.speedKmh); //if, else if 문이 없어져 더 간단해짐
  
  }
  
  void setTargetSpeedKmh(double speedKmh){
    targetSpeedKmh = speedKmh;
  }
 }
 
 enum SpeedPreset {
     STOP(0), PLANETARY_SPEED(7667), CRUISE_SPEED(16944);
     
     final double speedKmh;
     
     SpeedPreset(double speedKmh){
        this.speedKmh = speedKmh;
     }
}

2.3 For 루프 대신 For-Each

  • for-each는 set 처럼 인덱싱 되지 않는 컬렉션에도 동작한다.
  • 개선 코드
class LaunchChecklist {

    List<String> checks = Arrays.asList("Cabin Pressure",
                                        "Communication",
                                        "Engine");

    Status prepareForTakeoff(Commander commander) {
        for (String check : checks) {  // for 문 대신에 for-each 적용
            boolean shouldAbortTakeoff = commander.isFailing(check);
            if (shouldAbortTakeoff) {
                return Status.ABORT_TAKE_OFF;
            }
        }
        return Status.READY_FOR_TAKE_OFF;
    }
}

2.4 순회하며 컬렉션 수정하지 않기

  • while문과 Iterator 사용하면서 다음 원소가 있는지 확인하면서 제거.
  • 문제 코드
class Inventory {

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

    void disposeContaminatedSupplies() {
        for (Supply supply : supplies) {
            if (supply.isContaminated()) {
                supplies.remove(supply); //자료구조 변경시 충돌 발생, List, Queue, Set Collection은 ConcurrentModificationException 발생
            }
        }
    }
}
  • 개선 코드
class Inventory {

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

    void disposeContaminatedSupplies() {
        Iterator<Supply> iterator = supplies.iterator();
        while (iterator.hasNext()) { // hasNext를 통해 다음 원소가 남아 있는지 묻는다.
            if (iterator.next().isContaminated()) { // 그다음에 확인하고 지운다.
                iterator.remove();
            }
        }
    }
}

2.5 순회하며 계산 집약적 연산하지 않기

  • java.utio.regex.Pattern 의 Pattern.matches(regex,supply.toString()) 은 오토마톤을 만들고 컴파일을 함.
  • for 문 안에 matches를 쓰면 컴파일을 반복하면서 시간과 처리 전력을 소모함.
  • 그래서 Pattern.matches -> Pattern.compile, Pattern.matcher로 구분지어서 사용
  • 문제 코드
class Inventory {

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

    List<Supply> find(String regex) {
        List<Supply> result = new LinkedList<>();
        for (Supply supply : supplies) {
            if (Pattern.matches(regex, supply.toString())) { 
                result.add(supply);
            }
        }
        return result;
    }
}
  • 개선 코드
class Inventory {

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

    List<Supply> find(String regex) {
        List<Supply> result = new LinkedList<>();
        Pattern pattern = Pattern.compile(regex); // 단 한번만 정규식 객체를 컴파일하여 연산을 최소화
        for (Supply supply : supplies) {
            if (pattern.matcher(supply.toString()).matches()) {
                result.add(supply);
            }
        }
        return result;
    }
}

2.6 새 줄로 그루핑

  • 문제 코드
enum DistanceUnit {

    MILES, KILOMETERS;

    static final double MILE_IN_KILOMETERS = 1.60934;
    static final int IDENTITY = 1;
    static final double KILOMETER_IN_MILES = 1 / MILE_IN_KILOMETERS;

    double getConversionRate(DistanceUnit unit) {
        if (this == unit) {
            return IDENTITY;
        }
        if (this == MILES && unit == KILOMETERS) {
            return MILE_IN_KILOMETERS;
        } else {
            return KILOMETER_IN_MILES;
        }
    }
}
  • 개선 코드
enum DistanceUnit {

    MILES, KILOMETERS;

    static final int IDENTITY = 1; // 독립적인 단위라 분리

    static final double MILE_IN_KILOMETERS = 1.60934;
    static final double KILOMETER_IN_MILES = 1 / MILE_IN_KILOMETERS;

    double getConversionRate(DistanceUnit unit) {
        if (this == unit) { //같은 단위인지 확인
            return IDENTITY;
        }
        
        if (this == MILES && unit == KILOMETERS) { //변환
            return MILE_IN_KILOMETERS;
        } else {
            return KILOMETER_IN_MILES;
        }
    }
}

2.7 이어붙이기 대신 서식화

  • 문제 코드
class Mission {

    Logbook logbook;
    LocalDate start;

    void update(String author, String message) {
        LocalDate today = LocalDate.now();
        String month = String.valueOf(today.getMonthValue());
        String formattedMonth = month.length() < 2 ? "0" + month : month;
        String entry = author.toUpperCase() + ": [" + formattedMonth + "-" +
                today.getDayOfMonth() + "-" + today.getYear() + "](Day " +
                (ChronoUnit.DAYS.between(start, today) + 1) + ")> " +
                message + System.lineSeparator();
        logbook.write(entry);
    }
}

class Usage {
    static void main(String[] args) {
        new Mission().update("LInUS", "message");
    }
}
  • 개선 코드
class Mission {

    Logbook logbook;
    LocalDate start;

    void update(String author, String message) {
        final LocalDate today = LocalDate.now();
        String entry = String.format("%S: [%tm-%<te-%<tY](Day %d)> %s%n", //스트링 레이아웃 과 데이터 분리
                author, today,
                ChronoUnit.DAYS.between(start, today) + 1, message);
        logbook.write(entry);
    }
}

class Usage {
    static void main(String[] args) {
        new Mission().update("LInUS", "message");
    }
}

2.8 직접 만들지 말고 자바 API 사용하기

  • 문제 코드
class Inventory {

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

    int getQuantity(Supply supply) {
        if (supply == null) {
            throw new NullPointerException("supply must not be null");
        }

        int quantity = 0;
        for (Supply supplyInStock : supplies) {
            if (supply.equals(supplyInStock)) {
                quantity++;
            }
        }

        return quantity;

    }
}
  • 개선 코드

class Inventory {

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

    int getQuantity(Supply supply) {
        Objects.requireNonNull(supply, "supply must not be null"); //api 사용

        return Collections.frequency(supplies, supply); //api 
    }
}

'공부 > JAVA 클린코드' 카테고리의 다른 글

Chapter 1 - 우선 정리부터  (0) 2021.04.29