https://github.com/woowacourse/jwp-chess/pull/354 

 

[Spring 체스 - 1단계] 라쿤(이병훈) 미션 제출합니다. by nbalance97 · Pull Request #354 · woowacourse/jwp-chess

안녕하세요 루피! 로또 미션 이후 또 뵙게 되서 반갑습니다😊 다름이 아니라 질문이 하나 있습니다! 저희가 이번에 테스트를 진행하면서 기물 update하는 과정에서 테스트 코드에서는 h2를 사용

github.com

https://github.com/woowacourse/jwp-chess/pull/451 

 

[Spring 체스 - 2단계] 라쿤(이병훈) 미션 제출합니다. by nbalance97 · Pull Request #451 · woowacourse/jwp-chess

안녕하세요 루피😊 1단계에 이어서 2단계도 잘 부탁 드립니다!! 기존 코드에서는 오직 하나의 게임만 제공하도록 설계하였는데 이번에 여러개의 방을 지원하도록 하려고 하니 많은 변경이 필연

github.com

레벨 2가 끝난 지는 조금 되었지만.. 뒤늦게라도 배운 내용을 정리하고자 글로 남긴다..ㅎㅎ 이번 미션에서는 레벨 1의 마지막 미션인 체스 미션을 가지고 와서 Spring을 적용하여 보는 미션이었다.

Spring을 학습하고 적용하는 경험이 이번이 처음이었기 때문에 많이 헤맸으며, 기능 하나가 정상적으로 작동하면 페어와 기쁨을 감추지 못했던 기억이 새록새록 난다😊

 

이번 미션에서는, 얕게라도 스프링의 동작 원리에 대해서 학습하려고 노력했던 것 같다.

 

Mapping


미션을 진행하면서 가장 먼저 중점적으로 적용했던 것은 컨트롤러에 @Controller 어노테이션을 붙여 주고,  @GetMapping, @PostMapping 등의 어노테이션을 붙여 사용자의 요청 url, 메소드에 매핑되는 메서드가 호출되도록 변경하였다.

 

이전에는 직접 Spark의 메소드를 사용하여 get, post에 대응하도록 등록해 주어야 했는데, 어노테이션을 붙이기만 하니 직관적이고 훨씬 편하게 사용할 수 있었다. 

 

get("/", webController::boardPage);
post("/move", webController::move);
post("/start", webController::start);

// 위보단.. 아래가 더 직관적이라고 느껴진다. 굳이 메소드를 통해 등록을 따로..

@Controller
public class Controller {
    @GetMapping("/")
    public String get() {
    	...
    }
}

 

Controller vs RestController


또한, 이번 미션에서는 api와 view를 같이 제공하게 되었는데, 둘 다 하나의 Controller에서 처리해 주었었다. 이 점에 대해서도 리뷰어가 리뷰를 남겨 주셔서 찾아보고 주변 크루들과 이야기를 해 보니 @Controller에서는 view를 반환하는 것이 일반적이고, api에 관한 것은 @RestController에서 처리해 주는 것이 더 깔끔해 보이는 구조인 거 같다는 생각이 들었다.

 

물론 @Controller에 @ResponseBody를 붙여주면 되지만, @RestController이 @Controller에 @ResponseBody가 붙어있는 것인데 굳이..? 따라서 컨트롤러를 view용 컨트롤러, api용 컨트롤러로 나누어 주었다.

 

@Transactional


마지막으로, 레벨 2를 돌아보며 가장 많이 사용하게 되는 어노테이션중 하나인 @Transactional 어노테이션을 학습하는 계기가 되었다. 기존 프로젝트의 문제점은 체스 판을 저장할 때, 체스 말을 하나씩 db에 저장하게 될 텐데 그렇다면 중간에 서버가 종료되게 된다면 체스 말이 저장되다가 마는 문제점이 생긴다.

 

이러한 점을 해결하기 위해서, 체스 말을 db에 저장해주는 부분을 트랜잭션으로 묶어서 처리하였다. 트랜잭션으로 묶어서 처리하게 된다면 중간에 서버가 종료되게 된다면 롤백되어 아예 체스 말을 저장하기 전으로 돌리게 되므로 일관성 있게 데이터를 처리할 수 있다.  

 

마치며


체스 미션을 통해서 기존 작성한 프로젝트를 Spring으로 옮기면서 대략적인 Spring 사용법에 대해서 학습할 수 있어서 좋았다. 또한 기존 프로젝트의 코드가 Spring에선 어떻게 작성하면 되는지 방식이 매핑되다 보니 이해하기 쉬웠던 것 같다. 

https://github.com/nbalance97/java-chess/tree/step4

 

GitHub - nbalance97/java-chess: 체스 게임 구현을 위한 저장소

체스 게임 구현을 위한 저장소. Contribute to nbalance97/java-chess development by creating an account on GitHub.

github.com

 


미션 진행


4, 5단계에서는 web ui 적용 및 데이터베이스와 연동하여 게임 진행 상황을 저장/불러오는 기능을 구현해야만 했다😆

 

web ui를 구현하기 위해 html은 템플릿 언어를 사용하였으며, sparkjava를 사용하여 서버를 구성하였다. 미션에서 웹 서버와 연동하여 작업한건 처음이라 많이 어색하여 이번 미션에서도 기능을 구현하는데 급급하였다😂

 

우선, Application에서는 각각의 url에 맞추어서 url에 따라 함수를 호출하도록 구현하였다.

 

 

또한, Controller에서는 Service를 가지고 있고, Service의 메소드를 호출하는 방식으로 구현하였다. 

 

Controller에서 move에 대한 처리

기존까지는 Service에서 ChessGame을 가지고 있는 구조였는데, 이러한 구조는 여러 대의 서버를 두게 된다면 문제가 있다고 한다. 각각의 서버가 ChessGame을 갖는다고 할 때, 하나의 서버의 ChessGame이 수정되면, 전체 서버의 ChessGame을 다 바꾸어주어야 하는 문제점이 생기므로 일관된 상태를 유지하기 위해 Service에 존재하는 ChessGame을 삭제하고, 데이터베이스에서 그때마다 불러오는 방식으로 코드를 변경하였다.

 

필요할 때마다 ChessGame을 load해 온다.

컨트롤러의 역할이 많이 모호하다고 느껴졌는데, 이번 체스 미션을 진행하면서 컨트롤러의 역할을 어느정도 정리할 수 있어서 좋았다. 컨트롤러에서는 사용자의 요청에 맞게 Service에게 기능을 수행하도록 명령하고, 수행 결과를 받아서 전달하는 역할이라고 정리하였다. 기존까지는 밋밋한 콘솔에서만 개발을 하다가 웹으로 옮겨서 해보니 신기함과 동시에 내가 왜 백엔드 개발을 하고 있는지 상기하게 되는 계기가 된 것 같다. 전체적으로 힘들었지만 재밌는 경험이었다. 

https://github.com/nbalance97/java-chess/tree/step1

 

GitHub - nbalance97/java-chess: 체스 게임 구현을 위한 저장소

체스 게임 구현을 위한 저장소. Contribute to nbalance97/java-chess development by creating an account on GitHub.

github.com

 


체스 미션


여지껏 진행한 미션 중에 페어와 이야기를 가장 많이 나눈 미션이 아닌가 싶다. 예를 들면 각각의 말을 board에서 관리해야 하는지, 말들이 가지고 있어야 하는지부터 시작해서 말들의 이동 규칙 검증은 어디서 해야 하는지 등등.

 

전체적으로 구현 자체가 오래 걸렸던 것 같아서 완성 후 직접 실행시켜 테스트 해보는데, 콘솔로 실제 말이 이동할 때 오오오 하면서 감탄했던 것 같다ㅋㅋㅋ😄

 

체스 게임을 구현하면서 가장 중점적으로 구현한건 체스 말의 이동 검증이다.

  1. 말이 해당 위치로 이동이 가능한지 여부 체크
  2. 퀸, 룩, 비숍의 경우 해당 위치로 이동하는데 가운데 가로막는 말이 있는지 확인
  3. 폰이 가장 처리가 복잡하다.. 졸개 주제에,,
    1. 폰은 처음에는 최대 2칸 이동할 수 있다. -> 폰이 2행/7행에 있는지 확인하여서 처리
    2. 폰은 대각선에 상대 말이 있는 경우에만 대각선으로 이동할 수 있다.
    3. 폰은 바로 앞에 상대 말이 있으면 앞으로 이동할 수 없다.

각각의 말 클래스를 만들어 두고, 각각의 말 클래스의 내부에 현재 위치와 목표 위치를 전달받으면 도달 가능한지 검증하는 메소드를 두었으며, 가로막는 말을 검증하기 위해서는 board와의 상호작용이 필요할 수 밖에 없어서, 가로막는 말 검증은 board에 두었다.

 

또한, 이전 미션에서는 사용하지 않았지만 이번 미션에서는 상태 패턴을 사용해 보았다.

턴 순서가 흰 팀, 검은 팀이 교차하면서 나타나고, 왕이 잡히는 순간 게임이 끝나기 때문에 전체적으로 상태를 3가지로 구성하였다.

  1. 흰 팀 상태
  2. 검은 팀 상태
  3. 왕이 사망한 상태

게임을 진행하다가 잡는 말이 왕인 경우 KingDeath 상태로 이동하도록

 

검은 팀 차례에선 하얀 팀 차례로 이동

 

whiteturn, blackturn에서 왕을 잡지 않는다면 각각 blackturn, whiteturn으로 이동하게 구성하였으며, 왕을 잡게 되는 순간 kingdeath 상태로 이동하여 게임이 종료된다.

 

상태 패턴을 실제로 미션에서 사용해본건 이번이 처음인데 도입해 보니 생각보다 훨씬 편했다. if문으로 검사를 많이 해줘야 하는 부분을 play() 메소드를 호출하게만 해서 편했고, 실제로 board에서는 상태가 finish인지 확인만 해주면 되었다.

 


피드백 및 리팩터링


가장 기억에 남는 건 Controller을 많이 고쳤다는 점이다😇 

여지껏 미션에서는 콘솔 단계에서 끝나서 Controller에서 View의 메서드를 마음껏 호출해서 값을 가져와서 사용해도 아무 문제가 없었다.

하지만 이번에 web ui가 적용된다고 하면 Controller에서 View의 메서드를 호출하면 안됐다.

Controller이 사용자의 요청에 대한 응답을 내뱉어주어야 하므로, 실행 로직 자체는 외부 Application으로 옮기고, Controller에서 출력을 위한 정보를 리턴해 주도록 변경하였다.

 

다음은 Application의 구성 방식이다.

명령어에 따라 각각의 메소드들을 호출
chesscontroller에서 가져온 결괏값을 출력시켜주도록 구현하였다.

이러한 구조로 코드를 작성하니까 controller에서 필요한 로직만 남게 되어 controller의 코드가 훨씬 짧아지기도 하고 이해하기도 쉬운 코드가 되었다. 

 


결론


전체적으로 구현 자체가 어렵게 느껴져서 힘든 미션이었다. 또한 Controller의 역할에 대해서 다시 한번 생각해보는 계기가 된 것 같다.

물론 콘솔로 끝냈다고 끝인건 아니지만, web 미션에서도 어떤 방식으로 구현을 하게 될 지 어느정도 생각하면서 리팩터링을 한거라 어느 정도는 괜찮지 않을까 생각한다😅 

https://github.com/woowacourse/java-blackjack/tree/nbalance97

 

GitHub - woowacourse/java-blackjack: 블랙잭 미션 저장소

블랙잭 미션 저장소. Contribute to woowacourse/java-blackjack development by creating an account on GitHub.

github.com

 


미션 진행


 

블랙잭 미션 2단계에서는 총 두가지의 기능을 추가해야 한다.

 

1. 각각의 참여자의 금액을 입력받는 기능

2. 승/블랙잭 승/패/무 시 획득하는 이윤을 계산하는 기능

 

기능을 추가하는데는 그렇게 어렵진 않았다😀

구현을 위해 User 클래스에 bettingPrice 필드를 추가하고, 해당 필드에 값을 입력받는 로직을 추가하였다.

또한, 승/패/무에 따라 사용자의 이윤을 계산하는 클래스(UserProfit)를 두어 이윤을 계산하게 하였으며, 딜러의 이윤은 사용자의 이윤의 총합으로 계산하도록 구성하였다 (DealerResult) 😆

 

이번에 딜러의 이윤이나 블랙잭 승/패를 구하는 로직을 Enum 클래스를 활용해서 작성해 보았다.

정적 메서드인 checkUserResult에서 유저와 딜러를 입력받아서 유저의 승/무/패를 구하도록 로직을 작성하였다.

 

 

기존에는 Enum 클래스를 상수 처리를 위해서만 사용했다면, 이번에 Enum 클래스의 필드에 함수형 인터페이스를 두는 방식을 사용 해 보았다. 위와 같은 방식으로 승/패 로직을 구성하면 if문을 줄일 수 있어서 좋았다 😃 

 

 


리뷰 및 리팩토링


리뷰를 받고, 리팩토링을 하면서 느낀 점은 기존 코드에서 Controller에서 너무 많은 일을 하고 있다는 점이다.😂

리팩토링에서 주로 Controller에서의 일을 많이 줄이고, 컨트롤러에 존재했던 로직을 도메인이나 뷰로 많이 이동시켰던 것 같다. 

 

딜러 이름 검사를 컨트롤러에서 해줄 필요가 없었는데.. 관련 로직을 도메인으로 이동시켜서 내부에서 처리하도록 구현하였다.

 

컨트롤러에서 유저 이름과 베팅 금액을 매핑시켜 줬었는데, 컨트롤러가 수행해야 할 일은 아니라고 생각되었다.

이 점은 InputView에게 이름을 넘겨 주어서 이름과 베팅 금액을 매핑시킨 Map을 가져오도록 변경하였다.

 

상수가 중복된다는 점은 전혀 모르고 있었다. 😇 동일한 상수가 중복된다는 것은 동일한 검사 로직이 많은 클래스에 퍼져 있는 것이라고 생각한다. 이에 따라 점수 계산은 근본적으로 Participant 클래스에서 하므로, Participant 클래스에서만 상수 21을 관리하도록 변경하였으며 나머지 클래스에서는 관련 로직을 제거해 주었다.

 


결론


레벨 1에서의 블랙잭 미션에서는 enum 클래스를 다양하게 사용해 본 거 같다😄 확실히 enum 클래스를 사용하면 if문이 여러 번 나오거나 꼬일 때 많이 줄일 수 있는 것 같아 좋은 것 같다.

 

체감상 아직 Controller의 역할이 어디 까지인지 모호하게 느껴진다😅 그래도 이번 미션에서는 출력을 위한 클래스와 enum 클래스의 활용법에 대해서 많이 알아간 것 같다.

https://github.com/nbalance97/java-blackjack/tree/step1

 

GitHub - nbalance97/java-blackjack: 블랙잭 미션 저장소

블랙잭 미션 저장소. Contribute to nbalance97/java-blackjack development by creating an account on GitHub.

github.com

 

Lv 1의 세번째 미션은 블랙잭이었다. 블랙잭 또한 다른 미션들과 마찬가지로 1단계는 페어 프로그래밍으로 진행하였다. 페어나 본인이나 블랙잭 게임에 대해서 문외한이라 블랙잭 게임이 뭔지 알아보는데만 상당한 시간이 걸렸던 걸로 기억한다 😅

 

코드 구현을 하면서 기억에 남던 부분은 수업때 들었던 함수형 인터페이스를 너무 사용해보고 싶어서 결과를 구하는 부분에 직접 사용해 보았던 경험이었다😊 

처음 써본 함수형 인터페이스

 

유저와 딜러의 정보를 받아서 승/무/패를 구하도록 처음 시도해 보았는데 생각보다 잘 되서 좋았다😊

맹점이라면 뒤늦게 사람들의 이야기를 들어보니 블랙잭 승리는 조건이 조금 더 있다고 하더라.. 


피드백 및 리팩토링


받은 피드백 중 가장 기억에 남는 피드백은 게임 결과 부분이다.

기존까지는 OutputView에서 도메인에 직접 접근해서 출력을 해주도록 코드를 작성했었는데, 해당 부분에 대해서 리뷰어 던의 피드백을 받았다.😀

 

 

피드백에 따라 기존에 Users, Delaer, Deck을 모두 가지고 있는 구조를 BlackJack이라는 클래스로 빼고, 처음으로 View에서 사용하기 위한 클래스를 만들어서 진행하였다.

 

출력을 위해서 필요한 데이터는 유저의 이름카드의 정보, 카드의 합계 총 3개의 데이터가 필요하여서 3개의 데이터를 담고있는 DistributeResult 클래스를 만들어서 View에 전달하였다.

 

이러한 구조로 코드를 작성하니 기존에 Controller이 비대하던 문제점을 해결할 수 있었다😄

 

또한, 기존 View에서는 도메인에 직접 접근해서 게임 결과를 출력해주어야 했는데, 이러한 방식으로 구성하니 View에서는 도메인에게 직접 요청할 필요 없이 결과만을 전달받아서 출력할 수 있게 되었다🦝

 

전체적으로 블랙잭 1단계에서는 View에 데이터를 어떻게 전달해야 할 지에 초점을 맞추어서 고민을 많이 했던 것 같고, 실제로 코드가 더 명확해지는 경험을 해서 좋았다😏

https://github.com/nbalance97/java-lotto/tree/step2

 

GitHub - nbalance97/java-lotto: 로또 미션 진행을 위한 저장소

로또 미션 진행을 위한 저장소. Contribute to nbalance97/java-lotto development by creating an account on GitHub.

github.com


개요


이번 로또 미션에서는 기존 로또 미션에서는 자동으로 생성하는 기능만 있었다면, 수동으로 입력받는 기능도 더하라는 요구사항이 추가되었다😥 기존까지는 요구사항에 맞추어서 끝내면 그대로 끝난 경험이 많다. 그런데 이번에는 요구사항이 추가되면서 기존 코드를 기반으로 새로운 기능을 추가해야만 했다.

 

이전에는 이러한 경험이 없다 보니 코딩을 하면서도 이게 맞나? 싶기도 하더라.. 여차저차 요구사항에 맞추어서 기존 코드를 수정하고, 새로운 코드를 작성하면서 기능 구현을 끝마치기는 했지만, 스스로 코딩을 하면서도 기존 코드를 너무 많이 건드는 거 같아 좀 아닌것 같다고 느껴졌다. 물론 리뷰어 루피도 이를 피드백으로 남겨주셨다..ㅎㅎ

 

매서운 피드백..

 


인터페이스의 도입


사실 처음에는 Lotto를 인터페이스화 해서 AutoLotto와 PassiveLotto로 나누는 것은 어떨까? 하고 직접 진행을 해 보았다. 하지만 각각의 로또의 메소드가 대부분 일치하다는 점을 발견하고, 디폴트 메소드로 빼기에도 애매해서 많은 고민을 했다.😂 결국 루피에게 DM을 보내 조언을 구해보니, 인터페이스를 너무 크게 잡았다는걸 알게 되었다.

 

로또 자체를 인터페이스화 해서 AutoLotto, PassiveLotto로 구현하지 말고, 로또의 생성 규칙을 인터페이스로 잡는 것이 훨씬 구현하기도 편하고 보기에도 좋더라. 즉, AutoLottoFactory와 PassiveLottoFactory로 나누어서 구현하였다.

 

    private LottoFactory autoLottoFactory;
    private LottoFactory passiveLottoFactory;

    public LottoTicket(int autoLottoCount, List<List<LottoNumber>> passiveLottos, LottoNumberGenerator generatorPolicy) {
        this.autoLottoCount = autoLottoCount;
        this.autoLottoFactory = new AutoLottoFactory(generatorPolicy, autoLottoCount);
        this.passiveLottoFactory = new PassiveLottoFactory(passiveLottos);
        this.lottos = generateLotto();
    }

    private List<Lotto> generateLotto() {
        List<Lotto> lottos = new ArrayList<>();
        lottos.addAll(passiveLottoFactory.create());
        lottos.addAll(autoLottoFactory.create());
        return lottos;
    }

 

결론을 내자면 생성 규칙에 따라 Factory를 만들고, 해당 Factory에서 로또를 생성하는 방식으로 구현을 했다.

이렇게 구현을 하면 추후 이상한 방식(절반은 자동, 절반은 수동..?)으로 로또를 생성하게 되더라도 그거에 맞추어서 Factory를 만들어 준 다음 끼워 맞추어주기만 하면 되기 때문에 변경에 유연한 코드가 된 것 같다. 그리고 이러한 경험을 바탕으로 왜 인터페이스를 사용하는지 다시 알게 되어 엄청 유익한 경험이었다😎

 


결론


 

계속해서 점점 알아야 할 것도 많아지고 좀 힘든것 같긴 하지만 그래도 지금은 꽤 재밌다..ㅎㅎ 잘 모르는 상황에서의 리뷰어분들의 리뷰, 그리고 크루원들과의 대화 속에서 많은 걸 배우고 있고 모두 잘하시는 분들이라 계속해서 배워나가겠다는 마음가짐으로 천천히 공부해보고자 한다. 최근에는 데일리 크루원들과 오프라인에서 모각코를 했었는데, 거기서도 개발 관련 이야기를 하며 아직 배워야 할 점이 많다는걸 알게 되었다.. 아무래도 성장할 일만 남지 않았을까..?😎

https://github.com/nbalance97/java-lotto/tree/step1

 

GitHub - nbalance97/java-lotto: 로또 미션 진행을 위한 저장소

로또 미션 진행을 위한 저장소. Contribute to nbalance97/java-lotto development by creating an account on GitHub.

github.com


페어 프로그래밍


1단계 미션은 페어 프로그래밍으로 진행되기 때문에 당연히 로또 미션 1단계도 페어프로그래밍으로 진행된다. 이번에 매칭된 페어는 에덴!!

 

첫날은 Intelij의 Code with me를 사용해서 어느정도만 구현을 해 두고, 다음 날에 잠실 스터디룸을 빌려 에덴의 맥북 프로로 페어 프로그래밍으로 미션을 진행했다. 꼭 구현하면서 의견 교환이 아니더라도 충분히 많이 배우게 된 점이 있었다. 인텔리제이의 단축키 사용이었다. 

 

사실 단축키를 익혀야 겠다라고 생각은 했지만 당장 필요하지는 않은거 같아 미루고 있던 찰나에 페어가 단축키를 사용하는 모습은 나로 하여금 신선한 충격을 받게 만들었다. 훨씬 편하게 코딩하는 모습을 보고 집에 돌아와서 부랴부랴 필즈와 포키가 정리해 주신 단축키 공부를 시작했다..ㅎㅎ 에덴 덕분에 스스로 무엇이 부족한지 알 수 있게 되어서 좋은 경험이었다.

 


피드백 및 리팩토링


이번에 매칭된 멘토인 루피는 잘 때리실 거 같은 닉네임과는 다르게 좋은 피드백을 많이 남겨주셨다. 그 중 기억에 남는 피드백 중 기억에 남는 피드백 하나를 정리해보고자 한다.

로또의 각 숫자의 객체화

이전에는 로또의 각 번호를 Integer로 가지고 있도록 구현하였으나 피드백에서 로또 번호에 대해서 많은 검증을 하고 있어서 로또 번호를 객체로 만들어 역할을 분리해보라는 피드백을 받았다. 처음에는 왜 굳이 그렇게까지 객체로 나누어서 해야 하지? 싶어서 마지막의 마지막까지 미루다가 피드백 주신 내용이니 한번 해보자는 마음으로 시도해 본 결과 개인적으로 훨씬 좋았다.

 

LottoNumber라는 클래스에서 로또 번호의 검증을 해주고, WinningLottos와 Lotto 클래스에서는 로또 번호의 검증이 필요 없이 그냥 LottoNumber을 사용해 주기만 하면 되어서 코딩할 때 개인적으로 훨씬 편리하게 느껴졌다.

이 점에 대해서 루피에게 말씀드리니 단일 책임 원칙을 참고하라는 피드백과 함께 기존 Lotto 객체는 숫자 하나의 범위를 변경, 여러 숫자의 중복 허용 여부 두 가지 이유로 인해 변경될 여지가 있다. 라고 하셔서 후에 객체를 설계할 때 조금 더 많이 고민을 해보아야 겠다는 생각이 들었다.

 

번외로 LottoNumber 클래스를 만들면서 1~45까지의 인스턴스를 미리 만들어 두고, 정적 팩터리 메소드로 입력받은 숫자에 해당하는 인스턴스를 바로 반환해 보는 경험도 해 보아서 좋았다😃

 

public class LottoNumber {

    private static final int LOTTO_NUMBER_MAXIMUM = 45;
    private static final int LOTTO_NUMBER_MINIMUM = 1;
    private static final Map<Integer, LottoNumber> lottoNumberCache = new HashMap<>();

    static {
        for (int lottoNumber = LOTTO_NUMBER_MINIMUM; lottoNumber <= LOTTO_NUMBER_MAXIMUM; lottoNumber++) {
            lottoNumberCache.put(lottoNumber, new LottoNumber(lottoNumber));
        }
    }

    private int number;

    private LottoNumber(int number) {
        this.number = number;
    }

    public static LottoNumber of(int number) {
        validateNumberRange(number);
        return lottoNumberCache.get(number);
    }

    public int getNumber() {
        return number;
    }

    private static void validateNumberRange(Integer lottoNumber) {
        if (lottoNumber > LOTTO_NUMBER_MAXIMUM || lottoNumber < LOTTO_NUMBER_MINIMUM) {
            throw new IllegalArgumentException();
        }
    }
}

 

또한 추가적으로 기억에 남는 피드백은 필드에 리스트가 있으면 생성자에서 생성해주는 방식을 루피가 제안해 주셨는데 확실히 코드가 더 깔끔해지는것 같아서 기억에 남았다.👀 ArrayList 생성을 항상 필드에서 해주는 습관이 있었는데 이번 기회에 좀 더 깔끔하게 작성하는걸 고민하게 된 것 같다..ㅇㅇ

 

아래 녀석은 ArrayList를 필드에서 초기화시켜 주고, 생성자에서 요소를 추가하는 기존 본인의 방식이다.

private final List<LottoNumber> lottoNumbers = new ArrayList<>();

public Lotto(List<LottoNumber> lottoNumbers) {
    validateDuplicatedNumber(lottoNumbers);
    this.lottoNumbers.addAll(lottoNumbers);
}

 

굳이 이렇게 할 필요 없이.. 그냥 ArrayList 생성자의 매개변수로 lottoNumbers를 넘겨주면 된다.

private final List<LottoNumber> lottoNumbers;

public Lotto(List<LottoNumber> lottoNumbers) {
    validateDuplicatedNumber(lottoNumbers);
    lottoNumbers = new ArrayList<>(lottoNumbers);
}

 

1단계에서는 LottoNumber로 분리하는 것 이외에는 크게 어려운 피드백은 없었던 것 같고 루피가 좋은 조언들을 많이 남겨주셔서 생각을 많이 해보게 되어 좋은 경험인 것 같다.👍

그나저나 갈길이 멀다.. 아직 모르는게 많은것 같고 다른 크루들의 코드를 조금씩 보면서 눈치껏 배우고 있는데 언젠간 성장할 거라 생각하며 이번 미션에 임하려고 한다.~

https://github.com/nbalance97/java-racingcar/tree/step2

 

GitHub - nbalance97/java-racingcar: 자동차 경주 게임 미션 저장소

자동차 경주 게임 미션 저장소. Contribute to nbalance97/java-racingcar development by creating an account on GitHub.

github.com

 

이번 미션에서는 자동차 경주 구현 1단계에서 한 미션을 바탕으로 MVC패턴을 적용하여 리팩토링 해보는 미션이었다.  1단계를 진행하면서 너무 클래스가 난잡하다고 느껴져서 페어와 MVC패턴으로 구현을 하기로 해서 이미 MVC패턴으로 설계를 해놓긴 했으나, 패키지명을 Model로 두어 해당 패키지명을 domain으로 수정하였다.

 

1차 피드백


다른분들의 깃허브 코드들도 살짝살짝 보기도 하면서 내 코드와 비교해 보니, 메인 클래스에서 자동차 경주 게임을 실행하는것 보다는 RacingGame 클래스를 만들어서 해당 클래스에서 게임을 진행하는 방식이 더 좋을거 같았다. 그래서 RacingGame 클래스에서 게임을 진행하도록 리팩토링을 진행해 보았다.

 

main 메서드에서는 RacingGame의 play() 메서드를 호출하도록 구현하였다.

try {
    racingGame.play();
} catch (RuntimeException e) {
    System.out.println(e.getMessage());
}

 

RacingGame의 play 메서드로 기존 main메서드에서의 게임 실행 코드를 이사시켰다. 생성자에서 자동차 이름과 시도 횟수를 전달받아 tryCount는 실제 게임을 진행할 때 사용하고 자동차 이름 배열은 raceController로 전달하여 자동차 객체를 넣도록 구현하였다.. 게임 자체를 객체화 하니 개인적으론 좀 더 명확하게 느껴졌다. 👀

public class RacingGame {
    private final RaceController raceController;
    private final String[] carNames;
    private final int tryCount;

    public RacingGame(String[] carNames, int tryCount) {
        this.carNames = Arrays.copyOf(carNames, carNames.length);
        this.tryCount = tryCount;
        raceController = new RaceController();
        raceController.insertCarFromCarNames(carNames);
    }

    public void play() throws RuntimeException {
        OutputView.printResultPrefix();
        for (int i = 0; i < tryCount; i++) {
            raceController.moveRound();
            raceController.printPosition();
        }
        raceController.printWinner();
    }
}

 

매 순간 리뷰 요청 버튼을 누를때는 떨려..떨려요.. 떨리는 마음으로 리뷰 요청을 드렸고, 리뷰어 제이에게 피드백을 받았다. 제이의 피드백을 요약하면 다음과 같다.

  1. RacingGame에 컨트롤러의 역할을 모두 부여
  2. RacingController을 제거관련 도메인 로직을 도메인으로 이동

처음 피드백을 듣고 RacingGame과 RacingController을 다시 보니 굉장히 애매모호한 점이 있었다.🤔 RacingGame에서는 실제 게임을 진행하고, RacingController에서는 승자를 구하거나 리스트 내 Car들을 이동시키는 등 Car 리스트와 상호작용 하는 역할만 하고 있었기 때문이다. 따라서 RacingController 객체를 삭제하고 도메인에 Cars라는 클래스를 생성해 준 후, Cars 클래스에서 Car 리스트를 다루도록 리팩토링 하였다.

public class Cars {
	...
    private List<Car> cars = new ArrayList<>();
	...
}
public class RacingGame {
    private static final String RESULT_PREFIX = "실행결과";
    private Cars cars = Cars.getInstance();
    private final int tryCount;
	
    ...
    public RacingGame(String[] carNames, int tryCount) {
        this.tryCount = tryCount;
        cars.insertCarFromCarNames(carNames);
    }
    ...

 

이후 추가 리뷰를 요청드렸으며 다음 피드백에서 제이는 일급컬렉션이라는 키워드를 알려주셨다. 찾아보니 분명 언젠가  이전에 공부했던 기억이 나는 개념이었다. 필요에 의해 사용한 후 다시 공부하니 훨씬 이해가 잘 되고 왜 사용하는지 조금은 알게 된 거 같다 제이 덕분에 정말 많이 배운거 같다..ㅎㅎ 감사합니다 제이😃😃😃😃

 

https://github.com/nbalance97/java-racingcar/tree/step1

 

GitHub - nbalance97/java-racingcar: 자동차 경주 게임 미션 저장소

자동차 경주 게임 미션 저장소. Contribute to nbalance97/java-racingcar development by creating an account on GitHub.

github.com

 

미션을 하기에 앞서, 미션에 대한 설명을 해주시는데 미션은 페어 프로그래밍으로 진행이 된다고 한다!! 이번 생에 페어 프로그래밍은 처음이라, 많이 어색하기도 하고 서투른 점이 더 많아 어떻게 해야할 지 몰랐으나, 다행히 이번 페어인 레넌이 나에게 많이 맞추어 주어서 쉽게 미션을 해결할 수 있었지 않았나 싶다..ㅋㅋㅋ 레넌에게 압도적 감사를.

 

TDD


테스트 코드부터 작성하고, 테스트 코드를 통과하도록 구현하는 방식으로 개발하는 방법을 말한다. 평소 이론적으로는 TDD, TDD 하면 대충은 알고 있었으나, 실제로 TDD 방식으로 구현해 보니 테스트 코드를 작성하는게 여간 어려운게 아닌 것 같다.

 

여담이지만 처음 TDD로 개발하다 보니 테스트 작성 -> 테스트를 통과하는 최소 코드 작성 과정을 거치지 않고 테스트부터 많이 만들어 두고 테스트를 모두 통과하는 코드를 작성하는 방식으로 했었다..

 

이후 이론 수업을 들어보니 좀 많이 잘못되었다는걸 깨닫고 이후에는 테스트를 하나 작성하고 테스트를 통과하는 최소 코드를 작성하는 방식으로 진행하였다..ㅋㅋㅋㅋ😁😁

 

페어 프로그래밍


페어 프로그래밍은 인텔리제이의 Code with me 플러그인을 사용해서 진행하였다. 이 때, 본인은 윈도우 노트북을 사용하고 페어는 맥북을 사용하여 문제가 있지 않을까 걱정하였으나 다행히 문제가 없었다..ㅋㅋ  페어 프로그래밍에서는 두명이 각각 드라이버와 네비게이션의 역할을 담당한다.

 

드라이버란 키보드/마우스를 잡고 있는 사람, 네비게이터는 옆에 앉아서 함께 작업하는 사람 

 

페어 프로그래밍을 하면서 좋았던 점은 코딩을 하면서 계속해서 생각을 나누니 내가 생각치도 못한 부분을 페어가 커버해주는 느낌이 들어서 개인적으로는 정말 좋았으나.. 내 페어에게 미안한 점이 있다.

종종 네비게이터 역할을 맡을 때, 페어에게 이렇게 구현하는것이 어때요? 하고 제안을 한 다음 실제 구현을 다 하고 나니 어 이게 아닌가? 하고 수정하는 일이 좀 많았다..ㅠ퓨ㅠㅠ😂😂😂 이 점은 좀 공부를 더 해서 빨리 메꿔야 할 것 같다. 

 

코드 리뷰


배정된 리뷰어께 내 작은 코드를 보여드리고 평가를 받아보았다. 리뷰어에게 코드를 보여드리고 평가를 받는건 이번이 처음이라 긴장을 많이 했으나, 리뷰어께서 화가 나셨을지도 모르지만 엄청 친절하게 리뷰를 남겨주셔서 죄송하면서 감사한 마음이 들었다.. 리뷰어가 남겨주신 리뷰 중 기억에 남는 몇가지를 남겨보고자 한다.

 

컨벤션을 잘 지키자

상수와 일반 필드 사이의 개행이 없거나 메서드 사이에 개행이 없는 부분이 좀 있었다. 코딩을 하면서 이런 점을 좀 유의해서 봐야할 것 같다. 또한 배열을 반환하는 함수에서 단수형을 쓰지 말고 복수형을 써야 하는 점 등 네이밍에 유의해서 코딩을 해야 함에 유의!! (getWinner -> getWinners)

 

public String[] getWinner() {

요 친구를 아래 친구로 바꾸어 줌!!

public String[] getWinners() {

 

인스턴스의 최소화

인스턴스 필드에 불필요한 필드가 있는 문제점이 도출되어 꼭 필요한게 아니라면 인스턴스 필드를 최대한 줄여야 클래스의 응집력을 높일 수 있다고 피드백을 받았다. 이에 대체 클래스의 응집력이 무엇일까 하고 찾아보니 각각의 모듈이 고유의 기능을 잘 처리할 수 있는지를 나타내는 정도라고 한다.

 

이번 과제에서는 RandomNumberGenerator의 인스턴스를 RaceController 클래스의 필드에서 가지고 있게 했는데, 피드백을 보고 많이 고민해본 결과 RaceController의 필드에서 굳이 가지고 있어야 할 것 같지는 않고, car 클래스를 생성할 때 new RandomNumberGenerator()을 생성하는 방식으로 변경하였다. 

 

public class RaceController {
    private static final int DEFAULT_POSITION = 0;
    private List<Car> cars = new ArrayList<>();
    private RandomNumberGenerator randomNumberGenerator = new RandomNumberGenerator();

 

요 코드에서 randomNumberGenerator을 Car을 생성할 때 넣어주는 방식으로 구현했었는데, 해당 필드를 지워주고 생성자에서 생성하도록 변경하였다.

 

public void insertCarFromCarNames(String[] carNames) {
        for (String carName : carNames) {
            insertCar(new Car(carName, DEFAULT_POSITION, new RandomNumberGenerator()));

 

테스트 코드 디테일하게 작성

테스트 코드를 작성하는데 있어 정책이 드러나야된다는 피드백을 받았다. 자동차가 이동할 때 테스트의 이름을 그냥 단순하게 자동차_이동, 자동차_중지로 작성했는데, 피드백을 통해 테스트명을 랜덤값이_4이상이_나오면_자동차_이동 으로 바꾸었더니 훨씬 직관적인 테스트 이름이 된 것 같아 뿌듯한 경험이었다.


Lv 1의 첫번째 미션에서부터 스스로 많이 배우고 반성하게 된게 많았다. 코딩을 하면서도 스스로 이게 정말 좋은 코드인가? 메소드로 더 쪼갤순 없을까? 이 메소드가 정말 하나의 동작만 하고 있을까? 하고 계속해서 의심을 하는 스스로를 발견하게 되었으며, 데일리 미팅이나 수업때 참여하는 크루들의 모습을 보며 학습 욕구를 불태우게 되는 것 같다.. 피드백을 바탕으로 더 열심히 해서 좋은 개발자가 되어야 겠다😊

https://github.com/nbalance97/java-pairmatching-precourse

 

GitHub - nbalance97/java-pairmatching-precourse

Contribute to nbalance97/java-pairmatching-precourse development by creating an account on GitHub.

github.com

와.. 기능 구현하는데 시간이 부족하게 느껴져서 머리가 새하얘졌었다..ㅜ 앞서 테스트들과 마찬가지로 기능 목록부터 작성하고 기능을 구현하면서 필요한 내용은 추가적으로 목록에 덧붙이는 방식으로 개발을 진행하였다.  이 때 커밋을 신경쓰면서 개발을 하다보니 커밋할 내용보다 개발을 더 많이 하게 되거나 하는 문제도 있었다. 비록 모든 기능을 구현하지는 못했지만 나름 핵심 기능인 매칭 기능 / 조회 기능까지는 개발을 진행하였다.

 


부족했던 점


  1. 자바 문법이 어색하다.
    • 파이썬으로만 개발을 하다가 최근에야 자바를 다시 시작하다 보니 기능을 구현하는데는 문제가 없지만 중간중간 모르는 방식들은 따로 찾아봐야 하는 문제점이 있었다.
  2. 문제를 자세히 보지 않았다.
    • 크루 인원을 2명으로만 할 수 있는줄 알고 2명으로만 구현했으나 맨 마지막에 3명이 될수도 있었음.. 
    • 문제 힌트에 미리 만들어 둔 클래스가 있었는데 이를 참고하였으면 더 좋게 코드를 작성할 수도 있었을 것 같다.
  3. 로직 자체를 너무 복잡하게 짠 것 같다.
    • 단순하게 작성하였으면 훨씬 좋았을 것 같다.. 각각의 크루에 레벨에서 매칭된 다른 크루 정보를 가지고 있는데, 이것보다는 <크루, 크루> 형식으로 따로 보관해 두었으면 더 효율적으로 작성할 수 있었을 것 같음
  4. 패키지화 하지 않음..
    • 패키지화 하는 연습을 했는데 실전에서 시간에 쫓기다보니 구현을 다 하고 리팩토링 하겠다는 계획대로 되지 않았다.. 미리 패키지화 해서 코딩하는 습관이 필요한 것 같다.  

전체적으로 후련하지 않게 시간에 떠밀려서 제출하게 되어 조금 아쉬웠지만 최선을 다해서 시험을 봤으니 후회는 없었다. 또한 핵심적인 기능은 그래도 구현을 했다고 생각하여 그나마 다행이라고 생각한다ㅎㅎ

 


결과


최종 합격했습니다 ㅎㅎ 2022년엔 우아한 테크코스에서 열심히 배우겠습니다!!

+ Recent posts