LALA's blog
[우아한테크코스 4기] 프리코스 1주차 회고 본문
프리코스 1주 차를 끝마쳤다. 🥶
처음 과제를 봤을 땐 어렵지 않은 것 같다고 느꼈는데, 계속 수정해 나갈수록 부족함도 느끼고 어려움도 느꼈던 과정이었다.
부족했던 점
- JAVA 개발이 처음이라 문법이나 IntelliJ 사용이 익숙하지 않아 삽질하는 시간이 꽤 있었다.
- 기능 구현 목록을 정할 때 눈에 보이는 것들만 작성하기 바빴다.
- 설계를 먼저 하지 않고 기능을 구현하는데 집중했다.
- 모든 코드를 class 하나에 다 넣었다.
- 즉, 모든 데이터와 입력, 출력, 계산 수행을 한 class에서 하도록 했다.
- 자료구조를 비교해보지 않고 선택했다.
아래 코드를 보시다시피 제일 처음 기능을 모두 완성했을 땐 한 Class에 모든 기능을 구현했다. 사실 최근에 알고리즘 문제를 많이 풀다 보니 해당 과제를 알고리즘 문제를 푸는 과정처럼 생각하고 빨리 구현해서 테스트를 통과하도록 하는 것에 집중했던 것 같다.
처음 구현 완료 코드
public class BaseBallGame {
private int inputNum;
private LinkedHashSet<Integer> answerNumSet;
private String[] inputNumStrArr;
private int ball;
private int strike;
public BaseBallGame() {
answerNumSet = new LinkedHashSet<>();
}
public void start() {
while(true) {
makeAnswerNum();
play();
if(isFinishedGame()) {
break;
}
}
}
private void play() {
while(true) {
getInputNumByConsole();
if(!checkRangeOfGameNum(inputNum)) {
throw new IllegalArgumentException();
}
inputNumStrArr = makeStringArrayFromInt(inputNum);
initializeScore();
calculateScore();
printScore();
if(isAnswer()) {
System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료");
return;
}
}
}
private void initializeScore() {
ball = 0;
strike = 0;
}
private void makeAnswerNum() {
answerNumSet.clear();
while (answerNumSet.size() < 3) {
int randomNum = Randoms.pickNumberInRange(1, 9);
answerNumSet.add(randomNum);
}
}
private boolean checkRangeOfGameNum(int num) {
String str = Integer.toString(num);
if(str.length() != 3) {
return false;
}
if(str.contains("0")) {
return false;
}
Set<Character> s = new HashSet<>();
for(int i = 0; i < str.length(); i++) {
s.add(str.charAt(i));
}
if(s.size() != 3) {
return false;
}
return true;
}
private void getInputNumByConsole() {
System.out.print("숫자를 입력해주세요 : ");
String strNum = Console.readLine();
inputNum = Integer.parseInt(strNum);
}
private String[] makeStringArrayFromInt(int num) {
return Integer.toString(num).split("");
}
private void calculateScore() {
int i = -1;
for(Integer num : answerNumSet) {
i++;
if(num.toString().equals(inputNumStrArr[i])) {
strike++;
continue;
}
if(isContainedInAnswer(inputNumStrArr[i])) {
ball++;
}
}
}
private boolean isContainedInAnswer(String s) {
for(Integer num : answerNumSet) {
if(s.equals(num.toString())) {
return true;
}
}
return false;
}
private boolean isAnswer() {
int i = 0;
for(Integer num : answerNumSet) {
if(!num.toString().equals(inputNumStrArr[i++])) {
return false;
}
}
return true;
}
private void printScore() {
if(ball == 0 && strike == 0) {
System.out.println("낫싱");
return;
}
if(ball > 0 && strike > 0) {
System.out.println(ball + "볼 " + strike + "스트라이크");
return;
}
if(ball > 0) {
System.out.println(ball + "볼");
return;
}
if(strike > 0) {
System.out.println(strike + "스트라이크");
}
}
private boolean isFinishedGame() {
System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.");
String str = Console.readLine();
if(str.equals("2")) {
return true;
}
return false;
}
}
테스트 코드 통과 후 어떤 부분을 더 해야 할지 찾아보다가 다른 팀원들의 제출 코드를 보았을 때 class를 분리하지 않은 것이 가장 큰 문제점이라는 것이 느껴졌다. 다른 팀원들의 코드를 보고 "아 저게 정답인가? 저렇게 고쳐야 하나?"라는 조급한 마음이 들었다. 하지만 코드를 작성한 것에 서로 다른 의도가 있을 것이고, 힌트로 사용하되 내 방식대로 다시 고쳐봐야겠다고 생각했다.
1) Class 분리
먼저 기능 목록을 자세하게 리스트업하고 프로그램 시나리오를 계속 돌려보며 비슷한 기능들을 묶어서 class로 만들도록 했다.
이렇게 class를 분리하고 나니 기능을 수정하거나 추가하더라도 어떤 class가 담당해야 할지 판단하는데 좀 더 수월했다.
BaseBallGame
- 게임을 실행한다.
- 정답이 될 3자리수 난수를 발생시킨다.
- 사용자(InputView)에게 3 자릿수 입력값을 요청한다.
- Score에게 볼/스트라이크 계산을 요청한다.
- 계산된 힌트를 사용자(OutputView)에게 제공한다.
- 정답일 경우, 사용자(InputView)에게 재시작 OR 종료 값을 요청한다.
InputView (사용자 역할)
- 3 자릿수를 입력한다.
- 재시작 OR 종료 요청을 입력한다.
OutputView (사용자 View 역할)
- 힌트를 출력한다.
- 정답 메세지를 출력한다.
Score
- 볼 / 스트라이크 점수를 계산한다.
Validator
- 입력 값이 유효한 값인지 체크한다.
2) 상수
프로그램 구현시 제한된 숫자 범위나 출력 조건이 있었다.
처음에는 난수의 범위나 입력값의 길이를 직접 숫자를 입력했었는데, 이러한 특정 값들은 상수 class를 만들어서 분리했다.
이후 프로그램의 조건들이 변경됐을 때 해당 코드를 작성한 곳을 하나하나 찾아갈 필요 없이 상수 class에서만 수정하면 되기 때문에 유지보수에 수월할 것 같다.
추가적으로 여러 class들이 사용하는 상수들을 Constant class에 다 모아두었는데, 특정 class 에서만 사용하는 상수는 다른 class에서 접근하지 않도록 해당 class(혹은 pakage 별로?)에서 선언하는 방식으로 수정하면 좋을 것 같은 생각이 든다.
추가한 상수 class
public class Constant {
public static final String EMPTY_STRING = "";
public static final String NOTHING = "낫싱";
public static final String BALL = "볼";
public static final String STRIKE = "스트라이크";
public static final String THREE_STRIKE = "3스트라이크";
public static final String SPACE = " ";
public static final String ZERO_STRING = "0";
public static final int MIN_GAME_NUMBER = 1;
public static final int MAX_GAME_NUMBER = 9;
public static final int GAME_NUMBER_LENGTH = 3;
public static final String PLAY_COMMAND = "-1";
public static final String FINISH_COMMAND = "2";
public static final String RESTART_COMMAND = "1";
public static final String ANSWER_MESSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료";
public static final String INPUT_REQUEST = "숫자를 입력해주세요 : ";
public static final String RESTART_OR_FINISH_REQUEST = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.";
}
3) 자료구조 선택
정답인 3자리 수(answerNum)와 입력값(inputNum)을 저장하기 위해서는 자료구조를 선택하는 과정이 필요했다.
처음에는 중복체크도 할겸 answerNum 을 Set 으로 저장했고, inputNum 은 console에서 받은 String 값을 split() 했을 때 String[]으로 리턴받기 때문에 선택했다. 이후에 어떻게 쓰일지, 다른 자료구조와의 장/단점이 무엇인지 비교해보지 않고, 한마디로 아무 생각 없이 선택했다...😇
결과적부터 말하자면 리팩토링 후 answerNum과 inputNum 둘 다 int[] 배열로 수정했다.
후보군은 이렇게 있었다.
- LinkedHashSet<Integer>
- ArrayList<Integer>
- int[]
LinkedHashSet으로 가지고 있는 것은 숫자 생성/입력 시 중복을 체크하는 데에만 장점이 있고, 이후 answerNum과 inputNum을 비교할 때 인덱스로 접근하는데 어려움이 있기 때문에 제외시켰다. (유효한 값인지 체크할 때, 중복 체크용으로만 사용했다.)
ArrayList와 int[] 배열 중에서 선택해야 했다. JAVA를 처음 사용하다 보니 둘의 차이도 이번에 처음 찾아보게 되었다.
- 배열은 여느 배열과 동일하게 고정된 크기로 선언하여 인덱스로 접근할 수 있다. 연속된 메모리의 공간으로 이루어져 있다.
- ArrayList 또한 인덱스로 접근할 수 있고, 동적으로 크기를 늘릴 수 있다. 불연속적으로 메모리 공간을 차지하며, 설정한 저장 용량 크기를 넘어 더 많은 데이터가 들어오게 되면 배열 크기를 1.5배로 증가시키면서 데이터를 추가하는 방식이다.
- 배열 : 데이터의 크기가 정해져 있고, 추가적인 삽입 삭제가 일어나지 않으며 검색을 필요로 할 때 유리
- 리스트 : 데이터의 크기가 정해져 있지 않고, 삽입 삭제가 많이 일어나며, 검색이 적은 경우 유리
answerNum과 inputNum는 길이가 항상 3으로 정해져 있고, 추가 삽입/삭제는 일어나지 않고 기존 값을 변경하면 되기 때문에 배열을 선택했다. 연속적인 메모리 공간으로 이루어져 있기 때문에 순차적으로 접근하는 데에 더 좋은 방식이라고 판단했다.
private int[] answerNumbers;
private int[] inputNumbers;
최종 코드
이 코드가 정답이라고 확신하진 않지만, 내 생각을 말할 수 있도록 최선을 다해 개선해나갔다. 3주 후에는 더 자신감 있는 코드를 작성할 수 있길!
To do list
- OOP에 대해 공부하기
- 구현 전에 설계 연습하기
- 어떤 코드를 작성하던 왜 이렇게 작성했는지 설명할 수 있는 코드를 짜기
- JAVA 자료구조 타입 공부하기
- Wrapper Class 공부하기
마무리
처음 문제를 봤을 땐 쉽다고 생각했었는데 생각했던 것보다 많은 것을 배우는 시간이었다. 계속해서 수정해나갈수록 부족함이 느껴졌고, 생각할수록 어려운 부분도 많이 있었다. 특히 구현 전에 기능목록 작성과 설계의 중요성을 느꼈다. 중간에 설계를 했지만 또 계속해서 설계를 수정해 나가면서 앞으로 설계를 해나가는 과정을 많이 연습해봐야겠다는 생각이 들었다.
이번에 JAVA 개발이 처음이다 보니 String을 == 이 아니라 equals를 사용해야 한다던지, Character.getNumericValue()와 같은 Wrapper Class의 메소드같은 부분을 잘 파악하지 못하고 있었다. 따라서 JAVA 언어를 익히고 기본적인 개념들을 더 학습할 필요성을 느꼈다.
그리고 다른 팀원들의 코드를 보면서도 많은 것을 배웠다. 혼자만의 코드만 보면서 과제를 했더라면 많은 수정을 해나가진 못했을 것 같다. 누군가의 코드가 정답이라고 생각하진 않지만, 나와 다른 코드를 작성하면서 새로운 방법을 생각해볼 수도 있게 됐다. 그리고 내 코드도 누군가에게 그런 존재가 될 수 있다고 생각하며 좀 더 신경 써서 코드를 작성하게 됐다.
작은 과제를 하면서도 우테코의 과정을 미리 경험할 수 있었던 것 같다. 과제를 통해 본인의 부족한 점을 찾아 주도적으로 학습해나가며 팀원들과 간접적이지만 서로 공유하며 도움을 주고 함께 성장해나가는 과정이었던 것 같다.
규모가 크지 않은 과제이지만 꽤나 수정이 많고 어려움도 있었다. 하지만 계속해서 생각하고 수정 나갈수록 더 잘 해내고 싶다는 감정을 느꼈다! 그래서 내일 나올 2차 과제가 나오는데 또 어떻게 완성해나갈지 걱정 반 기대 반이다. 그리고 3차 과제와 최종 코딩 테스트가 끝났을 때 내가 얼마나 성장해있을지 궁금하고 기대가 된다! 화이팅 🤗
'개발 > 우아한테크코스' 카테고리의 다른 글
[우테코 강의] 자바 List, Generic (0) | 2022.02.25 |
---|---|
[우테코 강의] 문자와 문자열 ( String , StringBuilder , StringBuffer ) (0) | 2022.02.25 |
DTO 란? 장점은? (0) | 2022.02.17 |
[우아한테크코스 4기] 프리코스 3주차 회고 (0) | 2021.12.14 |
[우아한테크코스 4기] 프리코스 2주차 회고 (0) | 2021.12.07 |