이번 우아한테크코스 프리코스 4기에 함께 성장하는 방법을 배워보려 지원했다. 그러기 위해서는 함께 사용할 수 있는 코드(객체지향적 코드)를 작성해야 했고 이를 프리코스라는 과정을 통해 간접적으로 경험해볼 수 있었다.
1주차 과제는 숫자야구게임으로, 1주차 피드백만으로도 충분히 받아들일 수 있었고, 고통스럽게 하는 고민은 없었다. 다만, 2주차 과제에 고민이 있어 객체지향에 대해 고민을 더 많이 해본 선배님께 피드백을 받고 포스팅으로 고민을 공유하려한다. 1, 2주차 래포는 다음과 같다.
1주차
https://github.com/ChoiEungi/java-baseball-precourse
2주차
https://github.com/ChoiEungi/java-racingcar-precourse
1. InputRole의 멤버 변수에 대한 고민
본래 코드는 다음과 같았다. 본 코드에서 고민은 크게 2가지 였다.
- try- catch를 이용해 에러 헨들링을 진행해야 하는데, depth가 늘어나고, 코드의 중복 부분이 많아지고 이후 확장할 때 메서드의 길이가 15을 넘어갈 수 있을 것 같다.
- 꼭 nameList와 trialNumber가 class의 멤버 변수여야 하는가?
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
public class InputRole {
private String[] nameList;
private Integer trialNmber;
public void inputStart() {
while (true) {
try {
inputNames();
break;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
while (true) {
try {
inputTrialNumber();
break;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
public String[] getNameList() {
return nameList;
}
public int getTrialNmber() {
return trialNmber;
}
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class GameController {
public void gameStart() {
InputRole inputRole = new InputRole();
OutputRole outputRole = new OutputRole();
inputRole.inputStart();
changeInputToCar(inputRole);
outputRole.pirntResultInstruction();
for (int i = 0; i < inputRole.getTrialNmber(); i++) {
tryGameOnce();
outputRole.printOneGame(carList);
}
findWinner();
outputRole.printWinner(winnersList);
}
}
우선 1번 고민에 대해서는 try-catch 문의 기능적으로 크게 좋은 솔루션이 없어, 단순히 메서드를 분리하려 했다. 이는 캡슐화가 제대로 이뤄진다고 보기는 어렵지만, 지금 현실적인 상황에서 가장 최선의 방법이었다. 그리고 인풋을 받는 것이 극단적인 스케일인 몇 만개 이런 식으로 늘어나는 변화는 현실적으로 어렵기 때문에, 단순히 메서드를 분리하는 것으로 해결했다. 그러다보니 class 멤버 변수를 크게 사용할 이유가 없었고 inputStart()
메서드에 구애 받기 보다는 Util로서의 Input을 설정했다.
InputRole
이 단순히 인스턴스로서 상태를 갖는 객체일 수 있는데, 어디서든 특정 역할에 대한 인풋을 받고 싶으면 상태를 갖는 것이 아니라, 필요할 때 마다 필요한 인풋을 받아서 인풋의 결과값을 출력해주면 여러 클래스에서 여러 인스턴스를 선언 하지 않고 사용할 수 있게 된다.
고민 후 수정한 코드는 다음과 같다.
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 String[] getNameList() {
while (true) {
try {
return inputNames();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
public Integer getTrialNmber() {
while (true) {
try {
return inputTrialNumber();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
private String[] inputNames() {
System.out.println(NAME_INPUT_INSTRUCTION);
String inputNames = Console.readLine();
String[] nameList = inputNames.split(",");
for (String name : nameList) {
checkNameWhiteSpaceValid(name);
checkNameLengthValid(name);
}
return nameList;
}
private Integer inputTrialNumber() {
System.out.println(TRIAL_NUMBER_INPUT_INSTRUCTION);
String inputNumber = Console.readLine();
checkTrialNumberValid(inputNumber);
return Integer.valueOf(inputNumber);
}
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GameController {
private static final InputRole inputRole = new InputRole();
public void gameStart() {
OutputRole outputRole = new OutputRole();
changeInputToCar(inputRole.getNameList());
outputRole.pirntResultInstruction();
for (int i = 0; i < inputRole.getTrialNmber(); i++) {
tryGameOnce();
outputRole.printOneGame(carList);
}
findWinner();
outputRole.printWinner(winnersList);
}
이 코드를 보면 inputRole을 더 유연하게 사용할 수 있다는 점과 코드가 간결해진 점에서 크게 유용할 수 있었다.
2. Game 도메인 추가
- GameController의 역할을 고민하다 보니 GameController의 역할이 너무 많다는게 느껴졌다. 내가 생각한 역할은 다음과 같다.
- InputRole에서 받아온 후 이를 게임을 진행한 후 OutputRole에 출력하는 역할
- Game을 진행한다.
- 랜덤 숫자 생성
- 게임을 진행한다.
- 게임의 승자를 찾는다.
이러한 방식으로 역할이 나뉘었는데, 1번 2번은 같은 역할로서 볼 수 있겠지만, 3번은 충분히 분리해도 좋을 법할 것 같다는 생각을 해볼 수 있었다. 3번을 분리하기 전에 원래 코드를 보면 다음과 같다.
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
public class GameController {
private static final InputRole inputRole = new InputRole();
private static final int MAX_PICK_NUMBER = 9;
private static final int MIN_PICK_NUMBER = 0;
private static final int MOVE_FORWARD_CONTION_NUMBER = 4;
private final ArrayList<Car> carList = new ArrayList<>();
private final ArrayList<String> winnersList = new ArrayList<>();
public void gameStart() {
OutputRole outputRole = new OutputRole();
changeInputToCar(inputRole.getNameList());
outputRole.pirntResultInstruction();
for (int i = 0; i < inputRole.getTrialNmber(); i++) {
tryGameOnce();
outputRole.printOneGame(carList);
}
findWinner();
outputRole.printWinner(winnersList);
}
private void changeInputToCar(String[] nameList) {
for (String name : nameList) {
this.carList.add(new Car(name));
}
}
private int getRandomNumber() {
int randomNumber = Randoms.pickNumberInRange(MIN_PICK_NUMBER, MAX_PICK_NUMBER);
return randomNumber;
}
private boolean checkMoveForward(int randomNumber) {
return randomNumber >= MOVE_FORWARD_CONTION_NUMBER;
}
private void tryGameOnce() {
for (Car car : carList) {
int randomNumber = getRandomNumber();
if (checkMoveForward(randomNumber)) {
car.moveForward();
}
}
}
private void findWinner() {
int maxValue = findMaxInCarList(carList);
for (Car car : carList) {
if (car.getPosition() == maxValue) {
winnersList.add(car.getName());
}
}
}
private int findMaxInCarList(ArrayList<Car> carList) {
int maxValue = -1;
for (Car car : carList) {
if (maxValue < car.getPosition()) {
maxValue = car.getPosition();
}
}
return maxValue;
}
코드가 너무나도 길다. 그리고 역할이 너무 많다. 이후 확장할 때 코드를 작성하는데 점점 부담이 커질 수 밖에 없는 구조인 것이다. 그렇기 때문에 이를 분리해보면 다음과 같이 코드를 깔끔하게 변경해볼 수 있었다.
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
public class Game {
private static final int MOVE_FORWARD_CONTION_NUMBER = 4;
private static final int MAX_PICK_NUMBER = 9;
private static final int MIN_PICK_NUMBER = 0;
public void startOnce(List<Car> carList) {
for (Car car : carList) {
int randomNumber = getRandomNumber();
if (checkMoveForward(randomNumber)) {
car.moveForward();
}
}
}
public List<String> winner(List<Car> carList) {
int maxValue = findMaxInCarList(carList);
List<String> winnersList = new ArrayList<>();
for (Car car : carList) {
if (car.getPosition() == maxValue) {
winnersList.add(car.getName());
}
}
return winnersList;
}
private int getRandomNumber() {
int randomNumber = Randoms.pickNumberInRange(MIN_PICK_NUMBER, MAX_PICK_NUMBER);
return randomNumber;
}
private boolean checkMoveForward(int randomNumber) {
return randomNumber >= MOVE_FORWARD_CONTION_NUMBER;
}
private int findMaxInCarList(List<Car> carList) {
int maxValue = -1;
for (Car car : carList) {
if (maxValue < car.getPosition()) {
maxValue = car.getPosition();
}
}
return maxValue;
}
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 GameController {
private static final InputRole inputRole = new InputRole();
private final List<Car> carList = new ArrayList<>();
public void gameStart() {
Game game = new Game();
OutputRole outputRole = new OutputRole();
changeInputToCar(inputRole.getNameList());
Integer trialNumber = inputRole.getTrialNmber();
outputRole.pirntResultInstruction();
for (int i = 0; i < trialNumber; i++) {
game.startOnce(carList);
outputRole.printOneGame(carList);
}
outputRole.printWinner(game.winner(carList));
}
private void changeInputToCar(String[] nameList) {
for (String name : nameList) {
this.carList.add(new Car(name));
}
}
}
이렇게 역할(클래스)가 명확하게 분리돼 더 보기 좋고 확장성이 좋은 코드를 작성해볼 수 있었다. 리펙토링한 코드는 choieungi_refactor branch에서 확인해볼 수 있다.
추가적인 소감
객체지향적으로 코드를 변경하려 하니 클래스를 매개변수로 쓰는 것이 아닌, 클래스의 멤버 변수 하나를 매개변수로 쓸 수 있는 등 놓칠 수 있는 부분을 개선하게 되는 계기가 될 수 있었다. 이처럼 코드에 대해 고민을 계속하다보면 정말 필요한 코드만 작성할 수 있을 것이라 느낄 수 있었다.
코드 이외에는 2주차 피드백에서 다른 사람의 코드를 보고 함께 성장할 수 있기에 코드를 작성하면서 느낀 소감을 PR에 올리면 좋을 것 같다는 피드백이 굉장히 인상적이었고 다른 의미로 기뻤다. 나는 1, 2주차 PR에 모두 소감을 올렸는데, 내가 느낀 것들을 다른 사람도 볼 수 있으면 같이 성장할 수 있겠다 느꼈기 때문이다.
프리코스를 통해 단순히 시험의 과정이 아닌, 성장의 과정으로 느낄 수 있었던 뜻깊던 시간이 아닐까 싶다. 그렇기에 프리코스 과정이 굉장히 몰입감을 주고 재미도 있다. 다만, 자만은 하면 안된다. 아직 부족한 점이 많고, 생각해볼 여지가 많은 부분이 있는 코드이므로 3주차 과제에서는 더 큰 고통을 느껴보고 성장해나갈 것이다.
개인적으로는 피드백을 통해 고민해볼 수 있는 기회가 많아졌다. 그렇기 때문에 프리코스 과정 피드백에서 이런 부분을 구현해보면 어떻게 될까요? 와 같이 challenging해볼 수 있는 여지를 줘도 재밌을 것 같다는 느낌이 든다.