Lv1 | 사다리 타기 미션 학습 로그

yeonk·2023년 3월 28일
0

우아한테크코스

목록 보기
8/18
post-thumbnail

미션 소개


이번 미션의 포인트는 TDD!






구현


처음 시작할 때 페어인 민트와 전체적인 구조를 그려보고 진행했다.
역시나 어느정도까지 설계해야 하는지 어려웠다. 설계 후에는 작은 단위의 객체부터 만드는 방식으로 진행했다.
TDD를 제대로 해본적이 없어서 너무 힘들었다.. 어느 정도까지 테스트를 할 것인가? 에 대한 고민과 이 것이 TDD가 맞는지에 대한 끊임없는 의문을 품으며 미션을 진행한 것 같다.

사다리를 랜덤하게 생성하기 때문에 테스트를 위한 별도 처리가 필요했다. 그래서 전략 패턴을 사용하여 테스트도 진행할 수 있도록 하였다.

구현 중간에 방향을 잃어서 민트와 화이트보드에 구조를 그리며 다시 재정립하는 시간을 가졌다.

1단계가 끝난 후 2단계가 시작되어 혼자 구현하게 되었다.

이번 미션에서는 정적 팩토리 메서드를 공부해보고 적용하였다.
일부에만 정적 팩토리 메서드를 사용했었는데, 리뷰어가 정적 팩토리를 일부만 적용한 점에 대해 질문을 하였다. 그래서 고민해보니 정적 팩토리 메서드의 장점들을 다른 객체들에도 적용 가능할 것이라는 생각이 들어 이후에 적용하였다.

전체 구조는 아래와 같다.

controller
	ㄴLadderGameController
    
domain
	ㄴHeight
    ㄴLadder
    ㄴLadderConnectionStatus
    ㄴLadderGame
    ㄴLine
    ㄴName
    ㄴPostion
    ㄴRewards
    ㄴUser
    ㄴUsers
    
utils
	ㄴErrorMessage
    ㄴRandomLineMaker
    
view
	ㄴInputView
    ㄴOutputView






돌아보기


질문에 대한 답변은 리뷰어 코멘트 & 공부한 내용을 바탕으로 작성함

1. 상수 선언 방법 - static final vs final

상수를 선언할 때 관례적으로 static final 을 사용한다. 그렇다면 final만으로 상수로 사용할 수는 없을까? static final로 상수를 선언하는 이유는 무엇일까?

  • static을 사용하게 되면 해당 클래스에서 사용할 데이터를 프로그램을 시작하는 순간 바로 메모리에 올려 고정시키게 된다. 상수를 인스턴스가 만들어질 때마다 새로 메모리 공간을 생성하게 되면 비효율적일 것이다.

  • public vs private: public static은 외부에서 모두 사용할 수 있기 때문에 캡슐화가 깨진다. 범용성이 크지 않다면 private을 사용한다.



2. TDD 진행 시 커밋 단위

  • 커밋 단위에 답은 없지만, 이상적인 상황에서는 매 커밋 전후로 항상 모든 테스트가 통과해야 한다고 보면 된다. 그래야 어떤 커밋으로 테스트가 실패하는지 오류 파악을 하기 쉽다.

  • 하지만 테스트 코드가 많으면 프로덕션 코드와 테스트 코드를 따로 커밋하기도 한다.
    (지금은 단위테스트만 작성하고 있지만 추후에 통합 테스트까지 작성하게 되면 테스트 코드양이 매우 많아 진다)

  • 지금은 TDD를 공부하고 있으니 테스트+코드를 하나의 커밋으로 두면 TDD로 진행을 한 건지 알기가 어렵다. 때문에 당장으로서는 테스트와 프로덕션 코드를 따로 커밋하는 걸 추천한다!



3. enum의 속성을 어떻게 출력하고 반환하는가

  • enum의 속성을 출력/반환할 때 toString()getter 중에 많이 사용되는 방법이 무엇인지 또는 어떤 것을 사용해야할지.

지금은 toString이나 getter를 써도 상관 없지만, 리뷰어인 춘식은 getter를 추천했다.
toString을 사용하는 경우 enum화 해야할 상수가 많아지면 중복되는 코드가 많아져 가독성이 떨어진다.
또한 getter를 사용한다는 건 enum 필드에 값을 저장하는 건데, 이 경우 나중에 enum에서 특정 값을 찾아야 할 때 유용하게 쓰일 수도 있다.



4. 메세지 관리 방법

상수는 관련된 메세지끼리 한 클래스에서 관리하는 것이 좋을까, 각 역할(책임) 클래스에 위치하도록 하는게 좋을까?

각자 장단점이 있기에 때에 따라 쓰이는 방법이 다르다.

  • 각 역할 클래스에 위치

    • 장점: 어떤 에러 메세지를 보내는지 한눈에 파악이 가능, 에러 클래스를 따로 만들어 관리할 불필요

    • 단점: 코드 규모가 커지면 어떤 에러들이 있는지 파악하기 힘들고 수정이 어려워 결과적으로 유지보수가 어려워질 수 있음

  • 에러메세지를 모으는 클래스

    • 장점: 한 곳에서 에러 메세지를 관리할 수 있어 접근성이 편합니다. 변수명을 명시적으로 적어놓으면 한눈에 파악할 수 있도록 만드는 것도 어렵지 않을 것.

    • 단점: 규모가 아주 큰 프로젝트거나 에러를 세세하게 따져야 하는 경우라면 에러 메세지가 수십, 수백개가 된다면 그것대로 읽기 어려울 수 있다.

    • 단점을 방지하기 위해 큰 단위의 도메인(LadderGame, RacingCarGame, 등등)별로 에러 클래스를 만들어 주거나, 커스텀 상태 코드를 달아 분류를 하기도 한다. (HTTP 상태 코드가 대표적이죠)

어떤 방법이 좋을지 고민해봐야 한다.



5. 입력 값에 대한 검증 위치

InputView에서 검증을 진행하면 도메인 객체 생성 전에 입력을 받고 바로 검증할 수 있다는 장점이 있지만 참여자 수를 검증하는 것은 도메인 객체에서 해야할 역할이 아닌지 고민 되었다.

  • 입력값에 대한 검증 방법은 통일시키는 것이 좋다.

    • 전부 InputView에서 검사

    • InputView에서는 기본적인 입력(null인지, 숫자만 입력했는지 등)만 검증하고 나머진 역할에 따라 검증

전자의 경우 View에서 도메인의 정보를 알게 될 수도 있기 때문에 리뷰어인 춘식은 후자를 추천했다.



6. 예외 처리 위치

  • 공통적으로 잡을 수 있는 Main에 두는 게 좋다. 그래야 예상치 못한 에러가 났을 때도 잡아줘 돌발적인 시스템 종료를 막기 때문이다.

  • Main에 두지 않는 경우는, 에러가 발생할만 곳을 예측하여 내가 원하는 에러 모양으로 커스텀하고 싶을 때 이다. (현재 미션에 쓰이는 illegalArgumentException이 해당되는 케이스)

에러를 세세하게 잡는 것도 좋지만, 유저한테 에러 정보를 어떻게 전달해야 사용성이 편해질지도 고민해봐야 된다.

예시로 이름을 입력해야 할 때

이름은 1자 이상이어야 합니다.
이름은 5자 이하이어야 합니다.
이름에 특수문자는 들어갈 수 없습니다.

위 3개를 입력 마다 따로 받는 것과

이름은 1자 이상 5자 이하의 숫자 또는 글자이어야 합니다.

이렇게 하나를 받는 경우의 사용성은 분명히 차이가 있다.
너무 적게 줘도 문제지만 너무 많이 줘도 문제가 될 수 있으니 고민해서 적정량의 에러 메세지를 보내는 게 맞다.



7. getter의 사용을 줄이는 것에 대하여

getter를 어떻게 줄일 수 있을까? 엘레강트 오브젝트에서는 getter/setter 안티 패턴에서 유해한 부분은 두 접두사인 get과 set이라고 한다.
이 접두사들 때문에 객체가 진짜 객체가 아닌 데이터 저장소로 취급 받는다는 것.
책에서 내놓은 해결책은 접두사를 제거하는 것이다.

getDollars() 는 ‘데이터 중에 dollars를 찾은 후 반환하세요’ 라는 의미지만,
dollars() 라고 한다면 ‘dollar가 얼마나 있는가?’라고 묻는 거니 객체에 dollars 라는 필드가 있는지 알 수 없다. 이러한 방식으로 객체의 캡슐화를 존중하는 것.






기타

  • 필드 변수는 줄바꿈을 이용하여 static인 것과 아닌 걸로 분리해준다.

    private static final ZERO = 0;
    private static final ONE = 1;
    
    private final number;
  • 입력값을 처리할 때 맞춤법, 띄어쓰기에 의한 공백을 고려하여 trim(), strip() 을 이용하여 공백을 제거해준다.

  • 원격 저장소에 아직 push를 안 한 상태라면 터미널에서 git commit --amend 를 쓰거나 인텔리제이에서 커밋 수정 기능을 활용해 직전 커밋에 현재의 변경 사항을 반영할 수 있다! (push를 했다면 추후 force push를 해야 함)

0개의 댓글