Study/우아한테크코스

[우테코] 2주차 프리코스 회고록

개발하는 주디씨 2023. 11. 2. 00:38

 

 

서론

지난 1주차의 엄청난 반성과 깊은 깨달음을 토대로 2주차를 시작했다. 2주차 미션에 앞서 우테코에서 2주차를 설계하며 우테코 코치와 포비가 시사하고자 하는 경험과 목표를 파악하는 것에 중점을 두었다.

 

2주차 목표

  • 1주차에서 학습한 것에 더해 함수를 분리할 것
  • 각 함수별로 테스트를 작성하는 것
    • 테스트가 처음이라면, 언어별 테스트 도구를 학습해보기
    • 작은 단위의 기능부터 작성해보기
    • 단순하고, 있어보이지 않아도 기본에 충실한 테스트케이스 만들기
  • 요구사항을 잘 파악하기
    • 최대한 스스로 해결해볼 것
    • 무작정 다른 사람의 도움을 받는 것은 지양하기(커뮤니티도 되도록 피하기)
  • 기능을 구현하기 전 기능목록을 만들고, 기능 단위로 commit 하기
  • commit 컨벤션 지키기

 

프리코스, 너는 이런녀석이었구나(?)

1주차 피드백과 함께 전달해주신 내용을 통해.. 비로소 프리코스 미션 요구 사항을 어떤 식으로 구현하는지 알 수 있었다. 요구사항을 통해 기능목록을 도출하는 것이 여전히 어렵고 익숙하지 않았지만ㅜㅜ 이번 주차에서는 최대한 주어진 요구사항에서 기능목록을 도출하도록 생각하는 연습에 많은 시간을 투자했던 것 같다. 썼다 지웠다를 거의 100번이상 반복했던 것 같은데ㅋㅋㅋ진짜.. 이렇게 힘들 줄은 몰랐습니다.. (너무 만만하게 생각했네요🤧)

 

기능 목록을 도출하기까지

어느정도 고민을 끝내고 난 후 주어진 기능 요구사항을 정리하며 흐름을 생각하고, 그 과정에서 묶을 하나의 기능이 보이면 묶어주는 방식으로 목록을 작성했다. 객체지향과 특정 패턴에 집착하지 않아도 자연스럽게 객체 단위의 결과가 나와서 놀라운 경험을 할 수 있었다. 커뮤니티에서 많은 분들이 패턴 중심적으로 해야하는지 고민이 많은 것 같은데 처음부터 패턴이라는 이론 자체에 집중하면 마음을 울리는 경험을 하긴 어렵다. 혹시라도 이 글을 보는 동료가 패턴에 대해 고민한다면, 과감히 말해주고 싶다. "패턴은 나중에 깨닫는 것😄미리 알면 재미없습니다"

 

메소드 네이밍, 창작의 고통

기능 목록을 도출하고 난 후 다음으로 네이밍에도 많은 시간을 사용했던 것 같다. '나'만 이해할 수 있는 내용이 아니라, 이름만 보고도 내용을 유추할 수 있도록 주석이 필요없는 직관적인 이름을 짓기 위해 여러가지 단어를 조합해보고 이리저리 생각하느라 하루를 꼬박 보냈다. 커뮤니티를 잠깐 참고하니 다른 분들도 창작의 고통을 함께 겪고 계신 것 같아서 내적 친밀감이 생겨버렸다ㅎ (솔직히 이때 너무 허무하고, 이정도도 못하나?라는 생각이 많이들어서.. 저는 마음고생을 살짝 하기도 했습니다)

 

 


 

 

TDD를 알아가다.

 

요즘 한참 핫한 TDD, 테스트 중심 설계. 이번 주차를 통해 찍먹 해봤다고 생각한다. 정확하게는 왜 써야하는지 그 이유 정도는 알게 되었다. TDD 관련 내용은 얘기가 길어질 것 같아 추후에 포스팅으로 다시 가져오려고 한다. (읽어주실거죠?😉)

 

 

미션 <자동차 경주> 셀프리뷰

1) 문자열 상수로 관리하기

1주차에서 문자열을 그대로 소스 내부에 포함시켰더니, 분리하여 관리하는 게 어떠냐는 피어리뷰를 받았다. 또한 다른 분들의 코드를 리뷰하며 문자열끼리 묶어주니 훨씬 더 가독성이 좋음을 느꼈다. 모르고 있던 내용은 아니지만, 내가 놓쳤던 부분이라 아차! 싶었다. 그래서 이번주차부터는 해당 부분을 잊지않고 바로 적용해보았다.

 

이 문자열은 콘솔에서 출력되는 에러 메시지이다. 에러 메시지의 경우 큰 프로젝트에서는 중요하게 관리되는 요소 중 하나이다. 때문에 문자열, 상수 등 공통으로 관리가 필요한 녀석들은 묶어주는 습관을 가지면 좋을 것 같다.

 

package racingcar.constant;

public class StringError {
    public static final String REQUIRED_CAR_NAME = "자동차 이름을 입력해야 합니다.";
    public static final String REQUIRED_NUMBER = "숫자만 입력해야 합니다.";
    public static final String CAR_NAME_TOO_LONG = "자동차 이름은 5자 이하만 가능합니다.";
}

 

 

2) 컨트롤러는 모든 흐름을 컨트롤하자

이번 미션에서 패키지를 나누며 가장 중요하게 생각했던 부분은 컨트롤러의 역할이었다. 정말 말그대로 컨트롤러는 프로그램의 흐름을 컨트롤 해야한다고 생각한다. 그렇기 때문에 컨트롤러는 전반적인 흐름을 주도해야한다고 판단했다. 지난 과제와는 달리 컨트롤러에서 컨트롤할 하나의 메서드 run()을 두고 그 안에서 주석이 없어도 흐름을 쉽게 읽어낼 수 있도록 노력했다.

 

그리고 그 다음으로는 낭비하는 객체를 만들지 않겠다고 다짐했다. 따라서 게임을 진행하며 단 하나의 컨트롤러 객체가 필요하다는 것을 고려했고, 싱글톤으로 구현해주었다. 이 싱글톤 때문에.. 테스트케이스를 작성하며 잠시 방황했지만, 컨트롤러는 컨트롤러일 때가 가장 잘어울린다고 생각한다..!

(혹시 다른 의견이나 잘못생각하는 부분이 있다면 언제든 코멘트 남겨주세요😉)

package racingcar.controller;

import camp.nextstep.edu.missionutils.Console;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import racingcar.domain.Car;
import racingcar.domain.NumberGenerator;
import racingcar.view.UserInput;
import racingcar.view.UserOutput;

public class RacingGame {
    private static RacingGame instance; // 게임을 진행할 싱글톤 인스턴스
    private final List<Car> cars;

    private RacingGame() {
        cars = new ArrayList<>();
    }

    private static RacingGame getInstance() {
        if (instance == null) {
            instance = new RacingGame();
        }
        return instance;
    }

    public static void run() {

        UserOutput.askForCarNames();
        String[] carNames = UserInput.readCarName();

        RacingGame game = RacingGame.getInstance();
        game.addCar(carNames);

        UserOutput.askForTryCount();
        int rounds = UserInput.readRound();
        game.play(rounds);

        List<String> result = game.getWinners();
        UserOutput.showWinners(result);

        Console.close();
    }
    ...

}

 

run() 메서드를 잠시보면, 주석이 없더라도 세부 코드를 보지 않더라도 일단 한줄씩 읽어나갈 수 있다. 

 

//사용자의 입력을 받아 자동차 이름을 물어본다.
UserOutput.askForCarNames();

//사용자에게 입력받은 자동차 이름을 읽어 String[]에 저장한다.
String[] carNames = UserInput.readCarName();

//레이싱게임의 인스턴스를 재사용하여 객체를 생성해준다.
RacingGame game = RacingGame.getInstance();

//게임 객체에 자동차 이름을 추가하여 게임할 자동차를 추가한다.
game.addCar(carNames);

//시도횟수를 묻는 출력문을 사용자에게 출력해준다.
UserOutput.askForTryCount();

//사용자가 입력한 게임 라운드 횟수를 읽어 int에 저장한다.
int rounds = UserInput.readRound();

//횟수만큼 게임을 진행한다.
game.play(rounds);

//게임의 우승자 목록을 가져와 result에 저장한다.
List<String> result = game.getWinners();

//사용자에게 게임 우승자 결과를 출력한다.
UserOutput.showWinners(result);

//사용한 Console 객체를 닫는다.
Console.close();

 

 

3) Main()의 역할

자바 어플리케이션에서 main은 아주 중요한 역할을 한다. 사실 위에서 말한 컨트롤러 코드를 main()에 적어두어도 충분히 좋은 코드라고 볼 수 있다. 그러나, 내가 굳이 컨트롤러로 분리하고 Main()을 간단하게 쓴 이유는 main()은 프로그램을 실행하는 것이 역할이라고 생각했기 때문이다.

 

즉, 객체는 각자의 역할이 있고 그 역할은 최대한 작게 나누어 1개의 책임만 지도록 하는 것이 목표다. 그런데 main()은 이미 어플리케이션이라는 실행의 역할을 가지고 있기 때문에 여기에 프로그램 전체의 흐름을 담는 것이 부담스러웠다. 그리고 이것이 내 취향이기도 하다🤔ㅎㅎ

 

package racingcar;

import racingcar.controller.RacingGame;

public class Application {
    public static void main(String[] args) {
        RacingGame.run();
    }
}

 

 

 

 

이번 주차도 쉽지 않습니다... 개발자의 길을 멀고도 험하네요...

 

그래도 다들 고생많으셨습니다 :)

 

끝.