영호
우아한테크코스 5기 프리코스 2주차 후기 본문
개요
1주 차에는 코딩 테스트 형식의 문제들이 나왔고, 2주 차에는 프리코스를 검색해보면 항상 나오는 숫자 야구 게임이 나왔습니다. 1주 차 미션을 진행하면서 무엇을 신경 쓰면서 진행했는지 정리해보겠습니다.
무엇을 신경 썼는가
- 데이터 중심이 아닌 책임에 중심을 둔 설계
- 캡슐화
- 객체에서 외부에 공개할 메서드와 내부에 감출 메서드
책임에 중심을 둔 설계
그동안 저는 DB, 애플리케이션을 설계할 때 구현해야 하는 기능을 보고 '이런 데이터가 있어야겠네'라고 생각하면서 설계를 해왔습니다. 그러나 객체 지향적인 설계를 위해선 데이터 중심이 아닌 구현해야 하는 기능을 수행할 적절한 객체를 찾고, 구현 시점에서 필요한 데이터가 무엇인지 생각해야 된다는 내용을 알게 됐습니다.
그래서 저는 구현해야 하는 기능 목록을 정리하고 해당 기능을 수행하기 위한 객체들이 무엇이 있는지 찾고 이를 더욱더 세분화하면서 설계 및 구현했습니다. 이 과정에서 계속해서 설계가 바뀌는 과정을 겪고 설계는 정말 정확한 정답이 있는 게 아니라는 것을 체감할 수 있었습니다.
캡슐화
캡슐화에 대한 설명은 너무 많이 나와있기 때문에 생략하겠습니다.
저는 숫자 야구 게임의 결과인 strike, ball의 개수를 저장하는 BaseballResult라는 객체를 생성했습니다. 그리고 해당 데이터를 가지고 할 수 있는 결과 출력, 게임 종료 조건 판단 기능을 해당 객체에 같이 정의했습니다.
캡슐화를 지키니 다음과 같은 이점을 얻었습니다. 처음에 strike, ball을 각각 int타입으로 저장을 하고 있었지만, 이후, Map 자료구조로 변경을 했습니다. 하지만, 이 변경이 BaseballResult객체 외부에 영향을 미치지 않았습니다. 데이터와 해당 데이터를 처리하는 메서드를 객체가 자율적으로 진행하고 있기 때문입니다.
그래서 다음 미션을 진행할 때도 캡슐화에 더 신경 쓸 예정입니다. 다음 미션이라고 해봤자 오늘 나오긴 하지만,,
객체 외부 공개 메서드
캡슐화와 비슷한 맥락입니다. 객체가 외부에 구체적인 구현을 공개하지 않도록 하기 위해 노력한 부분입니다. 사용자가 정답을 입력하는 기능을 예로 들겠습니다. 입력에 대해서는 아래와 같은 조건이 있습니다.
- 1~9로 이루어져야 한다.
- 중복되는 숫자는 올 수 없다.
- 3자리로 이루어진다.
이제 해당 유효성을 검사하는 PlayerInputValidator객체를 만들고 1, 2번에 해당하는 유효성 검사 메서드를 각각 만들었다고 가정합시다. 그렇다면 외부에서 입력이 유효한지 알기 위해 1, 2, 3번 메서드를 각각 호출해야 할까요? 아니면 객체 내부에서 validateInput() 메서드를 생성하고 1, 2, 3번 메서드를 호출해 최종적으로 유효한 입력값인지 알려주는 메서드를 만들어 이를 외부에 노출시키는 게 좋을까요.
저는 후자가 좋다고 생각합니다. 왜냐하면 나중에 유효성 조건이 추가될 경우 PlayerInputValidator내부만 수정한다면 외부는 변경으로 인한 영향을 받지 않습니다. 그러나 전자처럼 외부에서 각각의 메서드를 호출하는 방식으로 구현한다면 외부 코드도 수정하는 영향이 발생합니다. 아래 코드는 후자의 방식으로 구현해본 코드입니다.
이러한 부분을 다음 미션에선 더욱 잘 지키면 보다 좋은 코드를 작성할 수 있지 않을까 기대하고 있습니다.
public class PlayerInputValidator {
private final static int PLAYER_ANSWER_CORRECT_SIZE = 3;
private final static String PLAYER_ANSWER_REGEX = "^[1-9]*$";
public List<Integer> validatePlayerAnswerInput(String playerInput) {
validateThreeLength(playerInput);
validateSatisfiedRange(playerInput);
List<Integer> playerAnswerNumbers = convertStringToIntegerList(playerInput);
validateIsNonDuplicateNums(playerAnswerNumbers);
return playerAnswerNumbers;
}
private List<Integer> convertStringToIntegerList(String nums) {
List<Integer> integerList = new ArrayList<>();
for (char num : nums.toCharArray()) {
integerList.add(Integer.parseInt(String.valueOf(num)));
}
return integerList;
}
private static void validateThreeLength(String inputNumbers) {
if (inputNumbers.length() != PLAYER_ANSWER_CORRECT_SIZE) {
throw new IllegalArgumentException();
}
}
private static void validateSatisfiedRange(String inputNumbers) {
if (!inputNumbers.matches(PLAYER_ANSWER_REGEX)) {
throw new IllegalArgumentException();
}
}
private static void validateIsNonDuplicateNums(List<Integer> input) {
long individualNumberCount = input.stream()
.distinct()
.count();
if (individualNumberCount != PLAYER_ANSWER_CORRECT_SIZE) {
throw new IllegalArgumentException();
}
}
}
private 메서드는 어떻게 테스트하지?
테스트 코드를 작성하다 보니 private메서드에 대해 테스트를 하려는 순간 멈칫했습니다. 외부에서 해당 메서드를 접근할 수 없기 때문입니다. 그래서 구글링 한 결과 제가 선택한 방법은 해당 private메서드를 사용하는 public메서드를 이용해 테스트하는 것입니다. 그래서 저는 아래와 같이 public메서드를 이용해 테스트 코드를 작성했습니다.
class PlayerInputValidatorTest {
PlayerInputValidator playerInputValidator = new PlayerInputValidator();
@Test
void 사용자_입력이_유효성_검사를_통과하면_리스트반환() {
assertThat(playerInputValidator.validatePlayerAnswerInput("451"))
.isEqualTo(List.of(4, 5, 1));
}
@Test
void 사용자가_입력한_정답이_세글자가_아니면_예외발생() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> playerInputValidator.validatePlayerAnswerInput("1234"));
}
@Test
void 사용자가_입력한_정답에_문자가_있다면_예외발생() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> playerInputValidator.validatePlayerAnswerInput("12"));
}
@Test
void 사용자가_입력한_정답에_중복숫자가_있으면_예외발생() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> playerInputValidator.validatePlayerAnswerInput("313"));
}
}
아쉬웠던 점
사실 처음 제출을 하니 '예기치 못한 에러 발생'문구가 나와서 코드에서 잘못된 부분을 찾으려고 했지만 찾지 못하고 결국 객체들에 나눠놨던 메서드들을 하나의 파일에 모았습니다. 이후, 제출 성공을 확인한 뒤 다시 설계하고, 구현을 하면서 어찌어찌 제출은 성공했습니다. 그러나 이 과정에서 기존에 작성했던 테스트 코드들을 삭제하고 다시 꼼꼼하게 테스트 코드 작성을 못한 것이 아쉬운 점으로 남았습니다.
위에서 언급은 안 했지만 함수 별로 테스트 코드를 작성하는 것도 이번 미션을 진행하면서 많은 신경을 쓴 부분 중 하나이기 때문입니다.
그리고 리팩토링을리팩터링을 할 때도 변경해야 하는 사항들을 정리한 뒤 진행해볼 생각입니다. 머릿속으로만 생각하고 바로 리팩토링을 하다 보니 중간에 꼬여서 다시 처음으로 돌리는 경험을 한 뒤, 종이에 변경할 구조를 그리고 진행하니 빠르게 리팩토링을 할 수 있었습니다.
또한 테스트를 작성하다 보면 테스트 코드 작성이 어려웠던 메서드들이 있었습니다. 그래서 테스트하기 좋게 코드를 짜는 방법에 대해서도 알아본 뒤, 다음 미션에 적용하고 싶습니다.
다음 미션을 진행할 때는 이번 미션을 진행하면서 지키려고 한 요소뿐만 아니라 아쉬웠던 점을 보완하기 위해 노력해볼 예정입니다.
'우아한테크코스5기' 카테고리의 다른 글
도메인 객체의 ID부여 (1) | 2023.06.04 |
---|---|
우아한테크코스 5기 최종 코딩 테스트 회고 (2) | 2022.12.21 |
우아한테크코스 5기 프리코스 4주차 후기 (1) | 2022.11.25 |
우아한테크코스 5기 프리코스 3주차 후기 (0) | 2022.11.16 |