on
우아한테크코스 4기 프리코스 2주차 후기 (자동차 경주 게임)
우아한테크코스 4기 프리코스 2주차 후기 (자동차 경주 게임)
728x90
우아한테크코스 4기 프리코스 2주차 후기 (자동차 경주 게임)
https://github.com/woowacourse/java-racingcar-precourse
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 예약어를 쓰지 않는다는 분기문을 최소한으로 하고 객체지향적인 코드로 구현하는 것이다.
또한 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
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/단일_책임_원칙
고찰
지금 2주차 피드백을 적다 보니 지키지 못한 것들도 몇 개 보인다. 자바 컨벤션, 피드백, 요구사항을 모두 생각하면서 구현하다 보니 확인 못한 부분도 많아서 더 아쉬움도 남는다.
피드백을 보면서 '왜?'라는 질문을 많이 하게 된다. 가독성이 좋은 코드를 구현하기 위해 다음과 같은 규칙을 가지고 구현하는 것이겠지만 가끔 굳이 왜 이렇게 구현하지?라는 의문이 남는다. 같이 우테코를 진행한 친구와 얘기를 나눠봤지만 완벽한 답이 나오지는 않았다. 그렇기 때문에 멘토님에게도 물어보고 싶은 것이 많았지만 아쉬운 대로 구글링 해보며 답을 얻고 공부하면서 진행하다 보니 왜 그런지 조금은 알 거 같기도 하다.
1주차 보다 조금 더 성장하고 배운 거 같다. 좋은 코드를 구현하기 위해 더 노력하고 코딩해야겠다.
728x90
from http://se-jung-h.tistory.com/210 by ccl(A) rewrite - 2021-12-27 15:01:34