우테코 회고록/프리코스 회고록

[프리코스 2주차] 미션 수행 기록

kim chan jin 2023. 10. 28. 17:11

1주차 때 우테코 6기 단톡방의 한 분께서 코드리뷰를 해주셨습니다.

https://github.com/woowacourse-precourse/java-baseball-6/pull/212/files

 

[숫자 야구] 김찬진 미션 제출합니다. by KimChanJin97 · Pull Request #212 · woowacourse-precourse/java-baseball-6

 

github.com

 

코드 리뷰의 내용은 다음과 같습니다.

1. 중복되는 상수를 클래스에 일일이 선언하는 것 대신 상수를 가진 인터페이스를 상속하도록 했는데, 과연 올바른 방법일까?

2. 개행문자, 주석 등 코드 가독성을 떨어뜨리는 것들은 푸쉬하기 전에 지우자

3. 메서드가 너무 크면 추출하자

 

코드 리뷰의 내용을 바탕으로 이번 2주차 미션을 진행했습니다. 코드 리뷰를 받고 주의를 기울였던 부분입니다.

1. 2주차 미션에는 공통되는 상수가 없었기 때문에 이번엔 인터페이스의 메서드 상속으로 사용했습니다.

2. 주석을 지우고 메서드 이름과 파라미터의 이름을 명확히 작성해서 코드 가독성을 높였습니다.

3. 메서드를 추출하여 코드 가독성을 높였습니다.

 

추가적으로 1주차 미션 때 했던 실수들을 반복하지 않는 것에 집중했습니다.

4. 요구사항 잘 읽기 -> 테스트 실패 등의 에러를 만나지 않았습니다.

5. 도메인(모델)에서 뷰를 의존하지 않기 -> 의도하진 않았지만 의존도가 낮아졌습니다! Low Coupling!

 

 

아래는 이번 2주차 미션을 진행하며 가졌던 의문과 고민했던 내용들입니다.

 

의문1

Validator 인터페이스 validate() 메서드에 선언을 했을 때,
그 구현체들 CarNameValidator, CarPositionValidator 등은 validate() 메서드를 상속해야만 했다.
근데 CarName VO에서 CarNameValidator 를 정적으로 갖고,
CarPosition VO 에서 CarPositionValidator 를 정적으로 갖도록 생각했는데,
validate() 메서드에 static 선언을 할 수 없었다. 이유가 무엇일까?

인터페이스는 디폴트 메서드가 아닌 이상 모든 메서드는 추상 메서드이다. 인터페이스를 구현하는 클래스에서 추상 메서드들을 반드시 오버라이딩해야 한다.

정적 메서드는 인스턴스 메서드이며, 클래스 수준의 메서드이다. 즉, 정적 메서드는 특정 클래스에 속하고 상속되지 않는다.

즉, 인터페이스의 추상 메서드와 정적 메서드는 양립할 수 없는 개념이다.

올바른 접근은 CarName VO 에 CarNameValidator 를 정적 필드로 갖고, CarPosition VO 에 CarPositionValidator 정적 필드를 갖는 것이다! 내가 사용할 validate(T t) 는 Validator 의 구현체를 통해서 사용할 것이기 때문에 validate() 메서드를 정적으로 만들 필요는 없는 것이었다!

 

 

 

의문2

interface Validator<T> 와 같이 지네릭 인터페이스를 사용했다.
Validator 를 상속 및 구현하는 CarNameValidator 에서 다음과 같이 매개변수화된 타입을 지정해줬다.
class CarNameValidator implement Validator<String> 등이다.
CarName VO 에서 CarNameValidator 정적 필드를 가지려고 하는 상황일 때,

1번. private static final Validator<String> validator = new CarNameValidator(); 
2번. private static final CarNameValidator validator = new CarNameValidator(); 

둘 중 무엇이 맞을까?

1번은 다형성을 위한 코드라고 하지만, 결국 다형성을 실현하려면 OCP 를 위반해야 한다.

2번은 애초에 구현체에 의존하기 때문에 DIP 를 위반한다.

결국 이를 위해서는 외부에 Config 클래스를 만들고 의존관계를 주입하는 과정이 필요할 것 같다!

 

 

 

의문3

완전한 은닉을 위해 getter, setter 를 쓰지 않고 매번 새로운 객체를 만들어내는 비용이 더 적을까?
아니면 getter, setter 를 써서 필드에 접근하고 조작하는 비용이 더 적을까?
다시 말해, 완전한 은닉의 이득이 더 클까? 아니면 getter, setter 를 써서 개발 시간과 노력을 줄이는 이득이 더 클까?

완벽한 은닉하려다 코드가 더 복잡해지고 있다. 일단 getter, setter를 허락하자.

 

 

 

의문4

Game 에서 Car 이름(문자열 "pobi,crong,pororo")을 받고 일일이 쪼개서 Cars 를 만드는 로직이 들어가는 것이 맞을까? 코드의 가독성이 떨어지는 것 아닐까?

Cars VO 만들자

 

 

 

의문5

Game - Cars - Car - CarName, CarPosition 의 의존 모습이다.
Game 에서 Cars VO를 갖는데 getCars 하면 Cars 를 가져오는게 맞을까? Cars 의 실제값 List<Car> 를 가져오는게 맞을까?

하나의 관계 이상의 의존도를 갖게 된다면 점 연산자을 여러 번 쓰게 되는 것이다. 하나의 관계를 넘는 의존도를 갖게 하지 말자!

 

 

 

의문6

의존도를 낮추는 일반적인 방법은 무엇일까?

SRP 때문인 것 같다. Model, View, Controller 별로 적절한 책임과 역할을 분리했기 때문!

SRP, OCP, LSP, ISP, DIP 중 제대로 지키고 효과를 본 것은 SRP 인 것 같다. SRP 만 지켜도 의존도가 확 떨어진 것 같다.

OCP, LSP, DIP 를 지키려면 외부에서 의존관계를 주입해주는 코드를 짜거나 프레임워크의 힘을 빌려야 할 것 같다.

 

 

 

의문7

CarName VO 와 CarName VO 인스턴스가 생성될 때마다 유효성을 검증하는 CarNameValidator 클래스가 존재하는 상황일 때,
CarNameValidator 는 싱글턴으로 구현하는게 좋을까? 아니면 그냥 일반적인 클래스로 구현하는게 좋을까?

만약 CarName 클래스에서 단 하나의 CarNameValidator 인스턴스를 얻어서 사용한다면 (즉, 싱글턴으로 사용한다면) 불필요한 객체 생성을 줄일 수 있다고 생각한다. 근데 과연 싱글턴으로 하는게 맞을까?

 

싱글턴은 리소스를 많이 차지하는 무거운 클래스일 때 적합하다. CarNameValidator 는 CarName 을 위해서만 필요하고, 유효성 검증은 큰 리소스를 필요로 하지 않기 때문에 싱글턴은 적합하지 않은 것 같다.

 

NameValidator 클래스를 제거하고 그 내부 로직을 Name 클래스 내부에 구현하는 건 어떨까?

만약 유효성 검증하는 메서드가 많다면 필요한 상수들과 검증 로직들이 너무 많아서 가독성이 떨어질 것 같다.

 

Name 클래스에서 private static 접근제어자와 CarNameValidator 생성자를 사용하여 인스턴스를 만들어 사용한다면 (즉, 일반적인 클래스로 사용한다면) 괜찮을 것 같다. 

 

 

 

의문8

메서드를 테스트하기 위해 getter, setter 를 만드는 것이 좋을까?

차라리 새로운 생성자를 더 하나 만들자. 필드를 드러낼 바에 객체를 만들자!

생성자를 새로 더 만드는 것이 은닉화에 더 다가갈 수 있는 방법이 맞을까?