하나의 객체가 많은 책임을 맡지 않도록 하자
public class InputView {
// ...
private static final Scanner scanner = new Scanner(System.in);
public static List<Car> getCarNames() {
System.out.println(CAR_NAMES_MESSAGE);
return Converter.toCarList(scanner.next());
}
// ...
}
사용자에게 입력을 받는 InputView 클래스이다. 자동차의 이름을 입력받아 Converter 클래스를 통해 Car 객체의 리스트를 반환하는 메서드를 가지고 있다. 그리고 Converter 클래스 안에는 자동차 이름에 대한 검증 로직이 Validator 클래스를 통해 들어 있다. 즉 InputView 클래스 안에서 입력을 받은 뒤 입력에 대한 검증, Car 객체로의 변환까지 이루어지고 있는 것이다.
이렇게 많은 일을 하는 친구는 연관된 한가지 요구사항이 바뀌더라도 내부 구현을 변경해야 할 일이 잦아진답니다.
InputView라는 객체가 정말 해야하는 역할과 행위가 뭔지에 대해서 생각해보면 좋을 것 같아요!
지금처럼 모든 검증을 InputView단에서 해서 완벽한 Car 객체를 만들 수 있어요. 그렇지만 만일 Car 객체를 InputView 객체가 아닌 다른 곳에서 사용하게 된다면? 이 코멘트에서 남겼듯 의도치 않은 Car 객체가 생성될 수 있겠죠? 그렇기에 InputView의 책임을 조금 줄이고 Car 객체가 Car 객체만으로도 온전하게 생성될 수 있도록 처리해보면 단단한 프로그램이 될 거 같아서 리뷰드린 내용이에요!
이렇게 피드백을 받고 Car의 이름에 대한 검증은 Car 스스로가 가지도록 하고 Converter 메서드도 View에서 분리하여 InputView 클래스는 사용자에게 보일 화면과 입력만 받는 책임만 맡도록 역할을 한정시켰다.
InputView
public static String getCarNames() {
System.out.println(CAR_NAMES_MESSAGE);
return scanner.nextLine();
}
Car
public class Car {
// ...
private final String name;
private int position;
private Car(String name, int position) {
validateCarName(name);
this.name = name;
this.position = position;
}
private void validateCarName(String carName) {
if (carName.isEmpty()) {
throw new IllegalArgumentException(EMPTY_CAR_NAME_ERROR_MESSAGE);
}
if (carName.length() > NAME_LENGTH_LIMIT) {
throw new IllegalArgumentException(INVALID_CAR_NAME_ERROR_MESSAGE);
}
}
// ...
}
먼저 사용되는 private 메서드를 먼저 적어 가독성을 높이자
public void excute() {
step1();
step2();
}
private void step1() {
}
private void step2() {
}
각 객체의 입장에서 생각해서 유의미한 네이밍을 하자
public void move(int randomNumber) {
if (randomNumber >= STANDARD_OF_MOVING) {
position++;
}
}
해당 메서드는 Car 객체가 움직이기 위해 랜덤으로 생성된 수를 받아 그 수가 일정 기준 이상일 때 움직인다는 메서드다. 랜덤 한 수는 다른 클래스에서 생성해서 넣어준다. 그런데 Car의 입장에선 들어오는 매개변수가 랜덤으로 생성되든 고정되어 생성되든 상관이 없다. 저렇게 randomNumber로 네이밍을 하면 난수만 받아야 하는 메서드로 규정되는 것이다. 따라서 randomNumber가 아닌 좀 더 유연하고 Car의 입장만 고려한 네이밍을 하는 것이 좋다.
추상화된 인터페이스에 의존하자
private final RandomUtilImpl randomUtil = new RandomUtilImpl();
private final RandomUtil randomUtil = new RandomUtilImpl();
위의 두 선언문은 난수를 생성하기 위한 클래스를 필드로 가지기 위해 선언한 것이다. 위쪽은 구현체에 의존하고 있고 아래쪽은 추상화된 인터페이스에 의존하고 있다. 타입을 인터페이스로 추상화함으로써 난수 생성 구현체가 바뀌더라도 코드 변경을 줄일 수 있고 다양한 의존관계를 맺을 수 있게 된다.
지금 구현에 대해서 이야기해보자면, RandomUtilImpl이라는 구현체의 타입으로 객체를 할당하기보다는, RandomUtil이라는 인터페이스로 할당하여 randomUtil이라는 변수가 인터페이스로써 사용될 수 있도록 하면 좋아요. 추상화된 인터페이스 타입을 통해 변수를 더 유연하게 사용할 수 있기 때문이에요.
정적 팩터리 메서드
테스트하고자 하는 객체를 명확히 판별하고 해당 객체의 테스트 클래스에 테스트 코드를 작성하자.
Car에 대한 테스트는 CarTest에, CarRepository의 테스트는 CarRepositoryTest에 작성함으로써 코드를 읽는 사람이 쉽게 파악할 수 있도록 해야 한다.
모든 원시 값을 포장하라
단순 반복문 .forEach()에 대해서는 일반 반복문을 쓰는 것이 좋을 수도 있다.
스트림에 대해 알고 난 뒤부터는 단순 반복문이라도 stream().forEach()를 이용해서 코드를 많이 작성해왔다. 하지만 이렇게 했을 경우 다음과 같은 문제가 있다.
- 성능이 떨어진다. 정통적인 for-loop를 사용할 때보다 오버헤드가 심각하게 발생한다.
- 오히려 가독성이 떨어질 수도 있다.
- 디버깅하기가 어려워진다.
자세한 내용은 글을 참고
'우아한테크코스' 카테고리의 다른 글
Level1 체스 미션 피드백 정리 (0) | 2022.04.10 |
---|---|
수업 따라하기 (Gradle 프로젝트에 Docker로 mysql 접속) (0) | 2022.03.30 |
Level1 블랙잭 미션 피드백 정리 (0) | 2022.03.21 |
Level1 로또 미션 피드백 정리 (0) | 2022.03.04 |
우아한테크코스4기 최종 합격 회고 (3) | 2022.01.21 |