우테코는 모든 미션을 페어로 진행한다.
페어 프로그래밍은 이번 미션을 통해 처음 해봤는데 두 명의 개발자가 하나의 컴퓨터로 작업하는 것이다.
한 명은 Navigator가 되어 작성하는 코드의 방향에 대해 실시간으로 피드백하고,
한 명은 Driver가 되어서 직접 코드를 작성한다.
아무튼 첫 미션에 대해 시간이 좀 지났지만, 더 지나기 전에 회고해보려 한다.
👍 좋았던 점
- 페어와 정말 빠르게 친밀도를 쌓았다. 굉장히 잘 맞았다고 생각했다.
- 덕분에 페어와 의견을 교류하는데 불편이 없었고, 서로 생각하는 것을 이야기하고 더 좋은 방법으로 타협하는 과정도 매끄러웠다.
- 의존성 주입에 대해서 구현해보고자 노력했다. 확장성에 대해 생각하는 상상력이 좋아졌다고 느꼈다.
자동차를 움직이는 로직에 있어서, 많은 크루들이 int를 인수로 받았지만 우리는 꽤 많이 생각했던 것 같다. (오버 엔지니어링이었을지도...?!) 수업 때에 이 부분에 대해서 직접 언급해서 다른 분들과도 이야기해 보았다.
- Controller에서의 Map으로 작동하는 방식, 정적팩토리 메서드, View에서의 싱글톤 패턴 적용 등등 좀 공부한 티를 냈다.
- 페어가 스트림을 사용해본 적이 없다고 해서, 함께 자바의 정석 기초편을 읽으며 공부했다. 페어는 스트림 쓰기에 재미를 들려서, 함께 테코톡 발표까지 하기로 했다.
💋 아쉬웠던 점
- 나는 성격이 급해 말이 빠르고 주장이 강한 편이다. 페어의 의견보다 나의 의견대로 진행된 부분이 많은 것 같다. 계속해서 나의 방식대로 똑같이 유지하기보다는 페어의 의견을 더 많이 반영해서 다양한 방법을 테스트해보는 것도 실력 향상에 더 큰 도움이 될 것 같다.
- 페어와 많이 친해져서 프로그래밍을 끝내고 많이 놀았는데, 좀 더 리팩터링에 집중했어도 좋았을 것 같다. 물론 후회는 없다.
- 싱글턴 패턴, Util 클래스, Repository, 정적 팩토리 메서드 등등 잘 모르고 남들이 쓰기에 빠르게 습득해서 사용하는 것을 나의 장점이라고 생각해 왔는데, 리뷰어가 이 부분에 왜 이런 방식으로 작성했냐는 질문에 답하기 어려웠다. 남들이 쓰기에 좋아보인다고 다 가져오기보다는 많이 질문하고, 적극적으로 여러 패턴과 방식의 장단점에 대해서 살펴볼 필요가 있을 것 같다.
- 처음에 CarName을 받아서 모두 inputView에서 검증했다. validator에 너무 집중하다 보니, domain에서 검증을 할 생각을 전혀 하지 못했다. 다른 분들의 코드를 좀 구경하면서 원시값 포장이라는 개념에 대해서 알게 되었다. class CarName을 따로 받아서 포장하는 객체를 만들었다.
💋 리뷰어로부터 배운 점
- 이제까지 Repository 레이어가 존재한다는 것조차 모르고 사용해왔다. 우테코에서 이전 기수의 최종 코테에 제공했던 페어 매칭 프로그래밍의 예시 코드를 그대로 가져와서 사용했다. 그 코드는 Crews라는 클래스에 List<Crew>를 필드로 관리했는데, static한 필드로 관리하며 내부에 add, find 등 데이터 조회에 필요한 내용들을 담아두고 있었다.
- 나는 해당 코드를 굉장히 획기적이라고 생각하여 나의 코테였던 java menu에서도 반영했다. 당시에 문제점이 있었다.
- 하지만, 무작정 static으로 작성하게 되다 보니, 테스트가 어려웠던 것이다. 해결 방법으로 나는 clear 메서드를 다시 정의해 List를 removeAll하는 public 메서드를 작성했고 테스트에서 각 테스트 이후에 Repository.clear ()메서드를 실행했다. 지금 돌이켜 생각해보니 외부에서 데이터베이스를 모두 삭제할 수도 있다는 말인데, 아찔하다.
위에서 아쉬웠던 점에 대해 언급했던 Repository에 대해서도 질문을 받았다.
싱글턴 패턴과 스태틱 클래스의 차이에 대해서도 질문을 받았는데, 조금 더 공부해봐야 할 것 같다.
- unmodifiableList이 만능이라고 생각해 왔는데, 어느 부분까지 도움이 될 뿐, 완벽하지는 않다는 것을 덕분에 알게 되었다.
- 나는 이곳에서 사실 unmodifiableList를 활용하면서 List<Car> cars의 각각의 요소인 Car의 move조차 안전하게 보호가 된다고 잠시 상상을 했었다. 하지만 생각해보니, 나도 이 클래스를 사용하면서 move()라는 메서드를 직접 호출했었고, 그런 보호는 없다. 리뷰어는 나의 이런 마음을 알고 물어본건 아니었겠지만 피드백을 받고 검색해보는 과정에서 깨달았다.
- 테스트 어노테이션 중 @NullAndEmptySource이 있다는 것을 알게 되었다.
@ParameterizedTest
@NullAndEmptySource
void blankNameTest(String input) {
List<String> names = new ArrayList<>(List.of(input, "깃짱"));
assertThatIllegalArgumentException()
.isThrownBy(() -> carNameValidator.validate(names))
.withMessage(Message.EXCEPTION_CAR_NAME_BLANK.getMessage());
}
- String의 메서드 중 isBlank와 isEmpty가 다르다는 것을 알게 되었다. null까지 테스트해주는 지의 여부가 다르다!
- MainController에서 현재까지는 IllegalArgumentException과 NullPointerException에 대해서만 처리하고 있었는데, 이외의 예외가 발생하는 것에 대비해 전체 Exception에 대한 처리도 해 주는 것이 좋다는 것을 배웠다.
💋 나의 수많은 궁금증
요구사항을 정리해 기능 목록을 작성하는 방법
어떻게 해야 개발 과정과 비슷한 기능 목록을 작성할 수 있을 지에 대해서 고민했다.
프로그램이 돌아가는 순서와 개발자가 프로그램을 설계하고 코드를 작성하는 순서는 보통 다른 것 같다.
다른 분들에게 질문한 결과, 다른 개발자가 이해하기 위해서는 조금 더 추상적으로 나의 기능목록을 작성하는 것도 좋을 것 같다.
궁금증에 대해서 README에 적어 놓았더니, 일부에 대해서는 리뷰어의 피드백도 받을 수 있었다.
이제까지 `Validation` 조상클래스에 보편적인 검증 로직(자연수를 정규표현식으로 검증, int 범위를 초과하는 입력에 대한 예외 처리)을 넣고, 공통되는 사용자 입력값 검증에 사용하기 위해 상속했다. 큰 연관성이 없는 검증들임에도 단지 코드 중복을 줄이겠다는 이유만으로 상속해도 될까?
Validator 추상클래스를 말씀해주신 것 같은데요. Validator를 상속하지 않고 MoveCountValidator, CarNameValidator 클래스만 만들어도 현재 요구사항에는 충분히 만족하고 있다고 생각합니다. 그리고 이해가지 않는 부분이 있는데요. 두 개의 클래스 모두 코드 중복이 있는 상황인가요? Validator를 상속하면 코드 중복이 사라지나요?
나는 어느 정도 습관적으로 추상클래스를 만들고 있었는데, 이 부분에 대해서 좀 깨달았다. 추상 클래스를 사용하면서 또 신나서 남용했던 것 같기도 하다. 꼭 필요한 것이 아니라면 굳이 노력을 들이지 않아야 중요한 곳에서 제대로 잘 할 수 있는 것 같다.. 이번에도 이유에 대해서 잘 설명하지 못했다.
`InputView`에서 받은 사용자의 입력값을 (나의 편의를 위해서) view에서 호출한 validator를 통해서 모두 검증한 뒤에 model로 보내왔다. 그런데 입력값을 받는 로직이 바뀌거나, 테스트 코드에서 들어온 입력을 처리할 일이 필요할 때 대처가 미흡하다. 예를 들어, 현재의 코드는 공백을 포함한 입력을 테스트 코드에 넣으면 공백을 처리하지 못하는 문제점이 있다. 입력에 대한 검증을 어느 곳에서 하면 좋을까?
검증 위치 관련해서는 제가 코드리뷰로 달았던 링크를 참고하시면 좋을 것 같아요. 원시값을 포장하게 되면 검증을 어디서 해야할 지 조금 더 이해가 잘 될 겁니다. 원시값을 포장하는 부분은 제가 따로 코멘트해둘게요!
원시값 포장에 대해서 이때 생각해보게 되었다.
기능 목록을 작성할 때에, 프로그램이 돌아가는 순서로 작성하는 것과 프로그래머가 개발하기 편한 순서에 따라 작성하는 것은 차이가 있다. 추상화한다면 얼만큼, 아니면 기능 목록은 개발자끼리 보는 것이기 때문에 철저히 프로그래머의 입장에서 작성하면 되는걸까?
어떤 순서로 작성하느냐는 개인차가 있어서 정답은 없을 것 같은데요. 저라면 프로그램이 돌아가는 순서로 작성할 것 같긴 합니다. 흐름대로 작성하면 다른사람들이 보기도 편하지 않을까요? 프로그래머의 입장, (본인 입장 혹은 페어 입장)에서 같이 논의하며 작성하시면 될 것 같습니다.
만약에 Validator 조상 클래스를 만든다고 치자! 그럴 때에 input을 항상 String으로만 받으라는 법은 없다. List으로도 받고 싶고, String으로 받고도 싶다. 또, 현재의 메서드는 검증 과정에서 문제가 발생할 경우에 예외를 발생시키고, 문제가 없다면 아무 일도 일어나지 않는다. 하지만, 만약에 내가 검증 후에 아무 이상이 없다면 정제된 값을 리턴받고 싶다고 생각해보자. 이럴 경우에 abstract method의 파라미터와 리턴 값의 타입은 어떻게 작성하는 것이 좋을까...? (제네릭스에 대해 공부해보면 알 수도 있을 것 같다.)
네, 말씀대로 Type Safe하게 가려면 제네릭을 선택하는 방법이 있습니다. 또 다른 선택지로는 모든 클래스의 상위 클래스인 Object를 사용해도 되겠죠. 어느 것이 좋을지는 차근차근 공부해나가셔도 좋겠네요
(int) (Math.random() * MAX_NUMBER)는 정말로 0부터 9까지의 랜덤한 값을 반환할까? (0과 9의 빈도가 더 적게 나타날 것 같다)
특정 라이브러리에 대해 궁금하다면 직접 내부 구현을 보는 걸 추천드리고 학습 테스트를 통해서도 파악할 수 있을 것 같습니다. 관련 내용 조금 더 첨부드리면 0~9까지의 빈도 차이라기보단 Math.random()은 내부적으로 java.util.Random()을 호출해서 사용하는데요. java.util.Random()은 특정 nano 시간대의 시드를 바탕으로 생성하기 때문에 동일하게 생성될 수 있습니다. 그래서 저번에 SecureRandom()에 관련된 내용을 전달드린거구요ㅎㅎ
- 결과적으로 Math.random()은 완벽한 랜덤은 아니라는 것도 알게 되었다. 뭔가 묘하게 앞쪽에 이름을 적은 사람이 더 빠르게 움직이는 것 같은 느낌도 있긴 했다. 새로 바꾼 코드는 따라서 SecureRandom을 사용했다. 조금 유난인가..? 하지만 우리 온보딩 연극조는 이 결과를 바탕으로 아이스크림 내기를 했기 때문에, 중요했다!
SecureRandom.getInstanceStrong().nextInt(9)
레이어에 대해 공부하면 좋을 것 같다. Repository, Domain 레이어에 대해서 공부하자!
Repository 레이어에 관련된 부분보다는 Domain 레이어, 객체지향에 대해 중점적으로 공부하시면 좋을 것 같아요. Repository 레이어는 추후 JPA를 진행하시면서 자연스럽게 배우게 될 내용이라 그때 공부하셔도 될 것 같습니다!
'우아한테크코스5기' 카테고리의 다른 글
[우테코] 사다리 미션 2단계 회고: TDD, 원시값 포장, 객체의 책임... 너무 어려웡 (3) | 2023.02.27 |
---|---|
[우테코] 사다리 미션 1단계 회고 (2) | 2023.02.19 |
[우테코] 좋은코드: 좋은 코드의 조건은 끝이 없지만 그중 몇가지... (0) | 2023.02.17 |
[우테코] 웹 기초: HTML, CSS, JS를 사용해 자기소개 페이지 만들기 (0) | 2023.02.17 |
[우테코] TDD (Test Driven Development): 테스트 주도 개발이 뭐지? 사용하는 이유? (0) | 2023.02.14 |