드디어 첫 페어 프로그래밍이었다. 콘티 크루, 애니 크루랑 셋이서 했는데, 솔직히 첫날 둘째날은 좀 산으로 갔다.
TDD 해야 한다는 걸 알면서도 셋이 모이면 자꾸 구현 얘기만 하게 되는 거다 ㅋㅋㅋㅋ. “이 클래스는 이렇게 하자”, “컨트롤러를 이렇게 나누자” 이런 얘기 하다 보면 시간이 훅 가더라. 결국 우리팀만 시간 오버해서 제출했다 ㅋㅋ 벌써 찍힌 느낌…
리팩토링
제출하고 나서 코드가 마음에 안 들었다. 근데 지각(?)했음에도 불구하고 리팩토링을 좀만 더 해도 되냐고 직접 리뷰어 데구리에게 DM 드렸더니 천사처럼 “충분히 만족할 만큼 리팩토링해서 다시 올려달라”고 해주셨다. (지각하면 사실 리뷰 안해주셔도 되는거로 안다)
감사합니다 진짜로
그래도 페어프로그래밍인데 혼자 구조를 너무 바꾸면 안되니까 리팩토링은 중복코드 제거수준으로 일단 간단하게 해두고, 따로 레포를 파서 아예 처음부터 내 마음대로 다시 구현해봤다. 진짜 주말동안 엄청 했는데, 하다 보니까 욕심이 생겼다. 이왕 하는 거 웹(Step 2) 전환까지 고려한 구조를 만들고 싶었다.
다른 PR들 분석
리팩토링하면서 다른 사람들은 어떻게 짰는지 궁금해서 PR을 여러 개 뜯어봤다.
그 중에 두 개가 특히 인상 깊었다.
제이드 PR #277
Controller, Domain/Entity, Domain/Service, View로 레이어를 명확하게 나눈 구조였다.
인상 깊었던 건 클래스 간 메시지를 전달할 때 클래스 객체 자체를 넘기지 않고
원시값이나 순수 객체로 변환해서 넘긴다는 거였다. 결합도를 낮추는 의도인데,
나도 Money 객체를 WinningLottoController에 그대로 넘기지 않고
getAmount()로 숫자만 꺼내서 넘긴 게 같은 맥락이었다.
LottoNumber로 원시값을 래핑하고 Lotto를 일급 컬렉션으로 구성한 것도
우리가 고민했던 방향이랑 똑같아서 괜히 반가웠다.
지니 PR #255
JSDoc을 모든 모듈에 적용해서 바닐라 자바스크립트인데도 타입스크립트처럼 쓴 게 신기했다.
테스트 파일을 __tests__ 폴더에 몰아두지 않고 모듈 바로 옆에 두는 방식도 처음 봤는데,
코드 변경할 때 바로 옆에 테스트가 있으니까 확실히 빠르게 대응할 수 있겠다 싶었다.
TDD로 오버엔지니어링 없이 요구사항만 정확히 구현했다는 평가가 부러웠다.
우리가 첫날 TDD 못 한 게 더 아프게 느껴졌다 ㅋㅋ
다양한 구조를 보면서 느낀 건, 레이어가 많을수록 유연하지만 복잡해진다는 거다.
Git 브랜칭 연습
Git을 너무 못 다룬다는 걸 이번에 뼈저리게 느꼈다. 그래서 따로 gitting-good-with-lotto 레포 만들어서 처음부터 다시 혼자 구현해봤다.
도메인 클래스마다 feat/money, feat/lotto 브랜치 따서 작업하고, PR 열어서 develop에 머지하는 흐름을 반복했다. 협업하는 것처럼.
별거 아닌 것 같아도 이게 손에 익어야 나중에 실제 협업할 때 브랜치 전략이 자연스럽게 나온다고 생각해서 일부러 좀 과하게 해봤다.
LottoNumber - 숫자 하나에 클래스가 필요한가?
솔직히 처음엔 이게 과설계 아닌가 싶었다. 그냥 숫자 하나인데 클래스로 감싸는 게 무슨 의미가 있나.
근데 생각해보면 로또 번호 1~45라는 규칙은 Lotto, WinningNumber 등 여러 곳에서 필요하다. LottoNumber 없이는 각 클래스마다 이 규칙을 중복 작성해야 한다.
// LottoNumber 없으면 Lotto에서도
if (num < 1 || num > 45) throw new Error(...)
// WinningNumber에서도
if (num < 1 || num > 45) throw new Error(...)
LottoNumber 하나로 감싸면 “이 숫자는 유효한 로또 번호임”이 생성 시점에 보장된다. 외부에서 다시 검사할 필요가 없어진다.
더 중요한 건 NaN, 소수 같은 엣지케이스를 한 곳에서 처리할 수 있다는 거다.
NaN < 1은 false, NaN > 45도 false라서 범위 검사를 그냥 통과해버린다.
이걸 모든 클래스마다 처리하는 게 아니라 LottoNumber 하나에서 막아두면 된다.
숫자 하나를 클래스로 감싸는 게 처음엔 어색했는데, 해보고 나니까 오히려 이게 없으면 불편하겠다 싶어졌다.
fromString()
가장 많이 바뀐 부분이 파싱 로직이다.
원래는 ConsoleInputView에서 입력받은 문자열을 직접 파싱해서 넘기거나, 생성자에서 Number(amount)로 변환했다. 근데 콘솔이든 웹이든 입력은 항상 문자열로 들어온다. 그럼 파싱 로직이 View에 있으면 웹으로 바꿀 때 WebInputView에 또 같은 로직을 써야 한다.
그래서 각 도메인 클래스에 fromString()을 만들었다.
Money.fromString("3000");
Lotto.fromString("1,2,3,4,5,6");
WinningNumber.fromString(winningLotto, "7");
이렇게 하면 웹 전환 시 View만 교체하면 파싱 로직은 그대로 재사용할 수 있다.
도메인이 스스로 책임진다
리팩토링하면서 제일 많이 고민한 건 이 로직이 어디에 있어야 할까 고민을 많이했던것같다.
원래 코드는 파싱, 검증이 여기저기 흩어져 있었다. Validator 유틸 클래스도 따로 있고, InputView에서도 파싱하고, 생성자에서도 변환하고.
결론은 도메인이 스스로 자기 유효성을 책임지는 게 맞다는 거였다. Money가 “나는 1000원 단위의 금액이다”를 스스로 보장하고, LottoNumber가 “나는 1~45 사이의 정수다”를 스스로 보장하면, 외부에서 다시 검사할 필요가 없어진다.
마무리
페어 프로그래밍은 생각보다 어려웠다. 셋이 있으니까 의견 조율도 필요하고, 방향 잡는 것도 혼자 할 때랑 다르더라. 근데 혼자였으면 절대 못 봤을 관점들을 얻었다. 다음엔 TDD부터 제대로 해보고 싶다. 테스트 먼저 짜는 게 어색한데, 어색한 걸 자연스럽게 만드는 게 지금 목표이다.
아직 피드백은 안왔으니, 피드백이나 보면서 공부할 준비나 하고 있어야겠다.