[우테코 6기] 프리코스 1주차 회고

pgmjun·2023년 10월 26일
4

우테코 6기

목록 보기
1/5

1주간의 프리코스 🔥

프리코스와 함께 보낸 1주는 좋은 사람들을 많이 만나 행복이 꽉꽉 들어찼던 시간이었다.

열심히 활동해서 그런지 1주 밖에 지나지 않았는데 벌써부터 우테코에 정이 많이 든 것 같다. 살면서 이렇게 몰입했던 적이 많이 없던 것 같다. 좋은 코드를 고민하고, 스터디에 참여하고, 사람들과 질의응답을 주고 받는 등 같은 분야의 많은 사람들과 함께 무언가를 고민하고 즐기던 시간들이 가슴이 벅차오를 정도로 행복했다.

특히 의미있는 고민과 생각을 공유하는 사람들이 정말 많았고, 서로 학습한 내용을 나누며 빠른 속도로 성장할 수 있도록 으쌰으쌰하는 분위기가 너무 즐거웠다. 나 또한 사람들이 더 편하고 재밌게 몰입할 수 있도록 공유에 많은 관심과 시간을 투자했던 것 같다. 그 과정에서 질문을 달아주거나 내용에 살을 붙여주는 사람들이 많았는데, 질문에 답변해주면서 나또한 더 깊이있게 생각해볼 수 있었고 공유가 공유를 낳는 선순환을 경험하며 공유의 의미와 기쁨을 다시 한번 느껴볼 수 있었다.
뿐만 아니라 도움이 되었다고 달아주는 사람들의 댓글 하나하나가 공유를 더욱 즐겁게 만들어주는 동기부여가 되어주었다!

미션을 수행하는 것도 정말 재미있었다. “어떻게 해야 더 가독성이 좋을까?”, “어떻게 해야 더 객체지향적으로 설계할 수 있을까?” 이런 고민들을 하던 모든 순간이 어제의 나보다 오늘의 나를 더욱 성장할 수 있게 만들어준 소중한 시간들이었다.

특히 책임을 바탕으로 역할을 분리하여 도메인을 설계하고, 클린 코드의 원칙을 최대한 지키려고 노력하여 최대한 높은 수준의 코드를 개발해나갔다.

이런 노력을 통해 코드의 가독성이 눈에 띄게 향상되어가는 것을 지켜보며 “왜 객체지향적으로 개발해야하는가?”, “클린 코드에서 강조하는 규칙들은 왜 중요시 되었는가?” 에 대해 몸으로 느낀 신기하고 즐거운 경험을 할 수 있었다.



좋았던 점 😊

💭 매직 리터럴, 매직 넘버 처리

public class BaseballConstant {

    public static final String SIGN_RESTART = "1";
    public static final String SIGN_STOP = "2";
    public static final int THREE_STRIKE = 3;
    public static final int PLAY_AMOUNT = 3;
}
public class GameMessage {

    public final static String GAME_START_MESSAGE = "숫자 야구 게임을 시작합니다.";
    public final static String NUMBER_INPUT_MESSAGE = "숫자를 입력해주세요 : ";
    public final static String RESTART_SELECT_MESSAGE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.";
    public final static String GAME_FINISH_MESSAGE = "3스트라이크\n3개의 숫자를 모두 맞히셨습니다! 게임 종료";
}

매직 리터럴매직 넘버를 잘 처리했던 것이 마음에 들었다.

문자열과 숫자는 그냥 사용하면 나중에 어떤 의미로 작성한 것인지 모르기 때문에 이름을 붙여 상수 또는 enum으로 관리해야한다고 배웠다.
때문에 이렇게 상수 클래스를 생성하여 관리하는 방식으로 코드를 작성해보았다.


💭 일급 컬렉션 사용

Player 일급 컬렉션

public class Player {

    private final List<Integer> numbers;

    private Player(List<Integer> numbers) {
        this.numbers = numbers;
    }

    public static Player from(List<Integer> numbers) {
        return new Player(numbers);
    }

    public int getNumberOf(int index) {
        return numbers.get(index);
    }
}

Computer 일급 컬렉션

public class Computer {

    private List<Integer> numbers;

    private Computer(List<Integer> numbers) {
        this.numbers = numbers;
    }

    public static Computer generate() {
        List<Integer> numbers = new ArrayList<>();
        while (numbers.size() < PLAY_AMOUNT) {
            int randomNumber = Randoms.pickNumberInRange(1, 9);
            if (!numbers.contains(randomNumber)) {
                numbers.add(randomNumber);
            }
        }
        return new Computer(numbers);
    }

    public int getNumberOf(int index) {
        return numbers.get(index);
    }

    public boolean contains(int number) {
        return this.numbers.contains(number);
    }

}

"컬렉션 단 하나만을 필드로 가지고 있는", "컬렉션을 감싸 이름을 붙인" 그런 클래스를 일급 컬렉션 이라고 지칭한다.

이번 미션에선 List<Integer>를 통해 값을 관리해야하는 상황이 존재했다. 때문에 이를 일급 컬렉션으로 관리해보고자 코드를 작성하였다.

일급 컬렉션처럼 이름있는 객체를 생성하여 내부에서 상태행위를 관리하도록 구현하는 것은 이후 유지보수 상황이 발생했을 때, 또는 특정 로직에서 에러가 발생했을 때
수정이 필요한 행위를 하는 객체의 로직만 수정하면 그 로직을 사용한 모든 곳에서 수정이 발생한다.

때문에 이렇게 객체의 역할에 따라 도메인을 분리하여 관리하는 방식은 유지보수의 측면에서 정말 중요하다고 생각한다.



💭 객체의 책임에 따른 역할 분리


객체지향적 관점에서 어떠한 역할에 대한 책임들을 모아 각각 한 곳에서 관리하는 것이 좋다고 한다. 그리고 그 역할들이 메시지를 주고 받도록 구현해야한다고 한다.

이 말의 뜻이 온전히 이해되지는 않았지만, 대략 어떤 느낌인지 이해했고 최대한 이를 적용해보고자 노력헀다.

어떤 기능(책임)이 필요할 지 기능명세서를 작성하며 고민해보았고,
책임에 따라 역할을 분리하고, 분리한 역할을 토대로 도메인 객체를 생성하였다. 그리고 도메인 객체들끼리 실제로 메시지를 주고받다고 상상하며 메서드명을 정의하였다.


play() 메서드를 통한 예시

private GameResult play() {
        Player player = Player.from(inputView.readPlayerNumber());
        GameResult result = new GameResult();

        for (int idx = 0; idx < PLAY_AMOUNT; idx++) {
            int playerNumber = player.getNumberOf(idx);
            int computerNumber = computer.getNumberOf(idx);

            result.update(playerNumber, computerNumber, computer);
        }

        return result;
    }

위는 작성한 코드 중 일부 예시이다.

게임 진행이라는 역할을 가진 BaseballGame 객체에서, 게임 1회 수행 책임을 가진 play()라는 메서드를 수행하는 코드이다.

  • 게임 수행 역할이 입력받기라는 역할을 가진InputViewplayerNumber를 입력받으라는 메시지를 보내고 Player를 생성
  • 플레이어 정보를 알아야한다는 책임을 가진 Player객체와 컴퓨터의 정보를 알아야한다는 책임을 가진 Computer객체에 각각 idx번 번호를 달라는 getNumberOf() 메시지를 보내어 idx번 번호를 응답받는다.
  • 응답받은 number를 바탕으로 게임 결과 관리 책임을 가진 GameResult 객체에 update라는 메시지를 보내어 게임결과 update를 수행한다.
  • 게임은 3개의 숫자를 통해 진행되기 때문에 3이라는 정수값을 담은 PLAY_AMOUNT번 만큼 이를 수행하면 게임의 최종 결과가 GameResult 객체에 담긴다.
  • play() 메서드는 게임 결과를 return함으로써 게임 1회 수행이라는 책임을 다한다.


아쉬웠던 점 😢

💭 else문을 써버린..

개선 전 코드

private void playGame() {
        GameResult result = play();

        if (result.getStrikeCount() == THREE_STRIKE) {
            outputView.printGameFinishMessage();
            String option = inputView.readRestartOrNot();
            if (option.equals(SIGN_RESTART)) {
                computer = Computer.generate();
                playGame();
            } else if (option.equals(SIGN_STOP)) {
                return;
            }
        } else {
            outputView.printGameResultMessage(result);
            playGame();
        }
    }

미션이 완전히 마감되고 사람들한테 코드리뷰를 부탁드리기 전에, 코드를 한번 검토하는 시간을 가졌는데..
게임을 작동시키는 playGame() 메서드에서 else로 분기처리를 했던 코드가 발견되었다..

완전 깜짝 놀랐고 많이 반성했다.

else 사용을 지양해야하는 이유

else문을 사용하면 if 또는 else if로 조건을 걸어 처리해놓은 부분 외의 모든 경우가 else문에서 캐치 되어 내부 로직이 작동되기 때문에 예상하지 못한 상황이 발생할 가능성이 생긴다.


개선 후 코드

private void playGame() {
        GameResult result = play();

        if (result.getStrikeCount() == THREE_STRIKE) {
            outputView.printGameFinishMessage();
            String option = inputView.readRestartOrNot();
            if (option.equals(SIGN_RESTART)) {
                computer = Computer.generate();
                playGame();
            }
        } else if (result.getStrikeCount() != THREE_STRIKE) {
            outputView.printGameResultMessage(result);
            playGame();
        }
    }

위는 내가 개선해본 코드이다.
다음 미션에선 else 문 까지 잘 처리하여 로직을 개발해야겠다..


💭 정적 변수 관리

public class ErrorMessage {

    public static final String NUMBER_FORMAT_ERROR_MESSAGE = "숫자 값만 입력 가능합니다.";
    public static final String NUMBER_LENGTH_ERROR_MESSAGE = "숫자는 3개만 입력할 수 있습니다.";
    public static final String NUMBER_DUPLICATION_ERROR_MESSAGE = "중복된 숫자는 입력할 수 없습니다.";
}

내가 알고 있는 클린코드 규칙 중엔 “매직 리터럴을 제거한다.” 라는 규칙이 있다.

때문에 이번엔 constant 객체를 생성하여 리터럴을 관리하였는데, 얼마 되지 않는 정적 데이터 관리를 위해 따로 클래스를 생성했다는 점에서 살짝 아쉬운 감이 없지않아 있었다.

다음 미션에선 이를 view 객체에서 관리하게 하는 등 조금 더 객체에 책임을 쥐어주어 이를 관리해보고자 한다.



다음 미션 목표 ✅

이제 “다음 미션에선 이런 것들을 더 도입해보고 싶다”에 대해 적어보려한다.

💭 Java 17 적극 활용

6기는 Java 11로 개발하던 5기 프리코스와 다르게 Java 17로 미션을 수행한다. 하지만 1주차 미션을 개발하면서 Java 17의 장점을 잘 활용하지 못했던 점이 많이 아쉬웠다.

record를 사용해볼까 했지만, record는 모든 필드에 대해 Getter를 자동으로 구현하기 때문에 때문에 모든 곳에서 A.필드명 형태로 필드에 접근이 가능했으며
record는 모든 필드가 private final 로 설정되어 있기 때문에 캡슐화가 깨지는 것에 대한 걱정은 줄여주지만 객체의 상태 변경이 불가능하다는 장점이자 단점이 있어 사용하지 못했다.

ex) 예를 들어 이번 미션에선 ballCount, strikeCount 필드를 가진 GameResult 객체를 생성하고 ballstrike를 판단하여 count를 올려주는 로직을 두어 객체 내에서 상태를 변경하고 관리하였다.

아쉬운 마음에 이번 주차 미션이 끝나갈 쯤에 Java17의 기능 중, 미션에 적용하며 학습해볼만한 내용을 공부하던 도중 누군가 프리코스 커뮤니티에 올려준 글을 통해 Text Block이라는 것과
함수형 프로그래밍의 Stream에서 일반적인 List<T>를 return하는 Collections(collect.toList()) 대신 unmodifiableList를 내뱉는 toList() 를 사용하여 가독성을 높임과 동시에 컬렉션을 불변 상태로 리턴 받아 Side Effect를 방지할 수 있다는 점을 학습하였는데, 이를 적용하여 더 좋은 코드를 만들어보고 싶다.

profile
하나씩 천천히 깊이있게 쌓아가는 백엔드 개발자 최승준입니다.

3개의 댓글

comment-user-thumbnail
2023년 10월 26일

헉 파이팅이에요

1개의 답글
comment-user-thumbnail
2023년 10월 31일

잘 봤습니다 :)

답글 달기