⚾️ 1 주차 미션은 숫자 야구!!
이번 6기 1주 차 미션은 온보딩 미션이 아닌 숫자 야구가 주어졌다!
https://github.com/woowacourse-precourse/java-baseball-6
내 코드는 다음 링크에서 볼 수 있다. (열리는데 시간이 조금 걸릴 수 있다.)
https://github.com/woowacourse-precourse/java-baseball-6/pull/319
⭐️ 코드 리뷰
나는 1 주차 미션이 끝나자마자 바로 코드 리뷰를 받고자 하였다.
다른 사람들도 마찬가지인 듯하였다. 미션 종료 1시간 전부터 디스코드에서는 리뷰를 대기하는 사람들이 모여있었다.
그래서 나는 전략을 바꿔서 피드백을 드리겠다고 글을 올렸다.
내가 피드백을 주었으면 그 사람도 나한테 피드백을 줄 가능성이 높다고 생각하였다 ㅎㅎ 그래서 위와 같이 글을 작성하였다.
그러자 30분도 안돼서 19 명의 동료들이 댓글을 달아주셨다.. 그래서 허겁지겁 더 이상 받지 않겠다고 글을 올렸다.
너무 많이 받으면 피드백을 다 못 드릴 것 같았다...😂
하지만 나는 총 30명의 동료들에게 리뷰를 드렸다 ㅎㅎ
리뷰를 통해 정말 많은 것을 배울 수 있었다.
생각보다 피드백을 드리는 것은 어려웠다.
물론 다른 사람의 코드를 읽는 것에 어려움이 있었지만, 무엇보다 피드백을 드릴 때 어떻게 말해야 내 의도를 잘 전달할 수 있을지 고민되었다.
"이렇게 말하면 너무 싹수없어 보이지 않나??", "이렇게 말하면 상대방이 기분이 나쁘지 않을까??"라는 생각이 계속 들어서 문장을 계속해서 수정하면서 피드백을 작성하였다.
역시 대면으로 피드백 주는 게 최고일 것 같다.ㅎㅎ (그래서 이후에 스터디에 참여해볼까 한다.)
그리고 나는 총 17 명의 동료들에게 피드백을 받았다.
(다시 한번 코드 리뷰를 해주신 17 명의 동료들에게 감사의 말씀을 전합니다!🙇♂️)
정말 많은 분들에게 피드백을 받다 보니 내 코드는 피드백으로 덮여 있어서 코드를 읽는데 어려움이 생길 정도였다.
심지어 내 PR 링크를 열려고 하면 너무 오래 걸려서 페이지가 열리지 않았다..
정말 정말 많은 것을 배울 수 있었다.
배운 내용들은 2 주차에서 활용해 볼 생각이다!
🤔1주 차 미션동안 고민했던 것
나는 우테코를 준비하면서 이전에 풀어보았기 때문에 큰 무리 없이 요구사항을 파악하고 구현할 수 있었다.
그럼에도 여전히 고민하고 배운 것들이 있기에 여기서 한 번 정리해보려고 한다!
🧐 검증의 위치?
1주 차 미션을 진행하면서 가장 고민했던 것은 검증 클래스를 어디에 위치할지에 관한 것이었다.
기능 요구사항에서 사용자는 컴퓨터의 숫자를 맞추기 위해 숫자를 입력해야 하는데 이 부분에서 검증이 필요했다. 확인해야 할 검증은 다음과 같았다.
- 빈 값인 경우
- 숫자가 아닌 문자가 포함된 경우
- 3 자리가 아닌 경우
- 같은 숫자가 반복되는 경우
이러한 검증을 어디에서 하는 게 맞을지에 대해 많은 고민을 하였고 다양한 방법을 시도하였다.
1. View에서 검증
이 부분에 대해 고민하던 중 가장 처음 생각한 방법은 InputView
에서 검증하는 것이었다.
위와 같이 처리해 주면 Controller
는 검증된 값을 받기 때문에 따로 추가로 검증해 줄 필요가 없어져 매우 깔끔해졌다.
그러나 위 코드에서 확인할 수 있듯이 InputView
가 “입력받기” + “검증하기” 두 가지의 책임을 가지게 되면서 코드가 복잡해졌다. 이 두 가지의 책임을 분리해 줄 필요성을 느끼게 되었고 이를 분리하였다.
2. 책임의 분리
예외 검증 로직은 Validator
라는 별도에 클래스를 만들어서 책임을 분리하였다. 이렇게 하니까 InputView
에 검증에 대한 메서드가 정의되어 있지 않기 때문에 깔끔해 보였다.
그러나 InputView
에 검증 메서드가 정의되어 있지 않다고 하더라도 InputView
내부에서 검증 메서드를 알고 가져와 사용하고 있기 때문에 여전히 검증에 대한 책임에서 벗어났다고 보기에는 힘든 것 같았다.
3. 검증 로직을 Controller로 옮기기
따라서 이를 해결하기 위해 InputView
에서 검증 로직을 실행하는 것이 아닌 Controller
에서 검증 로직이 실행되도록 리팩터링 해주었다.
이와 같이 리팩터링 하게 되면서 InputView
는 매우 단순하게 바뀌었고 ‘입력받기’에 대한 하나의 책임만 가지게 되었다. 그럼에도 여전히 생각해 볼 문제가 있는 것 같다.
InputView
의 존재가 무의미해진 것 같다. 이 상태라면 굳이InputView
가 필요할까?- 입력값 오류 시 다시 입력받으려면 로직이 매우 복잡해진다.
이와 같은 문제를 해결하기 위해 어떠한 방법을 사용하면 좋을지 계속 고민하였다.
4. 근본적인 문제에 대한 고민, 그리고 검증 구분
그동안은 “왜 책임을 분리하려고 하는가?”에 대해 고민해 보지 않고 단순히 책임 분리에만 집중하여 최대한 클래스의 책임을 나누어서 분리하였다. 그러다 문득 왜 책임을 분리해야 하는지에 대해 집중해서 고민하게 되었다.
이에 대한 나의 답은 “변경의 영향을 최소화하기 위해서”였다. 그렇다면 비즈니스 로직과 상관이 없어서 변경할 가능성이 거의 없다면 굳이 책임을 분리해 줄 필요 없다고 판단하였다.. 그래서 나는 우선 검증을 두 가지로 분리하였다.
- 비즈니스 로직 검증
- 기초적인 검증
요구사항이 자주 변경되는 비즈니스 로직과 관련된 검증, 그리고 크게 변경 가능성이 없는 기초적인 검증으로 나누었다. 그리고 InputView
에 기초적인 검증은 남겨주었고, 비즈니스 검증은 기존대로 Validator
라는 별도의 클래스에서 관리해 주었다.
또한 메서드가 출력할 메시지를 받는 방식으로 리팩터링 하여 공용적으로 사용하도록 바꿨다. 이로써 두 가지 예외로 구분하면서 예외 발생 시 어느 클래스에서 발생했는지 파악하는 것만으로도 단순히 일반적인 예외인지, 비즈니스 예외인지를 구분할 수 있게 되었다. 또한 요구사항이 변경하더라도 InputView
를 수정할 필요가 없어졌다.
이번 경험을 통해서 근본적인 문제 해결에 집중해야 한다는 것을 깨달았다. 이러한 깨달음을 가지고 단순한 규칙에 사로잡히지 않고 더 가독성 있고, 더 유지보수하기 쉬운 코드에 대해 고민해 나가야겠다!!
👀 어떻게 하면 더 가독성 있게 작성할 수 있을까?
이번 프리코스를 기회로 가독성 있는 코드를 작성하는 능력을 많이 키워보고 싶었다. 그래서 최대한 코드를 가독성 있게 작성하려고 노력하였다. 아래 내용은 어떻게 하면 더 가독성 좋은 코드를 만들 수 있을지 스스로 고민하고 적용했던 내용이다.
1. 함수 분리를 통해 가독성 높이기
기능을 구현하고 난 뒤 Controller
를 살펴보는데 한눈에 어떤 작업을 하는지 보이지 않았다. 그래서 이에 대해 고민하던 중 게임 진행 메서드인 proceed
를 다음과 같은 단계로 나누어서 가독성을 높이는 방법을 생각하였다.
- 게임 초기화
- 게임 진행
- 게임 종료
그러나 리팩터링 과정에서 문제점이 발생한 것을 알았다.
게임 초기화
단계에서 초기화했던 computer
를 게임 진행
단계에서 사용해야 하기 때문에 위와 같이 파라미터로 computer
를 받아야 했다. 지금 단계에서는 별로 큰 영향이 없어 보이지만, 만약 프로그램 요구사항이 증가하고 기능들이 추가된다면 매번 새로운 값이 파라미터가 추가될 상황이 발생할 가능성이 높아 보였다. 그래서 이 부분을 다음과 같이 개선해 주었다.
GameData
라는 모델을 생성하여 게임 초기화
, 게임 진행
에서 각각 접근하도록 리팩터링 해주었다. 이를 통해 새롭게 접근할 값이 필요한 경우 파라미터를 추가할 필요 없이 GameData
에 필드값만 추가해 줄 수 있다. 또한 미학적으로도 통일성 있게 바뀌어져 가독성이 올라갔다고 느껴졌다.
그러나 이 방법이 100% 정답이라고 생각되지는 않았다. 코드를 읽는 사람 입장에서는 어떠한 값이 넘겨지는지 한눈에 파악되지 않다. GameData
내부를 탐색해 봐야 알 수 있기 때문에 코드의 복잡성이 올라가고, GameData
의 필드 개수가 증가한다면 오히려 가독성을 해칠 수도 있겠다고 생각하였다. 어떠한 방법이 최선인지 계속 탐색해 봐야겠다.
이후에 JDK14부터 지원하는 record
를 사용해 보았다.
2. 함수화를 통해 가독성 높이기
코드를 읽다 보면 위와 같이 비교문에서 코드를 해석하는데 시간이 걸리면서 가독성이 떨어진다고 느껴졌다.
그래서 한눈에 어떤 작업인지를 파악할 수 있게 아래와 같이 따로 함수로 추출하였다.
또한 함수명을 최대한 어떤 작업을 하는지 알 수 있도록 명명하였다.
그러나 이러한 방식을 너무 많이 사용하면 오히려 함수 개수가 너무 많아져서 큰 구조로 볼 때는 가독성을 해칠 수 있다는 생각이 들었다. 이를 방지하기 위해서 함수로 추출할지 여부를 판단하는 명확한 기준을 세울 필요성을 느꼈다.
🏗️ 확장 가능한 설계
1주 차 미션의 요구사항은 간단하였다. 그래서 만약 여기서 기능이 확장되거나 변경된다면 어떻게 변할지 생각해 보고 확장 가능하게(변경하기 쉽게) 리팩터링 하기로 결심하였다.
우선 내가 기획자라고 생각하고 프로그램의 요구사항이 변한다면 어떻게 변할지 가정해 보았다.
다음은 내가 스스로 생각한 변경 사항이다.
- 숫자 개수의 변화 (3자리 수에서 5자리나, 2자리로 변경 가능)
- 허용 숫자의 변화 (1~9 에서, 0~5 까지만 허용 등으로 변경 가능)
이와 같이 변경 사항을 작성하고 실제로 변경해 보면서 어떤 점이 불편했고 어떤 식으로 수정하면 편하게 변경할 수 있을지 고민해 보았다.
1. 리팩터링 방식
숫자야구 개수(3)는 생각보다 여러 객체에서 사용하고 있었다. 그렇기 때문에 만약 숫자 개수가 변한다면 여러 객체들을 탐색하면서 값을 수정해야 했다. 이렇게 되면 버그 발생 가능성도 높아지고 수정하는데도 많은 시간이 소요될 것 같았다. 그래서 다음과 같이 리팩터링 하였다.
위와 같이 게임 설정과 관련된 값을 필드값으로 가지는 GameOption
객체를 생성하였다.
그리고 위와 같이 숫자야구 개수를 사용하는 곳에서 해당 객체를 통해 접근하도록 리팩터링 하였다.
이렇게 리팩터링 하면서 단순히 GameOption
에서 값만 바꾸면 다른 곳에서 추가로 수정해 줄 필요 없이 요구사항이 변경되었다!
허용 숫자도 이와 동일하게 방법으로 리팩터링 해주었다.
나의 최종 코드는 다음 링크를 통해 확인 가능하다!
https://github.com/woowacourse-precourse/java-baseball-6/pull/319
1주 차 미션에서 배운 것
1주차 미션을 진행하면서 기본적인 개념들에 대해서 정리해 보는 시간을 가졌다. 미션을 시작하기 위해 사용하는 Git부터 미션 중 사용한 Stream, Collection 등등에 대해 정리해 볼 수 있었다.
git
미션을 시작하기 위해서는 git을 사용해야 했다. 물론 이전에 git 을 사용한 적이 있기 때문에 큰 무리 없이 미션을 시작하고, 제출할 수 있었다. 그런데 그동안 git에 대해서는 정리해 본 적이 없었던 것 같아 이번 기회에 정리해 보는 시간을 가졌다. 또한 git clone과 git fork의 차이점에 대해서 잘 몰랐기에 이에 대해서도 학습하고 정리하였다.
이와 같이 정리하면서 git의 기본 동작 방식에 대해서 학습할 수 있었고 fork와 clone의 명확한 차이점에 대해서도 알 수 있었다.
record
미션에서는 JDK17을 사용하도록 되어있었다. 이전에는 계속 JDK11 로만 개발을 해왔어서 JDK11과 JDK17 사이에 어떠한 업데이트가 있었는지 알아보았다. 기타 여러 가지 기능들이 업데이트되었는데 그
중에서 JDK16부터 정식 스펙으로 포함된 record 가 눈에 띄었다. 그래서 record 에 대해 집중하여 알아보면서 정리하였다.
이와 같이 배운 record 를 1주 차 미션에서 적용해보고 싶었다. 그래서
Assertions & NsTest
제공된 테스트 코드에서 이전에 보지 못했던 함수들이 많이 있었다. 나만의 테스트 코드를 만들기 위해서는 해당 함수들에 대한 이해가 필요하다고 생각하였다. 그래서 해당 함수들에 대해서 탐색해 보았고 이를 정리하였다.
https://flight-developer-stroy.tistory.com/41
Assetions와 NsTest를 살펴보니 겉으로 보는 것과 다르게 내부에서는 생각보다 여러 작업이 일어난다는 것을 확인할 수 있었고 새로운 개념들을 알 수 있었다. 처음 살펴보았을 때는 복잡해 보여서 겁부터 먹었는데 하나씩 차근차근 살펴보면 이해하는데 큰 어려움이 없었던 코드였다. 긴 코드나 공식 문서를 보면 지레 겁을 먹는 경우가 많았는데 이번을 기회 삼아 천천히 살펴보는 습관을 가지도록 노력해야겠다!
이 외에도 stream, collection, MVC 패턴 등 여러 가지 개념에 대해서 학습하는 기회를 가졌다. 이를 학습하고 정리했던 글은 다음과 같다.
collection : https://flight-developer-stroy.tistory.com/39
stream : https://flight-developer-stroy.tistory.com/38
MVC : https://flight-developer-stroy.tistory.com/37
🚧 보완해야 할 점
1주 차 미션이 주어지자마자 빠르게 구현하고자 구조 설계를 하지 않고 바로 코딩하였다.
그래서 명확한 설계 없이 코드를 작성하다 보니 중간에 문제가 발생하였다.
1. 자주 변경되는 구현 방식
우선 구현 방식이 수정되는 일이 자주 발생하였습니다. 이런저런 방법을 시도하면서 구현하니까 시간을 낭비하는 경우가 많았다.
2. 테스트 코드 활용 부족
비즈니스 로직을 분리하지 못했습니다. 명확한 설계 없이 코드를 작성하다 보니 중간중간 여러 객체에서 비즈니스 로직을 구현하였고 그렇다 보니 특정 비즈니스 로직에 대한 테스트 코드를 제대로 활용하지 못했다.
이러한 문제점들을 보완하기 위해서 다음 주차 미션부터는 코드 작성을 하기 이전에 설계에 대해 깊이 생각해 봐야 할 것 같다. 그리고 명확하게 비즈니스 로직을 구분하여 테스트 코드도 제대로 활용하도록 노력해 봐야겠다!
'우아한테크코스' 카테고리의 다른 글
[우아한테크코스 6기 백엔드] 프리코스 3주 차 로또 🎰 회고 (2) | 2023.11.09 |
---|---|
[우아한테크코스 6기 백엔드] 프리코스 2주 차 자동차 경주🏎️ 회고 (0) | 2023.11.01 |
[우아한테크코스 6기] 프리코스 시작!! (0) | 2023.10.26 |
[우아한테크코스 6기 준비] 설명회 이후 서류 접수 시작! (0) | 2023.10.06 |
[우아한테크코스 6기 준비] 서류접수 1달 남은 회고록 (0) | 2023.09.06 |