[Java] 우테코 프리코스 1주차 - 숫자야구 (2)
다시 시작…
자고 일어나서 다시 어제 쓴 코드를 읽는데, 뭔가 어제 쓸때만해도 좀 감을 잡았다 싶었던 코드가
오징어처럼 보였다…
잠깐 멈추고 생각을 다시해보기로 했다.
객체지향
객체지향에 대해서 근본적인 공부가 필요하다고 생각되서 더 여러가지 코드를 보고 mvc패턴 예제도 더 찾아봤다.
우선 객체에 대해서 다시 생각해 보았다. 이 프로그램을 코드로 보지말고 마치 현실에서 내가 숫자야구 게임을 주최한다면 어떻게 진행할 지 생각해보기로 했다. 그럼 어떤 객체가 필요하고 기능을 어떻게 분리해야할지가 훨씬 명확해 질 것 같았다.
숫자야구 게임을 주최한다면 우선 참가자가 당연히 필요할 것이다. 랜덤으로 세자리를 뽑아줄 컴퓨터도 한대 필요 할 것이고, 이걸 바탕으로 숫자가 맞았는지 틀렸는지 판단하는 심판이 한명 필요할 것이다.
이렇게 그냥 현실에서의 객체로 생각을 하니 조금 더 기능 분담이 명확해졌다.
그래서 지금 Computer가 담당하고 있던, 유저가 입력한 숫자를 판단한 기능을 Referee라는 새로운 클래스를 만들어서 옮겨 주었다.
MVC 패턴
MVC 패턴에서 가장 어렵고 이해가 되지않았던 내용은 컨트롤러의 기능이었다.
이게 뷰와 모델을 이어준다는 느낌인데 정확히 어떤느낌인지 딱 감이 오지 않았다. 그래서 무작정 mvc패턴으로 작성된 예제들은 무작정 여러개 찾아 보았다.
뷰와 모델을 이어서 뷰에서 변화를 요청받으면 이를 모델에 전해주고, 모델의 변경점을 받아서 뷰로 전해주는 중개자라는 설명과 함께 코드를 보니 조금 감이 오는 것 같으면서도 너무 어려웠다…
그렇게 거의 두시간을 mvc만 생각해본 결과, Player 객체와 Computer 객체와 사용자가 보는 View가를 연결해주는 거라면 결국 숫자야구 한판이 진행되는 과정 하나하나가 Controller의 기능이 아닐까하는 생각이 들었다.
그래서 다시 코드를 대대적으로 리팩토링 했다.
Controller
BaseballGame 이라는 클래스를 만들어서 게임 한판도 객체로 만들어주기로 했다.
게임 한판을 하고 싶다면, Computer 하나와 Player한명, 그리고 Referee 한명이 필요하기 때문에
BaseballGame이 시작되면 이 세개의 객체가 생성 되고,
이 세개의 모델과 뷰가 서로 정보를 주고 받으면서 뷰는 모델에 유저의 입력값을 바탕으로 모델의 값을 변경하고,
뷰의 Output 요청을 받으면 갖고있는 정보를 뷰에게 전달해주면 뷰는 이걸 출력한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class BaseballGame {
private Computer computer;
private Player player;
private Referee referee;
public BaseballGame() {
computer = new Computer();
player = new Player();
referee = new Referee();
}
public void start() {
getNumberFromPlayer();
printHint();
}
public void getNumberFromPlayer() {
player.setPlayerNumber(InputView.getNumber());
}
public void printHint() {
OutputView.printHintMessage(referee.getHint(player.getPlayerNumber(), computer.getAnswerNumber()));
}
}
이제 이 컨트롤러를 메인함수에서 사용해 BaseballGame 객체를 하나 생성해주고 BaseballGame.start()
를 하면 1회에 한해서 사용자의 숫자를 입력받고 힌트를 출력한다.
1
2
3
4
5
6
7
public class Application {
public static void main(String[] args) {
//TODO: 숫자 야구 게임 구현
BaseballGame baseballGame = new BaseballGame();
baseballGame.start();
}
}
이렇게 하고나니까 훨씬 코드가 깔끔해졌다는 생각이 들었다.
왜 mvc모델을 사람들이 많이 사용하는지 조금 알 것 같았다!
우선 basballGame.start()라는 이 코드가 너무 마음에 들었다. 가독성이 훨씬 좋아진 느낌이라고 해야하나???
사용자가 숫자야구 게임을 하고 싶다면 BaseballGame 객체를 생성해주고, 이 객체의 start()를 사용해주면 바로 게임이 시작된다.
만약 서로다른 게임을 두개 개최하고 싶다면 객체를 두개 생성해서 객체 두개를 start()해주면 된다.
뿐만 아니라 숫자야구에 기능을 추가하는 경우, 예를들어 심판인 Refree가 힌트 뿐만아니라 오답횟수를 출력해준다던가, Computer가 두개 여서 Player가 두개의 숫자를 맞추도록 룰을 변경할 때도 유지보수가 간편하다.
전자의 경우는 Refree에 가서 오답횟수를 세는 메서드를 하나 더 추가하고 이를 뷰로 연결해서 BaseballGame에서 출력하도록 이어주면되고,
후자의 경우는 BasebllGame 객체가 생성되면 Computer객체가 두개 생성되도록 해주면 된다.
그리고 만약 이 프로그램에 숫자야구 말고 다른 게임을 추가 하고 싶다면, 또 다른 컨트롤러 객체를 만들어서 다른 게임을 진행할 수 있도록 만들면 된다.
리팩토링
다음 기능을 추가하기전에 한번 더 검토할 사항이 있는지 코드를 한줄 씩 읽어 봤다.
내가 코드를 처음 보는 사람이라는 생각으로 다시 코드를 검토했는데, 가독성이 그다지 좋지 않다는 생각이 들었다…
근데 유독 가독성이 좋다고 느껴지는 부분이 있었는데, 바로 BaseballGame 클래스의 start() 메서드 부분이었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class BaseballGame {
private final Computer computer;
private final Player player;
private final Referee referee;
public BaseballGame() {
computer = new Computer();
player = new Player();
referee = new Referee();
}
public void start() {
getNumberFromPlayer();
printHint();
}
public void getNumberFromPlayer() {
player.setPlayerNumber(InputView.getNumber());
}
public void printHint() {
OutputView.printHintMessage(referee.getHint(player.getPlayerNumber(), computer.getAnswerNumber()));
}
}
코드가 짧아서 그런가 싶었지만, start() 부분을 보면 이 메서드가 무슨 일을 하는지 명확하다.
getNumberFromPlayer, 플레이어로부터 숫자를 받아오고, printHint, 힌트를 출력한다.
이 메서드가 궁금하다면 아래로 내려가서 메서드가 하는일을 체크하면 된다.
이 코드를 보니 왜 이름을 알아보기 쉽게 지으라고 했는지, 메서드는 왜 최대한 하나의 일을 하게 만들어야 하는지, 왜 매개변수는 최대한 적게 써야하는지에 대한 해답을 찾은 느낌이었다.
지금까지는 단순히 메서드 이름을 기능을 설명하는데만 충실하게 지으려 했는데, 가독성을 위해서는 기능을 잘 설명할 뿐만 아니라 마치 메서드의 이름들을 모아 놓으면 하나의 책 문장을읽는듯이 읽혀야 된다는 생각이 들었다.
다시 지금까지 썼던 코드를 리팩토링 해보기로 했다.
먼저 PlayerNumber 부터 리팩토링 해보자.
PlayerNumber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class PlayerNumber {
private String playerNumber;
public PlayerNumber(String playerNumber) {
if (isStringLengthCorrect(playerNumber) && isDigitPlayerNumber(playerNumber) && isDifferentPlayerNumber(playerNumber)) {
this.playerNumber = playerNumber;
}
}
public String getPlayerNumber() {
return playerNumber;
}
public static boolean isStringLengthCorrect(String word) throws IllegalArgumentException {
if (word.length() != Constant.USER_NUMBER_LENGTH) {
throw new IllegalArgumentException(SystemMessage.NUMBER_LENGTH_NOT_CORRECT);
}
return true;
}
public boolean isDigitPlayerNumber(String word) {
for (int i = 0; i < word.length(); i++) {
isDigitCharInString(word, i);
}
return true;
}
public static boolean isDifferentPlayerNumber(String word) throws IllegalArgumentException {
Set<Character> set = new HashSet<>();
for (int i = 0; i < word.length(); i++) {
set.add(word.charAt(i));
}
if (set.size() != word.length()) {
throw new IllegalArgumentException(SystemMessage.HAS_SAME_NUMBER);
}
return true;
}
public static boolean isDigitCharInString(String word, int index) throws IllegalArgumentException {
if (!Character.isDigit(word.charAt(index))) {
throw new IllegalArgumentException(SystemMessage.STRING_IS_NOT_NUMBER);
}
return true;
}
}
위에서 읽었을때 너무 복잡하다는 생각이 들었다. 내가 생각한건 PlayerNumber가 생성될때 아래의 유효성 검사를 진행하고 나서야 playnumber라는 클래스 멤버변수가 생기는 건데,
모르는 사람이 한번 봐서 읽기가 쉽지 않았다. 그래서 아래와 같이 리팩토링을 진행했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class PlayerNumber {
private String playerNumber;
public PlayerNumber(String playerNumber) {
isStringLengthCorrect(playerNumber);
isDigitPlayerNumber(playerNumber);
isDifferentPlayerNumber(playerNumber);
this.playerNumber = playerNumber;
}
public String getPlayerNumber() {
return playerNumber;
}
public static void isStringLengthCorrect(String word) throws IllegalArgumentException {
if (word.length() != Constant.USER_NUMBER_LENGTH) {
throw new IllegalArgumentException(SystemMessage.NUMBER_LENGTH_NOT_CORRECT);
}
}
public void isDigitPlayerNumber(String word) {
for (int i = 0; i < word.length(); i++) {
isDigitCharInString(word, i);
}
}
public static void isDifferentPlayerNumber(String word) throws IllegalArgumentException {
Set<Character> set = new HashSet<>();
for (int i = 0; i < word.length(); i++) {
set.add(word.charAt(i));
}
if (set.size() != word.length()) {
throw new IllegalArgumentException(SystemMessage.HAS_SAME_NUMBER);
}
}
public static void isDigitCharInString(String word, int index) throws IllegalArgumentException {
if (!Character.isDigit(word.charAt(index))) {
throw new IllegalArgumentException(SystemMessage.STRING_IS_NOT_NUMBER);
}
}
}
우선 예외를 출력하는 메소드들을 굳이 boolean형으로 반환하지 않아도 된다는 생각이 들었다. 예외 상황이 발생되면 IllegalArgumentException이 알아서 예외를 출력하기 때문에 굳이 if문을 사용하지 않아도 된다.
이렇게 바꾸고 PlayerNumber의 생성자 부분을 보면 전보다 아주 깔끔하게 읽힌다.
마치 한 문장을 읽는것처럼 유효성 검사들을 읽을 수 있다.
AnswerNumber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class AnswerNumber {
private String answerNumber;
public AnswerNumber() {
getNewAnswerNumber();
}
public String getAnswerNumber() {
return answerNumber;
}
public void getNewAnswerNumber() {
ArrayList<String> answerNumberList = new ArrayList<String>();
while (answerNumberList.size() < Constant.USER_NUMBER_LENGTH) {
isNumberInList(getStringRandomNumber(), answerNumberList);
}
this.answerNumber = String.join("", answerNumberList);
}
public static void isNumberInList(String number, ArrayList<String> list) {
if (!list.contains(number)) {
list.add(number);
}
}
public static String getStringRandomNumber() {
return Integer.toString(Randoms.pickNumberInRange(Constant.MIN_RANGE_NUM, Constant.MAX_RANGE_NUM));
}
}
getNewAnswerNumber와 isNumberInList를 보면, 단순히 메소드를 줄이라는 말에 어거지로 두개로 나눠놓은 걸 볼 수 있다.
이러면 메소드 하나가 하나의 일을 하는게 아니라 하나의 일을 억지로 두개의 메서드가 하는 꼴이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class AnswerNumber {
private String answerNumber;
public AnswerNumber() {
getNewAnswerNumber();
}
public String getAnswerNumber() {
return answerNumber;
}
public void getNewAnswerNumber() {
LinkedHashSet<String> answerNumberList = new LinkedHashSet<String>();
while (answerNumberList.size() < Constant.USER_NUMBER_LENGTH) {
answerNumberList.add(getStringRandomNumber());
}
this.answerNumber = String.join("", answerNumberList);
}
public static String getStringRandomNumber() {
return Integer.toString(Randoms.pickNumberInRange(Constant.MIN_RANGE_NUM, Constant.MAX_RANGE_NUM));
}
}
이렇게 코드를 바꿔주었다. 원래는 set을 사용하려 했는데 set함수는 순서가 없어서 입력받은 숫자를 순서대로 출력할 수 없다는 생각에
ArrayList로 구현했었던 거였는데, 역시 찾아보니까 순서가 있는 LinkedHashSet이 있었다…
이렇게 자바에서 이미 지원하는 라이브러리를 사용하니 코드가 훨씬 깔끔해지는 걸 보고, 자바의 라이브러리를 적극적으로 찾아보고 사용해야겠다는 생각이 들었다.
Referee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Referee {
public int[] getHint(String playerNumber, String answerNumber) {
int ballCount = CountingBall(playerNumber, answerNumber);
int strikeCount = CountingStrike(playerNumber, answerNumber);
return new int[]{ballCount - strikeCount, strikeCount};
}
public static int CountingBall(String playerNumber, String answerNumber) {
int ball = Constant.BALL_INITIAL_VALUE;
for (int i = 0; i < playerNumber.length(); i++) {
if (answerNumber.contains(Character.toString(playerNumber.charAt(i)))) {
ball += 1;
}
}
return ball;
}
public static int CountingStrike(String playerNumber, String answerNumber) {
int strike = Constant.STRIKE_INITIAL_VALUE;
for (char userNum : playerNumber.toCharArray()) {
if (answerNumber.indexOf(userNum) == playerNumber.indexOf(userNum)) {
strike += 1;
}
}
return strike;
}
}
어쩌면 지금 짠 코드중에 제일 가독성이 떨어지는 것 같다…
Referee는 getHint 요청을 받으면 PlayerNumber와 AnswerNumber를 비교해서 볼과 스트라이크 개수를 int[] 형태로 반환하는데, OutputView에서 이 값을 바탕으로 볼과 스트라이크를 출력한다.
좀 더 기능을 나누어서 깔끔하게 읽히도록 만들어보자.
1
2
3
4
5
6
7
8
9
10
11
public class Referee {
private final Hint hint;
public Referee() {
hint = new Hint();
}
public int[] getHint(String answerNumber, String playerNumber){
return hint.getHint(answerNumber, playerNumber);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Hint {
private int ball;
private int strike;
public int[] getHint(String playerNumber, String answerNumber) {
resetCount();
ballCount(playerNumber, answerNumber);
strikeCount(playerNumber, answerNumber);
strikeExceptBall();
return new int[]{ball, strike};
}
public void resetCount(){
ball = Constant.BALL_INITIAL_VALUE;
strike = Constant.STRIKE_INITIAL_VALUE;
}
public void ballCount(String playerNumber, String answerNumber){
for (int i = 0; i < playerNumber.length(); i++) {
if (answerNumber.contains(Character.toString(playerNumber.charAt(i)))) {
ball ++;
}
}
}
public void strikeCount(String playerNumber, String answerNumber){
for (char playerNum : playerNumber.toCharArray()) {
if (answerNumber.indexOf(playerNum) == playerNumber.indexOf(playerNum)) {
strike ++;
}
}
}
public void strikeExceptBall(){
ball = ball - strike;
}
}
strike와 ball을 클래스 변수로 만드는게 훨씬 코드가 깔끔해져서, 따로 클래스 변수로 만든 뒤 원시값포장을 위해 Hint 클래스를 따로 만들었다.
리팩토링을 하며 느낀건데, 코드를 우선 한 메소드로 기능적으로 동작하게 먼저 쓰고 메소드를 차분히 분리하는게 훨씬 편한것 같다. 그렇게 메소드를 분리한 뒤에 클래스로 분리 할 수 있다면, 클래스로 분리 하면 된다.
1
2
3
4
5
6
7
8
public int[] getHint(String playerNumber, String answerNumber) {
resetCount();
ballCount(playerNumber, answerNumber);
strikeCount(playerNumber, answerNumber);
return new int[]{ball, strike};
}
이제 힌트를 가져다주는 getHint 메서드가 아주 깔끔해졌다. count들을 리셋해주고,
볼을 세고, 스트라이크를 센 다음 볼에서 스트라이크를 빼고 이 둘의 값을 배열로 반환해주면 된다.
게임의 승패 유무를 판단하는 기능 추가
만약 3스트라이크 인 경우 “3개의 숫자를 모두 맞히셨습니다! 게임종료” 문구 출력과 함께 게임을 새로 시작할것인지 프로그램을 종료 할 것인지 입력을 받아 처리해야한다.
우선 3스트라이크인지 판단하는 기능은 심판인 Referee가 담당하도록 해주어야 한다고 생각했다.
1
2
3
4
5
6
public boolean isThreeStrike() {
if (hint.getStrike() == Constant.GAME_SET_STRIKE_VALUE) {
return true;
}
return false;
}
Referee에 위와 같이 3스트라이크를 판별하는 메서드를 추가해 주었다.
1
2
3
public static void printGameSetMessage(){
System.out.println(SystemMessage.GAME_SET_MESSAGE);
}
게임이 종료된걸 알리기 위한 메시지를 출력하는 printGameSetMessage()를 OutputView에 추가해주고, BaseballGame에서 종료 할지 말지를 결정하도록 했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void start() {
do {
getNumberFromPlayer();
printHint();
} while (!isGameSet());
}
public boolean isGameSet() {
if (referee.isThreeStrike()) {
OutputView.printGameSetMessage();
return true;
}
return false;
}
isGameSet() 메서드가 3스트라이크인지를 판별해서 3스트라이크라면 게임을 종료하고 게임 종료 메시지를 출력한다.
리팩토링
OutputView에 대한 코드를 보다보니, OutputView는 오직 출력에 관한 기능만 가져야 할 것 같은데, 들어온 힌트를 판단해주는 기능까지 갖고 있었다.
OutputView를 조금 더 OutputView 답게 만드는 리팩토링을 진행 하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class OutputView {
public static void printBallMessage(){
System.out.printf(SystemMessage.BALL_MESSAGE);
}
public static void printStrikeMessage(){
System.out.printf(SystemMessage.STRIKE_MESSAGE);
}
public static void printNothingMessage(){
System.out.printf(SystemMessage.NOTHING_MESSAGE);
}
public static void printCount(int count){
System.out.print(count);
}
public static void printGameSetMessage(){
System.out.println(SystemMessage.GAME_SET_MESSAGE);
}
}
힌트를 판단하는 기능은 BaseballGame 컨트롤러에서 담당해주도록 하였다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class BaseballGame {
private final Computer computer;
private final Player player;
private final Referee referee;
public BaseballGame() {
computer = new Computer();
player = new Player();
referee = new Referee();
}
public void start() {
do {
getNumberFromPlayer();
printHint(getHint());
} while (!isGameSet());
}
public void getNumberFromPlayer() {
player.setPlayerNumber(InputView.getNumber());
}
public void printHint(int[] hint) {
printBallHint(hint);
printStrikeHint(hint);
printNothingHint(hint);
printEnter();
}
public static void printBallHint(int[] hint) {
if (hint[0] != 0) {
OutputView.printCount(hint[0]);
OutputView.printBallMessage();
}
}
public static void printStrikeHint(int[] hint) {
if (hint[1] != 0) {
OutputView.printCount(hint[1]);
OutputView.printStrikeMessage();
}
}
public static void printNothingHint(int[] hint) {
if (hint[0] == 0 && hint[1] == 0) {
OutputView.printNothingMessage();
}
}
public int[] getHint() {
return referee.getHint(player.getPlayerNumber(), computer.getAnswerNumber());
}
public static void printEnter() {
System.out.println();
}
public boolean isGameSet() {
if (referee.isThreeStrike()) {
OutputView.printGameSetMessage();
return true;
}
return false;
}
}
이렇게 해주면 모델의 상태에 따라서 사용자에게 보여주는 출력을 바꿔주는게 됨으로 훨씬 더 View는 View답고 Controller는 더 Controller의 기능에 충실해진 것 같다.
게임 종료가 되면, 다시 게임을 진행할지 완전히 프로그램을 종료할 지를 출력한다.
이 기능을 구현하기 전에 한참 고민을 했다. 이 기능은 과연 Computer가 담당해야 하는 기능인지, 아니면 Referee가 담당해야 할지 아니면 BaseballGame 컨트롤러에 추가 해야할지 고민이 됐다.
그래서 만약 이 프로그램이 후에 여러 게임을 가진 프로그램이 될수도 있다고 생각하고 가정해보았다.
이 프로그램에 숫자야구 게임 뿐만아니라, 새로운 게임이 추가된다고 생각해보자.
이제 이 프로그램이 시작하면 어떤게임을 플레이 할지 선택하고, 그 게임이 종료되면 이 게임을 한번 더 할지, 다른 게임을 다시 선택하러 갈지, 프로그램을 종료할 것인지 고를수가 있는 기능 추가가 필요해질 것이다.
이때 숫자야구 게임의 종료여부를 묻는 기능은 어디 클래스에 있을때 가장 유지보수가 편할까??
이렇게 생각해보니, 게임 재시작과 종료 여부를 묻는 이 기능은 아예 BaseballGame과 독립된 클래스가 가져야할 기능 같았다.
그래서 이 기능은 프로그램 자체가 가져야 할 기능 같아서, Application 클래스에 추가하기로 했다.
RetryNumber 클래스
재시작 여부를 받을 숫자의 유효성 검사를 해주기 위해서 RetryNumber로 원시값인 int retryNumber를 포장해주었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RetryNumber {
private String retryNumber;
public RetryNumber(String retryNumber) {
isCorrectRetryNumber(retryNumber);
this.retryNumber = retryNumber;
}
public void isCorrectRetryNumber(String number) throws IllegalArgumentException {
if (number != Constant.GAME_RETRY_NUMBER || number == Constant.PROGRAM_END_NUMBER) {
throw new IllegalArgumentException();
}
}
}
들어온 문자열이 “1” 이나 “2”가 아닌 경우는 IllegalArgumentException을 출력한다.
Application 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Application {
public static void main(String[] args) {
//TODO: 숫자 야구 게임 구현
do {
BaseballGame baseballGame = new BaseballGame();
baseballGame.start();
} while (askRetry());
}
public static boolean askRetry() {
RetryNumber retryNumber = new RetryNumber(InputView.getRetryNumber());
if (retryNumber.getRetryNumber() == Constant.GAME_RETRY_NUMBER) {
return true;
}
return false;
}
}
askRetry() 메서드를 만들어 앞서 만든 retryNumber에 값을 받아 처리하도록 한다.
만약 retryNumber 가 1이라면 재시작, 아니라면 게임을 종료한다.
이로써 모든 프로그램 구현이 완료되었다.
테스트도 모두 통과했고, 내가 직접 프로그램을 돌려 잘 작동 하는 것을 확인 하였다.
아쉬운점
첫번째로는 깃, 커밋을 이렇게 기능단위로 해본적이 없어서 초반에 좀 엉망진창 커밋이 됐는데, 다음 과제 때는 정말 완벽하고 깔끔하게 해보고싶다.
두번째로는 TDD 사실 이건 너무 무리해서 초반부터 적용해보려 했으나, 아예 구조를 어떻게 짜야 할지 상상이 안돼서 처음에는 해보다가 나중에는 적용하지 못했다. 다음 과제부터는 TDD를 제대로 해서 코드를 짜보고 싶다.
느낀점…
진짜 이렇게 간단한 프로그램에 이렇게 시간을 쏟아본적이 처음이다.
코드를 하나 작성하기 전에 기본 최소 30분 이상씩은 고민만 하다가 코드를 작성하느라 너무너무 시간이 오래걸렸다. 5일 가량 동안 거의 30시간에 가까운 시간을 투자해서 작성을 완료했다.
처음에는 구조를 짜는데에 굉장히 많은 시간을 투자했다. 어떤 기준으로 패키지를 나누고 기능을 나눠야 할지 정해져야 좋은 코드가 나올 것같아서 mvc패턴에 대해 공부하고, 클래스 분리와 메소드 분리를 하는데만 이틀을 넘게 쓴것 같다.
지금 생각해보면 무작정 기능만 돌아가게 작성하고 리팩토링하는게 훨씬 속도가 빨랐겠다는 생각이 들지만, 5일동안 머리를 싸매고 좋은 코드란 무엇인가에 대해 고민하는 시간이 재밌었다.
이렇게 열심히 작성한 코드를 1대1 피드백을 못받는게 너무나 아쉽다. 아직도 궁금하고 모호한게 너무 많아서 만약에 우테코에 못가더라도 꼭 수준있는 사람을 수소문해서 코드 리뷰를 받아보고 싶다.
이번 기회를 통해 클린코드가 중요하다는건 알고있었지만, 직접 클린코드에 대해서만 머리를 싸매보면서 클린코드가 ‘왜’ 중요한지를 절실하게 깨달을 수 있는 순간이었고, 앞으로도 어떤 언어로 어떤 일을 하던간에 이 개념은 평생 갈고 닦을 생각이다.
힘들고 처음에는 너무 막막했지만, 한편으로는 뭔가에 집착해서 이렇게 노력하는 과정이 너무나 즐거웠다. 마지막에는 거의 내가 변태가 된것 마냥 불편한점들을 쥐잡듯이 찾아다니는 모습을 보며, 깊은 뿌듯함을 느끼기도 했다.
첫번째 과제만에 이렇게 큰 성장을 이뤘는데 3주가 지나면 얼마나 더 성장해있을지 기대된다.
댓글남기기