본문 바로가기
후기

우아한테크코스 4기 프리코스 2주차 후기 (자동차 경주 게임)

by clean_h 2021. 12. 27.
728x90

우아한테크코스 4기 프리코스 2주차 후기 (자동차 경주 게임)

https://github.com/woowacourse/java-racingcar-precourse

 

GitHub - woowacourse/java-racingcar-precourse: 자동차 경주 게임 미션을 위한 저장소

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

github.com

4기 프리코스 2주차는 자동차 경주 게임 미션이다.

intellij 실행화면

 


진행방식은 세 가지 요구사항인 기능 요구사항, 프로그래밍 요구사항, 과제 진행 요구사항을 만족하기 위해 노력하면서 구현하는 것이다.

미션에 대한 요구사항은 https://github.com/woowacourse/java-racingcar-precourse README.md을 통해 확인할 수 있다.

 

기능 요구사항

초간단 자동차 경주 게임을 구현한다.

  • 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
  • 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
  • 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
  • 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
  • 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
  • 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
  • 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
  • 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
  • 아래의 프로그래밍 실행 결과 예시와 동일하게 입력과 출력이 이루어져야 한다.

 

프로그래밍 요구사항

  • JDK 8 버전에서 실행 가능해야 한다.
  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용(예 -while문 안에 if 문이 있으면 들여쓰기는 2)j
  • 3항 연산자를 쓰지 않는다.
  • 함수가 한 가지 일만 하도록 최대한 작게 만들어라.
  • camp.nextstep.edu.missionutils에서 제공하는 Randoms, Console API를 활용해 구현해야 한다.
    • Random 값 추출은 camp.nextstep.edu.missionutils.Randoms의 pickNumberInRange()를 활용
    • 사용자가 입력하는 값은 camp.nextstep.edu.missionutils.Console의 readLine()을 활용
  • 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
  • else 예약어를 쓰지 않는다. (switch/case도 허용하지 않는다.)
  • Car 객체를 활용해 구현해야 한다.

 

과제 진행 요구사항

  • 미션은 저장소를 Fork/Clone해 시작한다.
  • 기능을 구현하기 전에 구현할 기능 목록을 정리해 추가한다.
  • 커밋 컨벤션을 참고하여 commit log를 남긴다.

 


 

else 예약어를 쓰지 않는다. (switch/case도 허용하지 않는다.)

이번 추가된 요구사항 중 else 예약어를 쓰지 않는다 라는 조건이 있다. 

처음 이 조건을 읽었을 때 else를 쓰지 않고? 어떻게 구현을 해야하지? 라는 의문부터 들었다. 생각만으로는 else를 무조건 사용해야만 할 때도 존재할텐데 라는 생각이 들었다. 

 

다음 블로그를 읽고 else 예약어를 쓰지 않는 이유를 알 수 있었다.

https://velog.io/@lxxjn0/else-%EC%98%88%EC%95%BD%EC%96%B4%EB%A5%BC-%EC%93%B0%EC%A7%80-%EC%95%8A%EB%8A%94%EB%8B%A4

 

else 예약어를 쓰지 않는다

이 글은 우아한테크코스 리뷰 페이지에 함께 게시된 글입니다. The ThoughtWorks Anthology의 더 나은 소프트웨어를 향한 9단계: 객체지향 생활 체조 중 규칙 2에 대한 내용이다.

velog.io

 

 

정리해보면 else 예약어를 쓰지 않는다는 분기문을 최소한으로 하고 객체지향적인 코드로 구현하는 것이다.

또한 else는 'if 조건을 만족하지 않을 때'라는 의미이다. if 조건 이외일 때 이므로 어느 조건일 때 실행하는지 정확하지 않기 때문에 가독성이 떨어지게 되고, 오류 발생가능성도 증가한다.

그렇기 때문에 else 예약어를 사용하지 않는다. 

else를 사용해야할 때는 enum을 사용하거나, if문에서 바로 return을 해주는 방법으로 else를 사용하지 않을 수 있다. 


Car객체를 활용해 구현해야 한다.

package racingcar;

import camp.nextstep.edu.missionutils.Randoms;

public class Car {
    private final String name;
    private int position = 0;

    public Car(String name) {
        this.name = name;
    }

    // 추가 기능 구현
    public String getName(){
        return this.name;
    }

    public int getPosition(){
        return this.position;
    }

    public void CarPlay(){
        RandomNum();
        PrintPosition();
    }

    private void RandomNum(){
        int num = Randoms.pickNumberInRange(0,9);
        if(num >= 4){
            position ++;
        }
    }

    private void PrintPosition(){
        System.out.print(name + " : ");
        for(int i=0; i<position; i++){
            System.out.print("-");
        }
        System.out.println();
    }

}

Car 객체는 다음과 같다.

 

일단 객체란 어떠한 속성값(필드 : field)과 행동(메소드 : method)을 가지고 있는 데이터이다. 

여기서 Car가 객체이고, 속성값은 name, position을 의미하고 행동은 CarPlay, RandomNum 등 메소드를 말한다. 

자동차 이름을 입력받고 자동차의 객체를 생성한 후 List에 저장할 수 있다.

 

조건 1: name, position 변수의 접근 제어자인 private을 변경할 수 없다.

private로 선언하는 이유는 캡슐화와 보안 때문이다. 해당 클래스 내부에서 사용할 수 있도록 private로 선언한다. 

 

조건 2: setPosition(int position) 메소드를 추가하지 않고 구현한다 하였다.

setPosition을 추가하지 않는 이유는 setter를 사용함으로써 setter에 대한 의존석이 매우 높아진다. setter로 접근이 쉬워지면서 객체의 일관성, 안정성을 보장받기 힘들다.

setter 대신 객체를 생성할 때 생성자를 통해 데이터를 받아서 처리한다.

public Car(String name) { this.name = name; }

다음과 같이 name은 setter를 사용하지 않고 생성자를 통해 name을 받아올 수 있다. 

 

또한 클래스 내부 메소드를 통해 position의 값을 변경할 수 있다.

 


 

IllegalArgumentException

IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다. 

    // 자동차 이름 입력
    public String[] InputCarName(){
        String carsString = Console.readLine();
        String[] cars = carsString.split(",");
        try {
            IsValidName(cars);
        } catch (IllegalArgumentException e){
            System.out.println(e.getMessage());
            return InputCarName();
        }
        return cars;
    }
    
	private void IsValidName(String[] cars){
        for(String car : cars){
            if(car.length() > 5) {
                throw new IllegalArgumentException("[ERROR] 이름은 5자 이하만 가능하다.");
            }
        }
    }

throw를 사용하여 강제로 예외를 발생시키고 IllegalArgumentException()에 메시지를 저장한다. 강제로 catch문으로 이동한 후 getMessage를 통해 에러 메시지를 출력하고 입력을 다시 받는다.

 

다음과 같이 try catch, throw 구문으로 예외처리를 해줄 수 있다. 

 


2주차 피드백

  • 기능 목록 구현을 재검토한다

클래스 이름, 함수는 언제든지 변경될 수 있기 때문에 기능 목록을 너무 상세하게 작성하지 않는다. 

구현해야 할 기능 목록을 정리하는 데 집중한다.

정상적인 경우도 중요하지만, 예외적인 상황도 기능 목록에 정리한다. 

 

  • 값을 하드 코딩하지 마라

문자열, 숫자 등의 값을 하드 코딩하지 마라

상수 static final을 만들고 이름을 부여해 이 변수의 역할이 무엇인지 의도를 드러낸다. 

 

  • 축약하지 마라

축약하기보다 의도를 드러낼 수 있는 이름을 사용한다. 의도가 드러날 수 있도록 이름을 짓는다. 

 

  • 함수(메서드) 라인에 대한 기준

공백 라인도 한 라인에 해당하고 함수 15라인으로 제안한다.

 

  • commit 메시지에 "#번호"를 추가하지 않는다

github에서 #번호는 다른 이슈 또는 Pull Request를 참조할 때 사용한다. 

 

  • 발생할 수 있는 예외케이스에 대해 고민한다

정상적인 경우를 구현하는 것보다 예외 상황을 모두 고려해 프로그래밍하는 것이 더 어렵다. 예외 상황을 고려해 프로그래밍하는 습관을 들인다. 

 

  • 주석은 꼭 필요한 경우만 남긴다

함수의 역할이 무엇인지는 함수의 이름으로 충분히 의도를 드러냈기 때문에 주석은 꼭 필요한 것이 아니면 남기지 않는다.

 

이 부분에서 약간 의문이 든다. 나는 대체로 나와 다른 사람들 모두 편하게 알기 위해 주석을 많이 쓰는 편인데 꼭 필요한 주석이란 무엇인가? 하는 의문이 든다.  

 

  • git을 통해 관리할 자원에 대해서도 고려한다

.class 파일은 java 코드가 있으면 생성할 수 있다. 따라서 .class 파일은 굳이 git을 통해 관리하지 않아도 된다. 

intellij의 .idea 폴더, eclipse의 .metadata 폴더 또한 개발 도구가 자동으로 생성하는 폴더이기 때문에 굳이 git으로 관리하지 않아도 된다.

 

  • Pull Request를 보내기 전 브랜치를 확인한다

기능 구현 작업을 fork된 Repository의 main branch가 아닌, 기능 구현을 위해 새로 만든 브랜치에서 작업한 후 PR을 보낸다.

 

  • java에서 제공하는 api를 적극 활용한다

java api에서 제공하는 기능인지 검색을 먼저 해본다.

 

  • 배열 대신 java collection을 사용하라

java collection 자료구조(List, Set, Map 등)를 사용하면 데이터를 조작할 때 다양한 api를 사용할 수 있다.

 

  • 객체에 메시지를 보내라

상태 데이터를 가지는 객체에서 데이터를 꺼내려(get) 하지 말고 객체에 메시지를 보내라.

 

  • 필드(인스턴스 변수)의 수를 줄이기 위해 노력한다

필드(인스턴트 변수)의 수가 많은 것은 객체의 복잡도를 높이고, 버그 발생 가능성을 높일 수 있다.

필드(인스턴트 변수)에 중복이 있거나, 불필요한 필드가 없는지 확인해 필드의 수를 최소화한다.

 

  • 비즈니스 로직과 UI 로직을 분리해라

비즈니스 로직과 UI 로직을 한 클래스가 담당하지 않도록 한다. 단일 책임의 원칙에도 위배된다.

 

단일 책임 원칙은 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다.

https://ko.wikipedia.org/wiki/단일_책임_원칙

 

단일 책임 원칙 - 위키백과, 우리 모두의 백과사전

객체 지향 프로그래밍에서 단일 책임 원칙(single responsibility principle)이란 모든 클래스는 하나의 책임만 가지며, 클래스는 그 책임을 완전히 캡슐화해야 함을 일컫는다. 클래스가 제공하는 모든

ko.wikipedia.org

 


 

고찰

지금 2주차 피드백을 적다 보니 지키지 못한 것들도 몇 개 보인다. 자바 컨벤션, 피드백, 요구사항을 모두 생각하면서 구현하다 보니 확인 못한 부분도 많아서 더 아쉬움도 남는다.

피드백을 보면서 '왜?'라는 질문을 많이 하게 된다. 가독성이 좋은 코드를 구현하기 위해 다음과 같은 규칙을 가지고 구현하는 것이겠지만 가끔 굳이 왜 이렇게 구현하지?라는 의문이 남는다. 같이 우테코를 진행한 친구와 얘기를 나눠봤지만 완벽한 답이 나오지는 않았다. 그렇기 때문에 멘토님에게도 물어보고 싶은 것이 많았지만 아쉬운 대로 구글링 해보며 답을 얻고 공부하면서 진행하다 보니 왜 그런지 조금은 알 거 같기도 하다. 

1주차 보다 조금 더 성장하고 배운 거 같다. 좋은 코드를 구현하기 위해 더 노력하고 코딩해야겠다. 

 

728x90

댓글