본문 바로가기

우테코

우테코 4주차 - 3주차 공통 피드백 검토

3주차 공통 피드백이 왔다.

 

한번씩 살펴보자


1. 함수(메서드) 라인에 대한 기준

: 함수 15라인의 기준은 main에도 해당한다.

 

2. 비즈니스 로직과 ui로직을 분리하기

: 객체는 하나의 책임을 지녀야 한다.

: 비즈니스 로직과 ui로직을 한 클래스가 담당하지 않도록 하자

 

3. 연관성이 있는 상수는 static final 대신 enum을 활용

public enum Rank {
    FIRST(6, 2_000_000_000),
    SECOND(5, 30_000_000),
    THIRD(5, 1_500_000),
    FOURTH(4, 50_000),
    FIFTH(3, 5_000),
    MISS(0, 0);

    private int countOfMatch;
    private int winningMoney;

    private Rank(int countOfMatch, int winningMoney) {
        this.countOfMatch = countOfMatch;
        this.winningMoney = winningMoney;
    }
}

 

4. 인스턴스 변수의 접근 제어자는 private로 구현

5. final을 통해 값의 변경을 미리 막자

 

6. 무분별한 getter보다 객체에 메시지를 보내 객체가 로직을 수행하도록 하자

 

 

객체지향 프로그래밍은 객체가 스스로 일을 하도록 하는 프로그래밍이다.

모든 멤버변수에 getter를 생성하고 값을 꺼내 외부에서 로직을 수행한다면

객체가 로직을 갖고 있거나 메시지를 주고받는 형태가 아니게 된다.

 

ex) 자동차 경주에서 우승자를 찾는 findWinner 메소드

    public List<String> findWinners() {
        final int maximum = cars.stream()
                  .map(car -> car.getPosition())	
                  .max(Integer::compareTo)
                  .get();
           
        return cars.stream()
                .filter(car -> car.getPosition() == maximum)
                .map(Car::getName)
                .collect(Collectors.toList());
    } 
         ...

 

- getPosition을 통해 position 상태값을 직접꺼내서 비교하고 있음

- private인 멤버변수끼리 비교하는 로직이다.

 

=> 객체 끼리 메시지를 주고 받을 수 있게 리팩토링

 

public class Car implements Comparable<Car> {
         ...
    public boolean isSamePosition(Car other) {
        return other.position == this.position;
 	}
 	
    @Override
    public int compareTo(Car other) {
        return this.position - other.position;
    }
         ...
}

public class Cars {
         ...
    public List<String> findWinners() {
        final Car maxPositionCar = findMaxPositionCar();
        return findSamePositionCars(maxPositionCar);
    }
    
    private Car findMaxPositionCar() {
        Car maxPositionCar = cars.stream()
            .max(Car::compareTo)
            .orElseThrow(() -> new IllegalArgumentException("차량 리스트가 비었습니다."));
    }

    private List<String> findSamePositionCar(Car maxPositionCar) {
        return cars.stream()
            .filter(maxPositionCar::isSamePosition)
            .map(Car::getName)
            .collect(Collectors.toList());
    }
}

 

=> getPosition이 없는 상황에서 각 compareTo를 구현하여 자동차끼리 비교가 가능하게 해주었다.

=> isSamePosition 또한 같은 position값을 비교할 수 있게 해주었다.

 

즉, 상태를 가지는 객체를 추가했다면 객체가 로직을 구현하도록 해야한다.

데이터를 꺼내지 말고 메시지를 던지도록 구조를 바꾸자

=> 객체가 일을 하도록 객체답게 사용하자

 

#getter를 사용할 때 외부에서 변경하지 못하도록 => unmodifiableList

public List<Car> getCars() {
		return cars;
	} (x)

public List<Car> getCars() {
		return Collections.unmodifiableList(cars);
	} (o)

 

 

7. 필드의 수를 줄이기 위해 노력하자

- 인스턴스 변수가 많은 것은 객체의 복잡도와 버그 발생가능성을 높인다.

 

8. 예외에 대한 케이스도 테스트 하자

9. 테스트 코드도 계속 리팩터링해나아가자. 

 

10. 테스트를 위한 코드는 구현 코드에서 분리되어야 한다.

테스트를 위한 편의 메서드를 구현 코드에 구현하지 마라.

테스트를 통과하기 위해 구현 코드를 변경하거나 테스트에서만 사용되는 로직을 만들지 않는다.

 

  • 테스트를 위해 접근 제어자를 바꾸는 경우
  • 테스트 코드에서만 사용되는 메서드

 

11. 메서드 시그니처를 수정하여 테스트하기 좋은 메서드로 만들기

: 메서드 시그니처를 수정하는 것 => 테스트에 적합한 방향으로 바꿀 수 있음

: 단위 테스트에 있어 매개변수나 변수명 하나도 TDD를 염두해둘 필요가 있음

 


느낀 점

 

첫째, "TDD를 위한" 기능 코드 변경을 주의하자

: 사실 테스트 작성을 위해 prviate 접근 제어자를 가끔 public으로 풀어주었었다.

: 그러나 TDD를 위해서 기능 코드에 변경이 이루어지면 안된다는 사실을 알게 되었다.

: TDD는 1번 시행하고 그만이 아니라, 기능을 하나하나 완성해가면서 반복적으로 테스트되어야 하는 것이다.

 

둘째, 객체간 메시지를 주고받게 하자.

: 아직 OOP에 익숙하지 않아 계속해서 데이터 단위로 문제를 바라보게 되는 것 같다.

: 각 객체는 데이터 저장소가 아니다. 특정 로직을 수행하는 일을 하는 단위이다.

: 그만큼 단순 창고이상의 의미를 부여하기 위해 노력하자.

 

셋째,  메소드 시그니처 변형을 통한 TDD를 진행해보자

: 그동안 TDD를 제대로 신경쓰지 못했던 이유 중 하나는 구현을 하면서 점차 테스트를 진행하기 애매한 클래스들이 나왔기 때문이다. 그런 클래스에 대해서도 TDD에 알맞은 형태를 구현하도록 계속해서 신경 쓸 필요(의식할 필요)를 느꼈다.