우테코 프리코스 2주차 회고

김한준 (Hanjun Kim)·2022년 11월 8일
0

회고

목록 보기
1/4

들어가며

2주차 미션이 시작되고 프리코스 커뮤니티가 열렸을 때 가장 기대한 건 피어 리뷰였다. 동기들의 코드를 보며 피드백을 받기도하고 내가 배운 내용을 바탕으로 동기들에게 도움을 주고 싶은 마음도 컸다. 그런데 막상 리뷰를 남기려다보니 쉽사리 피드백을 남길 수 없었다. 혹여나 미숙한 내 실력으로 잘못된 내용을 전달하진 않을까 그로 인해 동기에게 피해가 가진 않을까 싶은 불안감 때문이었다.

그래서 이번 미션을 진행하면서는 뭔가를 배우더라도 좀 더 정확하게 배우려고 노력했던 것 같다. 검색을 하더라도 여러 자료를 참고했고, 공식 문서를 읽어보기도 했다. 단순히 알고 넘어가는 것과 정확히 이해하는 것은 천지차이인 것 같다.

학습한 내용

Git

2주차에도 1주차와 마찬가지로 Git에 대해 학습했다. 2주차에는 프리코스 피드백에 첨부된 자료들을 바탕으로 origin, upstream의 개념과 각종 git 명령어의 기능, 사용법에 대해 학습했다.

Git 학습 내용 정리

또한 2주차 미션을 진행하며 git reset 명령어를 자주 사용했는데, 기계적으로 사용하기보다는 정확이 어떤식으로 동작하는지 명확하게 이해하고자 'Git reset 명확히 이해하기' 자료를 통해 reset의 기능을 시각적으로 학습하고 이해할 수 있었습니다.

커밋과 브랜치의 동작에 대해 학습할 때는 개인적으로 Learning Git branch 사이트가 정말 많은 도움이 되었다. 해당 사이트에서 브랜치 동작을 실험해보며 코드의 결과를 한눈에 확인 할 수 있어서 정말 좋았다.

Git을 공부하는 것은 현재 미션을 수행함에 있어서도 도움이 되지만 특히나 미래에 팀원들과 협업을 진행할 때 정말 큰 밑거름이 될 거라 생각하기에 매주 조금씩이라도 꾸준히 학습을 이어나갈 예정이다. 꾸준함의 결과로 미래의 팀원에게 폐를 끼치질 않길, 또한 Git 활용에 애를 먹는 동료에게 도움을 줄 수 있길 기대한다.

객체지향의 사실과 오해

1주차 미션을 진행하며 <클린코드> 책의 초반부를 학습했다. <클린코드>를 읽으며 단순히 블로그에 정리된 글을 읽는 것보다 직접 책을 읽는 것에서 오는 깨달음이 더 많다는 걸 경험했다. 저자의 충분한 설명과 자세한 예시를 토대로 이해하니 직접적으로 와닿는 느낌이었다. 이러한 경험을 이어나가고자 <객체지향의 사실과 오해>를 구매했다.

2주차 미션을 진행하면서 객체를 사용하는 일이 더 잦아졌다. 객체지향 언어를 다룸에 있어서 객체에 대한 이해는 당연 필수적이다. 객체를 더 잘 다루고 더 깊이 이해하고자 객체지향의 사실과 오해를 읽게 됐다.

아직 초반부이긴 하지만 확실히 책을 읽고 나니 객체의 역할, 책임, 협력 관계에 대해 생각해보게 됐다. 이전엔 아무 생각 없이 멤버 변수를 추가하고 메서드를 찍어냈었던 것 같다. 하지만 앞으론 객체의 본질에 맞게끔 역할, 책임, 협력을 끊임없이 의식하며 설계할 수 있도록 의식적으로 노력할 것이다.

객체지향의 사실과 오해 학습 내용 정리

아직 풀리지 않은 궁금증들

기능 테스트

개인적으로 이번 미션을 진행하면서 가장 많은 시간을 할애한 부분이다. 기능을 테스트한다는게 말은 단순해도 정말 애매하고 어려운 작업이었다. 2주차 미션을 진행하면서의 내 커밋 프로세스는 다음과 같았다.

  1. 기능 목록을 작성한다
  2. 기능을 구현한다.
  3. 테스트한다.
  4. 리팩토링한다.

그런데 막상 기능 구현까지 마치고나서 테스트를 해보려하니 테스트하기가 정말 애매했다. 예를 들어 '정답을 맞추면 게임을 종료하는 기능'을 다음과 같이 구현했다. (생략된 코드도 있다.)

public static void start() {
	Balls computer = Balls.createRandom();
    GameResult gameResult = GameResult.createEmpty();

	while (!gameResult.isAnswer()) {
    	Balls user = getBallsWithUserInput();
        gameResult = computer.getGameResultVersus(user);
    }
}

그렇다면 여기서 start() 메소드 자체를 테스트하는 건 개인적으로 까다롭다고 생각했다. 우선 테스트 코드에서 정답을 입력하는 과정이 추가되어야 했고 임의로 생성된 computer의 값 또한 정해줘야 했다. 뿐만 아니라 게임이 정상적으로 종료되는지 확인해야했고 그렇지 않으면 게임이 반복되고 있는지도 확인해야 했다.

처음엔 이 기능을 어떻게 테스트해야할까 너무 막막했다. 테스트만을 위한 메서드를 추가하는 것도 고려해봤지만 아무리 생각해도 좋은 방법은 아닌 것 같았다. 원활한 테스트를 위해 기능 구현 목록을 수정하는 것도 고민해봤다. 하지만 그러면 너무 테스트에 의존적으로 커밋 프로세스를 변경하는 것 같은 느낌을 받았다.

그래서 생각을 바꾸고 최대한 단순하게 생각하기로 했다. 게임을 종료한다는 건 결국 while문에서 빠져나와야 한다는 걸 의미한다. 그래서 '정답을 맞추면 게임을 종료하는 기능'은 getGameResultVersus 메서드가 정상적으로 값을 반환하는지 테스트 하는 것으로 대체했다.

이 방법이 좋은 방법이라고 생각하지 않는다. 기능 테스트란 결국 함수 단위로 진행된다고 알고있었다. 하지만 나는 start() 함수를 내 손으로 직접 테스트하지 못했다. start() 함수 내부에 호출된 작은 기능들을 따로 테스트 하는 것에 만족할 뿐이었다. 결국 start() 메서드 자체에 대한 테스트는 기존에 주어진 ApplicationTest에 위임해버렸다. 테스트 역량이 부족하다는 게 무엇인지 몸소 느낄 수 있었던 시간이었다.

테스트에 대해 고민하다보니 다시 1주차에 했던 기능 목록 작성에 대한 고민으로 돌아가게 됐다. 결국 이 모든 고민이 내가 '기능 목록을 효과적으로 작성하지 못했기 때문이 아닐까?' 라는 생각도 들었다. 최대한 빨리 동기 분들과 이 주제에 대해 얘기를 나눠보고 싶다.

입력값 검증의 위치

2주차 미션을 진행하며 입력값 검증을 어디서 해야할지에 대해 고심했던 것 같다. 객체의 생성자에서 검증을 진행해야 할지 아니면 사용자로부터 입력을 받자 마자 검증을 진행해야 할지 이 두 선택지 사이에서 고민하고 고민하고 또 고민했다.

일단 가장 합당한 방법은 생성자 내부에서 입력값을 검증하는 것이라 생각했다. 사용자가 입력한 값을 그대로 생성자에 넘겨주고 잘못된 값을 입력하면 객체를 만들지 않도록 구현하는게 가장 합리적인 방법 같았다.

그런데 생각해보면 입력값 검증이 클라이언트 단에서 이루어지는 경우도 많다. 예를 들면 비밀번호 생성과 같은 경우 값을 넘기기 이전에 검증이 이루어진다. 단순 문자열의 경우 검증이 모두 완료된 상태로 값을 넘기는 것도 좋은 방법인 것 같았다.

또한 검증의 책임을 객체에게 넘기는 것에 대해서도 고민해볼 문제라고 생각했다. 아무리 검증 로직을 Validator로 넘긴다고 하더라도 입력 값을 그대로 가져오게되면 검증 이후에 값을 정제해서 객체 생성에 적합한 인자로 변환하는 로직이 추가되야한다. 생성자 코드가 불필요하게 길어질 수도 있겠다라고 생각했다. 이러한 점에서 확실히 클린코드에 가까워 보이는 건 값을 입력하자마자 검증하는 것이었다.

우선 2주차 미션은 생성자에 검증된 값을 넘기는 방식으로 진행했다. 아직 값을 검증하고 생성자로 넘길지, 생성자로 넘기고 나서 검증해야할 지, 둘 중 어떤 방법이 더 좋은지에 대한 확신이 없다. 프리코스 동기들의 코드를 참고해보면서 좀 더 고민해봐야 할 것 같다.

정적 팩토리 메서드의 무분별한 사용

이번 미션에서 정적 팩토리 메서드를 적극 활용해봤다. 확실히 생성자를 통한 방식보다 코드를 이해하기 더 수월하고 네이밍의 변화를 주면서 다양하게 활용할 수 있다는 점도 좋았다. 다만 '내가 너무 정적 팩토리 메서드를 남용하는게 아닌가?' 라는 느낌도 받았다.

static에 대해 공부하면서 static 변수의 사용은 최대한 지양해야한다는 글을 읽은 적이 있다. static 변수를 사용하면 변수에 저장된 값이 프로그램 시작부터 끝까지 남아있어 메모리 낭비가 일어날 수 있다. 또한 의도치 않게 static 변수에 남아있던 값으로 인해 에러가 발생할 위험도 있다.

static 변수가 이렇다면 static 메서드를 사용하는 것은 어떤가? 에 대해 궁금증이 생겼다.

public static Balls create(String userInput) {
    return new Balls(userInput);
}

위 코드처럼 단순히 생성자를 대신하는 용도로 사용하는 것은 괜찮다고 생각한다. 다만

public static Balls createRandom() {
    ArrayList<Integer> randomNumbers = getRandomNumbers();
    return create(
        randomNumbers.get(FIRST),
        randomNumbers.get(MIDDLE),
        randomNumbers.get(LAST)
    );
}

static ArrayList<Integer> getRandomNumbers() {
    ArrayList<Integer> randomNumbers = new ArrayList<>();
    while (randomNumbers.size() < PROPER_LENGTH) {
        int randomNumber = Randoms.pickNumberInRange(START_INCLUSIVE, END_INCLUSIVE);
        if (!randomNumbers.contains(randomNumber)) {
            randomNumbers.add(randomNumber);
        }
    }
    return randomNumbers;
}

위와 같이 정적 팩토리 메서드가 또다른 static 메서드를 필요로 한다면 그로 인해 발생하는 문제는 없을까? static에 대해 더 공부하고 고민해봐야할 것 같다.

상수 처리

이번 미션에서는 재사용되지 않는 상수는 해당 클래스의 상단에 static final로 선언해두었고, 재사용이 필요한 상수들은 Enum과 Interface로 분리했다. 상수 처리에 대해 고민하면서 확실히 Enum을 사용하는게 여러가지 이점이 있다는 것을 알게되었다. 하지만 직접 사용해보면서 'Enum 값을 불러오는 과정이 가독성흘 해치진 않는가?'에 대해 많은 고민을 했다.

if (quitOrRestart.equals(UserResponse.RESTART.getValue())) {
    start();
}

위와 같은 코드에서 getValue() 부분이 상당히 눈에 거슬렸다. 클래스 내부에 선언한 상수라면 단순히 RESTART로 끝났을 텐데 Enum을 사용하니 UserResponse.RESTART.getValue()로 길어져버렸다.

static import를 사용하여 조금이나마 코드 길이를 줄일 수 있었겠지만 자칫 잘못하면 가독성을 오히려 해칠 수도 있다고 생각했다. 지금까지 했던 고민들의 결과는 '간단한 상수의 경우 굳이 Enum을 사용할 필요가 있을까?' 였다. 상수를 관리는 과정에서 해당 상수에 추가적인 기능이나 상태가 필요해진다면 Enum을 사용하는 것이 무조건 좋겠지만 단순히 값만 넘겨줘야하는 상황이라면 Enum이 아닌 Interface를 사용하는 것도 좋은 선택이라고 생각했다.

우선 앞으로 getValue() 메서드의 네이밍을 바꿀 것인지 아니면 다른 방법이 더 있는지 찾아보고 고민해봐야 할 것 같다. 그리고 Enum에 대해서도 더 공부해볼 것이다. Enum을 배우면 배울수록 Enum의 필요성을 더 느끼게된다. Enum을 잘 다룰 수만 있다면 코드의 품질을 한 차원 더 높일 수 있을 것 같다.

마치며

프리코스 2주차도 정말 빠르게 지나갔다. 한 주 동안 불확실함과 애매함에 둘러쌓인 채 미션을 진행한 것 같은 느낌이 든다. 그럼에도 1주차보다 발전했느냐? 라고 묻는다면 '그렇다!'라고 자신있게 대답할 수 있을 것 같다. 좋은 이름을 짓는 과정도 저번주 보다 수월해졌고 Git을 다루는 것 또한 손에 점점 익어간다. 내 코드를 읽어내려가면 '그래도 이정도면 수월하게 읽힌다!' 라는 좋은 느낌이 들기도한다. 가장 중요한건 이젠 동기들에게 조금이라도 의미있는 피드백을 남길 수 있을 거 같다는 자신감이 생겼다는 것이다.

이제 곧 3주차 미션이 시작된다. 미션이 시작되기 전까지 밀린 궁금증들을 최대한 빨리 해소하고 공유하고 싶다. 물론 돌이켜 보면 '정말 멍청한 궁금증이었구나!'라는 사실이 밝혀질지도 모른다. 그래도 상관없다. 멍청한 질문에서도 배울 건 있다고 생각한다!

profile
조금 느려도 꾸준한 성장을 추구합니다.

1개의 댓글

comment-user-thumbnail
2022년 11월 10일

잘보고갑니다!
상수와 관련해서 저는 개인적으로 클래스 내부에 상수가 옹기종기 모여있으면 좀 지저분해보여서
static import를 사용해서 enum으로 분리하는것이 더 좋다고 생각해요!

답글 달기