mermaid
나는 설계를 할 때 시각화를 하는 것을 좋아한다.
그래서 이전까지는 draw.io 를 통해 다음과 같은 방식으로 다이어그램을 그린 후 구현을 시작했다.
하지만 시스템의 구조는 항상 동일한 것은 아니다. 변할 수 있다.
그렇기에 매번 다이어그램을 갱신하는 것은 귀찮았고(?) 문서가 코드를 따라가지 못하는 상황이 왔었다.
리뷰어분은 Mermaid 사용이 어떠냐고 제안해주셨고 다음과 같이 개선할 수 있었다.
정적인 문서를 조금이라도 더 동적으로 바꾸고, 간편하게 다이어그램을 그릴 수 있다는 점에서 좋다고 생각한다!
무상태 컨트롤러
나는 보통 컨트롤러에서 가장 추상적인 도메인 객체를 상태로 갖고 있게 코드를 짠다.
즉, 다음과 같은 식이다.
public class BlackJackController {
private final InputView inputView;
private final OutputView outputView;
private final BlackjackGame blackjackGame;
...
}
이에 대해 리뷰어분이 질문해주신 것이 기억에 남는다.
컨트롤러는 상태를 가져야 할까요?
이 질문을 통해 내가 1차적으로 내린 결론은 컨트롤러는 상태를 가져도 된다는 것이다.
BlackjackController는 BlackjackGame을 알 수 밖에 없다.
즉, 어떤 방식으로 되었든 의존성은 존재해야 하며 상태로 가지는 것이 문제가 될까? 라는 생각이 들었다.
일시적 협력이 아닌 영구적 협력이고, 메소드마다 blackjackGame을 사용한다면.. 왜 문제가 될까?
이에 대해 리뷰어님은 객체가 상태를 가지는 것에 대한 문제점을 생각해보라고 지적해주셨고, 내가 내린 해답은 다음과 같았다.
나는 답변을 작성하면서 간접적으로 해답을 찾을 수 있었다.
위 코멘트처럼, 컨트롤러가 상태를 가지면 항상 정확한 동작을 예측하는 것은 불가능하다.
이건 컨트롤러뿐만 아니라, 모든 가변 객체가 가지는 문제점이다.
아직 싱글스레드이기에 가변 객체의 문제점을 직접적으로 마주한 적은 없지만,
멀티스레드 환경이라면 run 메소드의 수행 자체를 여러 스레드가 수행하기 때문에 문제가 될 것 같았다.
사실 아직 멀티스레드를 사용해본 적이 없기에 감이 완전히 오지는 않지만..
왜 멀티스레드 환경에서 불변 객체를 유용하게 사용할 수 있는지를 학습할 수 있게 되었던 계기같다.
리뷰어님은 컨트롤러의 경우 사용자의 요청 흐름을 제어하고 모델과의 상호작용을 담당하는 역할이라고 생각하기 때문에
인스턴스를 여러 개 생성할 필요가 없다고 생각하고 싱글톤으로 thread-safe하게 사용한다고 하신다!
캐싱
블랙잭 게임에서는 카드를 제공하는 객체가 존재한다.
사용자가 카드를 하나 뽑을때마다 랜덤으로 52개의 카드 중 하나를 제공해야 한다.
해당 책임을 담당하는 객체를 나는 다음과 같이 구성했었다.
public class Deck {
private final NumberGenerator numberGenerator;
private final List<Card> cards;
public Deck(NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
this.cards = new ArrayList<>();
initializeCards();
}
...
}
인스턴스가 생성될 때마다 52개의 카드를 생성한다.
이 부분을 캐싱해보는게 어떻냐고 리뷰어분이 말씀해주셨고, 다음과 같이 개선할 수 있었다.
public class Deck {
private static final List<Card> cards;
private final NumberGenerator numberGenerator;
static {
cards = makeCards();
}
...
}
인스턴스 변수가 아닌 클래스 변수로 52개의 카드를 저장함으로써, Deck 인스턴스가 생성될 때마다 카드를 만들지 않게 했다.
미션에서의 성능 차이는 체감할 수 없었지만,
이렇게 자주 사용될 수 있는 부분, 그리고 매번 재생성할 필요가 없는 부분에는 캐싱을 적용하는 것은 좋은 방법이라고 느꼈다.
상속과 재사용
재사용을 목적으로 상속을 사용해서는 안된다.
물론 Java API에서의 컬렉션 계층 구조 등은 예외 사항이 될 수도 있다. (쉽게 변하지 않는 부분이므로)
라이브러리가 아닌 변경이 잦은 애플리케이션에서는 상속은 많은 단점을 가진다. (인터페이스 상속이 아닌 구현 상속)
하지만 장점도 명확하기에 Is-a 관계이면서 변경이 아닌 확장이 잦게 발생할 수 있는 부분이라면 상속을 사용해도 된다.
즉, 코드 재사용만을 위해 상속을 사용하는 건 매우 위험하다는 이야기이다!
나는 이 부분을 미처 파악하지 못하고 블랙잭 게임 미션에서 상속을 사용하게 되었고
재사용을 위해서 상속을 사용하는 것에 대해 리뷰어님께 질문을 드렸다.
단순한 중복 제거가 목적이라면 컴포지션을 고려하거나 명확한 Is-a 관계인지를 고민하신다고 한다!
현업에서는 상속을 잘 사용하지 않는다고 하던데.. 정말 이 상황에서 상속을 사용해야 할까?를 자주 고민해봐야겠다.
'우테코 5기' 카테고리의 다른 글
[레벨 1 미션] 체스 학습 기록(2) (2) | 2023.04.06 |
---|---|
[레벨 1 미션] 체스 학습 기록(1) (2) | 2023.04.04 |
레벨 1 레벨로그 (1) | 2023.03.30 |
[레벨 1 미션] 체스 미션 회고 (1) | 2023.03.29 |
설계 관점에서 바라본 불변 객체 (1) | 2023.03.29 |