Skip to content

최세민 1주차 학습 일지 ‐ 체스 게임

Semin Choi edited this page Jul 1, 2024 · 7 revisions

1주차 회고

  • 시간 제약을 고려하여 기능 구현을 우선적으로 하자.
  • 기능 구현 이전에 도메인을 확실하게 이해하자.
  • TDD를 하거나 기능 구현 뒤에 바로 테스트 코드를 작성하는 습관을 들이자.

기능 구현을 최우선으로

체스게임을 구현하면서 구조나 클린코드를 신경쓰다보니 기능 구현을 완료하지 못했습니다. 구조나 클린코드에 대해서 먼저 고민하기보다는 우선 구현하고 점진적으로 개선해 나가는 것이 오히려 생산성에 도움이 된다고 느꼈습니다. 급하게 구현하다보니 도메인 지식에도 미숙한 부분이 있었고 이래저래 부족한 부분이 많았던 코드였습니다. 항상 기능 구현을 최우선으로 두고 이를 위해 도메인 지식도 처음부터 꼼꼼하게 익히는 것이 중요하다는 것을 느꼈습니다.


enum의 필드는 왜 private이어야 하는가?

enum의 필드는 변하지않는 enum타입의 속성으로, private으로 사용하면 해당 값을 얻기 위해 getter 메소드를 호출해야합니다. 하지만 final 변수라면 굳이 get 메서드를 통해 호출할 이유는 없지 않을까? 라는 생각을 했습니다.

enum 필드를 private으로 사용하지 않으면 캡슐화를 해친다고 합니다. 하지만 getter에 대한 캡슐화가 그렇게까지 의미가 있는지 사실 와닿지는 않았습니다. 그럼에도 언제든 요구사항은 변할 수 있다는 것을 고려하면 getter에 특수한 계산 로직이 생길 수도 있고, 그런 경우에는 캡슐화를 해두는 것이 더 안전한 것 같습니다. r ecord class의 경우에도 인자에 바로 접근하지 않고 getter 메소드를 사용하도록 설계되어 있는 것을 보며 충분히 그래야할만한 근거가 있다고 결론을 내렸습니다.


인터페이스를 정의하기 전에 고려해야 할 점

체스 미션을 하면서 다양한 부분에 인터페이스 도입을 시도해 봤습니다.

  • 게임의 상태를 관리하기 위한 State패턴

    • State 패턴은 다양한 상태가 있고 이로인해 많은 분기가 일어날 때 적용하면 좋습니다. 하지만 체스게임에서는 상태가 많지 않고 실제 주요한 로직은 기물 이동 요구사항에서 대부분 발생하므로 상태의 책임 크기가 균등하게 배분되지 않았습니다. 따라서 해당 인터페이스 도입은 성공적이지 않았습니다.
  • Console, Swing, Web 등 다양한 클라이언트로부터 요청을 수용하고 응답하기 위한 Input/OutputManager 인터페이스

    • 각각의 클라이언트마다 요청과 응답 방법이 다르기 때문에 메소드 시그니쳐가 다를 수 밖에 없었습니다. 따라서 인터페이스를 적용하기에는 적절하지 않은 부분이었습니다.

위와 같이 책임이 너무 작은 곳에 인터페이스를 적용하거나, 인터페이스가 일관되지 않은 부분에 인터페이스를 적용하는 것은 오히려 독이 될 수 있다는 것을 배웠습니다.


테스트 코드 작성의 필요성, DCI 패턴

지금까지는 처음 설정한 요구사항에서 크게 변하는 상황이 많지 않았기 때문에 테스트 코드의 중요성이 잘 와닿지 않았습니다. 하지만 빠르게 요구사항이 변화하고 기능을 테스트해야 하는 경우 테스트 코드가 없으니 오류를 발견하고 디버깅하는 과정에서 너무 많은 시간을 소요하게 됐습니다. 이를 통해 테스트 코드의 필요성을 몸소 체험했고 상황에 맞게 적절한 패턴의 테스트 코드 작성하는 습관을 확실하게 들여보고자 합니다.

코드 리뷰를 하며 DCI 패턴을 적용한 테스트코드를 보았는데, 인상깊어서 조금 더 알아보았습니다.

테스트 코드를 작성하다보면 다음과 같이 중복된 시나리오 문구가 반복될 때가 많습니다.

- 기물을 이동할 , 목표 위치에 아군 기물이 있으면 이동이 실패한다.
- 기물을 이동할 , 목표 위치에 적군 기물이 있으면 해당 기물을 제거하고 이동시키는 기물이 해당 위치로 이동한다.
- 기물을 이동할 , 이동 경로에 다른 기물이 있으면 이동이 실패한다.
- 기물을 이동할 , 이동 경로에 다른 기물이 있으면 나이트는 이동할  있다.

위와 같이 public 메소드 내부에 숨겨진 다양한 시나리오를 여러개의 테스트메소드로 나눠서 작성하다보면 전체 테스트를 수행했을 때, 어떤 테스트가 실패한 것인지 잘 이해되지 않을 때가 있습니다.

DCI는 Describe Context It 의 약자입니다. '기물 이동 시 목표 위치에 아군 기물이 있으면 이동에 실패한다' 라는 테스트 시나리오가 있다면 다음과 같이 분리할 수 있습니다.

  • Describe(테스트 하고자 하는 대상): 기물 이동 시
  • Context(상황/조건): 목표 위치에 아군 기물이 있으면
  • It(기대 결과): 이동이 실패한다.

위에 예시로 설명했던 테스트 케이스들을 DCI 패턴으로 보완하면 다음과 같아집니다.

- 기물을 이동 
  - 목표 위치에 아군 기물이 있으면 
    - 이동이 실패한다.
  - 적군 기물이 있으면 
    - 해당 기물을 제거하고 이동시키는 기물이 해당 위치로 이동한다.
  - 이동 경로에 다른 기물이 있으면 
    - 이동이 실패한다.
    - 나이트는 이동할  있다.

예시가 조금 미흡하지만 테스트 시나리오가 많고 복잡할 때 Given-When-Then 형태로 작성하는 것 보다 테스트 대상이 무엇인지 쉽게 이해할 수 있었습니다. 테스트 시나리오의 복잡도에 맞게 적절한 패턴을 선택해서 테스트 케이스를 작성해야겠습니다.


List vs Set

기물 이동을 구현하기 위해 Direction enum을 사용하던 중, ListSet 중에 무엇을 사용하는게 더 나은지 고민했습니다.

  public enum Direction {
    NORTH, SOUTH, WEST, EAST ...
    
    ...
    
    public ststic List<Direction> getLinearDirections {
        return List.of(NORTH, SOUTH, WEST, EAST);
    }
}

위와 같이 컬렉션에 저장하는 값들이 중복되지 않는게 옳은 상황이라면, Set을 사용하는 것이 더 명시적이고 좋다고 생각했습니다.

하지만 Set은 내부 배열에 빈공간이 존재하여 iteration에 오버헤드가 있고, 해시값을 사용하기 때문에 List보다 . 더많은 메모리 공간을 차지한다는 단점이 있습니다. 이를 보완한 LinkedHashSet

위의 예시에서 값들은 새로 추가되지 않고, 주로 iteration 용도로 사용하기 때문에 시간/공간 복잡도 측면에서 List를 사용하는 것이 유리합니다.

특별하게 Set을 사용해야만 하는 상황이 아니라면 List를 사용하는 것이 성능상 더 좋다는 것을 배웠습니다.

👼 개인 활동을 기록합시다.

개인 활동 페이지

🧑‍🧑‍🧒‍🧒 그룹 활동을 기록합시다.

그룹 활동 페이지

🎤 미니 세미나

미니 세미나

🤔 기술 블로그 활동

기술 블로그 활동

📚 도서를 추천해주세요

추천 도서 목록

🎸 기타

기타 유용한 학습 링크

Clone this wiki locally