오늘은 두 번째 라이브 세션이자 OT 를 제외하고는.. 과제를 진행하며 참여한 첫 라이브 세션이었다.
첫 주차 과제로 '자동차 경주' 게임을 만들게된다. 과제는 STEP1, STEP2, STEP3 으로 나뉘어 있는데 이전 기수에서는 화면단을 가지고 cypress 로 테스트를 하는 방식이었다면, 이번엔 콘솔을 기반으로 jest 로 TC 를 짜는 방식이다.
개인적인 기록이니까.. 그냥 양식에 구애받지 않고 자유롭게 남겨보겠음.
사실 첫 과제 PR 을 이번 라이브세션 전까지 올리고 싶었는데, 생각보다 어려웠고 또 TDD 를 적용하면서 해야한다고 생각하니 어디서부터 시작해야할지 몰라 더더욱 어려웠던 것 같다. 다른 분들의 PR 을 보면 MVC 같은 패턴 구조까지 적용해서 깔끔하게 만드셨는데 나는 '입력을 어떻게 받아야하나... stdin... stdout...' 이런 것부터 하고 있었으니... 😇 뭔가 어렵다고 느끼니까 더욱 어려워지고, 약간 어려움 무한 굴레에 빠지다보니 재미를 느끼기도 힘들었고 아무튼 그렇게 한 주가 흘렀다.
첫 과제 이전에는 온보딩 과제로 계산기 과제가 나갔는데, 이건 제때 빠르게 PR 을 올릴 수 있었다. ㅎ
여기서 받은 리뷰에 대해서 잠깐 공유해볼까 한다. 크게.. 4가지 정도가 있었다.
1. 상수명
약간 불분명한? 의도를 알기 힘든 상수, 변수명이 있었다. 변수명을 짓는 것은 늘 엄청난 고민이었는데, 회사 코드를 작성하면서는 더더욱 큰 고민을 하게 되었다. 이 코드가 일회성으로 끝나는게 아니고 한 번 작성하면 몇 달은 남게 될 코드이니까 오히려 코드를 실제 작성하는 시간보다 변수명을 짓고, 어떻게 첫 스타트를 끊어야 하는가에 많은 생각보다 정말 많은 시간을 쏟게 되는 것 같다.
오늘 라이브세션 첫 순서에서, 메이커준님께서 변수명을 잘 짓는 방법에 대해 공유를 해주셨다.
핵심은, 1.의도를 드러내고 / 2. 구현은 숨기고 / 3. 반환 타입에 대한 명확한 힌트를 제공한다.
일단 최대한 의도를 드러낼 수 있도록 긴 이름으로 작성해보고, 여기서 구체적인 구현 방법 (예를들면... 내 생각엔 Array, Object, localStorage 이런 아주 구체적인 방법) 을 제외한다. 그리고 세번째 핵심의 예시로는 불린 값을 표현할 수 있는 is- 접두사를 붙인다던가 하는 예시가 있을 수 있겠다.
2. 에러처리
validation 을 처리하는 부분이 있었는데, 여기서 올바르지 않은 경우 에러를 throw 해놓고 이를 catch 하는 부분이 없었다. throw 만 하고 catch 해서 적절히 핸들링하지 않으면 프로그램이 터져버린다. 사용자 관점에서 에러 핸들링이란게 사실 되게 중요한 부분인데, 회사 코드를 작성하면서부터 이 사실을 인지하기 시작한 것 같다. 예전에 토이프로젝트로 만들때는 돌아가게 만드는 것에 더 초점을 맞췄는데, 이제는 제대로 돌아가지 않으면 서비스도 터지고 CS 도 터지기 때문에 에러 핸들링에 대해 책임감있는 태도를 갖게 되었다.
최근에 에러 바운더리를 활용해봤는데, 에러 바운더리가 감싼 영역에 각각 맞는 핸들링을 할 수 있어서 굉장히 편리했다. 조금 더 우아하게 처리가 가능해진다면 블로그도 작성해봐야지.
3. 중복 코드 제거
이건 말뭐..
4. 의존성에 대한 고민
테스트케이스를 작성할 때 서로 커버되는 범위가 겹치는 TC 가 있어서 이를 어찌하면 좋냐는 질문에, '의존할 필요가 없는데 의존을 하고 있어서 이런 문제가 발생하는 것 같다' 라는 피드백을 받았다. 함수가 한 번에 한 가지 기능만을 하도록, 의존성을 줄이고 추상화를 고도화 할 수록 코드도 클린해지지만 (가독성 및 재사용성..) TC 를 짜기도 편리하다는 것을 알게 되었다.
이상 피드백이었고, 아래는 구구절절 나의 느낀 점이자 현재 과제 진행 과정... 더 잘 쓰고 싶은데 너무 졸려서 약간 횡설수설 할 수도..
TDD 를 처음에 어떻게 접근해야 좋을지 모르겠어..
오늘 라이브 세션을 듣기 전까지만 해도 정말 엄청난 고민이었다. 일단 첫 TC 를 어떻게 작성해야하느냐, 아니면 또 예전처럼 일단 구현을 해놓고 TC를 작성하느냐 (전혀 테스트가 주도하고 있지 않은...)
그래서 코드를 썼다 지웠다 썼다 지웠다 하느라고 과장을 조금 보태 100줄도 쓰지 못한 것 같다.
오늘 라이브 세션을 통해 느낀 나의 개선점은, 핵심을 더욱 더 쪼개서 그 작은 핵심으로 전체 프로그램(목표한 결과)를 돌릴 수 있도록 만들어야 한다는 것이다. 과제를 시작하기 전에 어떤 요구사항을 구현할 것인지 리스트업을 하도록 권장했는데, 아래는 (구)나의 리스트업이었다.
[전체 프로그램의 요구사항]
- 자동차 이름을 입력받는 기능
- 자동차 이름의 길이를 검사하는 기능
- 자동차 경주를 진행하는 기능
- 우승 자동차를 판별하는 기능
- 프로그램 종료 기능
[그래서 내가 처음 시도하려고 했던 내용]
- 자동차 경주를 진행하는 기능
- 2대 이상의 자동차를 임의로 배정한다.
- 각 자동차에 대해 0~9 사이 무작위 값을 구한다.
- 4이상인지 확인하고 전진 후 다음 경주를 진행한다.
- 경주는 5회 진행한다.
- 경주의 진행 과정을 콘솔에 출력한다.
여기서 오늘 라이브세션에서 얻은 인사이트로 문제점을 파악하자면...
1. 일단 자동차 경주를 진행하는 과정을 처음 시작부터 만들 수가 없다. 조금 더 구체적인 단위로 문제를 쪼개야 했는데 그러지 못했음.
2. 2대 이상의 자동차를 임의로 배정한다는 것은 요구사항과 맞지 않음. (이건 너무 구체적인 방법에 대한 문제)
3. 시도하려했던 각 태스크의 요구사항을 커버하는 정도가 일관적이지 못했음. 어떤건 너무 구체적이고 어떤건 너무 포괄적이고 (???)
지난 시간에도 들었던 내용이지만, 오늘도 강조해주셨던 내용은.
1. 동작 가능한 가장 작은 버전부터 만든다. 단, 핵심을 포함하도록.
2. known to unknown (알고 있는 것부터 시작해 차차 범위를 늘려가는 방식)
여기서 '핵심을 포함해야 한다.' 라는 말에, 이 과제에서 어떤 기능이 핵심일까를 고민했고 그래서 나온 결론이 '자동차 경주를 진행하는 기능을 먼저 만들자!' 였다. 자동차 경주 게임이니까 경주를 진행하는게 핵심 기능인 것은 맞는데, 그 핵심의 범위가 너무 지나치게 넓어졌기 때문에 저런 요구사항 리스트업이 나온게 아닐까 싶다.
스노우볼
오늘 라이브 세션을 보면서, known to unknown 을 만들어가는 과정이 꼭 '눈사람 만들기' 같다는 생각이 들었다. 눈사람을 만들 때 가장 작은 눈공(?) 에서 출발해서 서서히 굴려가듯이 코드를 정말 처음에 눈 뭉치만큼 작게 만들어서 굴리고 또 굴리는 것 같았다.
그래서 앞으로는 첫 시작점을 어디서 잡느냐, 어떤 순서로 시작하느냐 보다도 가장 쉽고 간단하게 전체 사이클(비슷하게라도) 돌리려면 어떻게 해야하는가? 를 기준으로 TC 를 만들어가도록 해야겠다. 그렇게 만드려면, 일단 구체적인 구현 방법은 제외하면서 기대되는 동작이 무엇인가에 집중하도록 해야한다.
그렇게해서 만들어진 나의 TC 들은 아래와 같다.(아직 진행중...)
일단 콘솔에 입력과 출력이라는 구체적인 구현 방법과 인터랙션을 제외하고, 자동차가 굴러가고 자동차 경주를 진행하는 것에 초점을 맞췄다.
describe('자동차', () => {
let car
beforeEach(() => {
car = new Car(CAR_NAME)
})
it('자동차는 이름을 가질 수 있다.', () => {
expect(car.getName()).toEqual(CAR_NAME)
})
it('자동차는 랜덤 숫자가 4 이상이면 앞으로 전진한다.', () => {
for (let move = 1; move <= 3; move++) {
car.move(4)
expect(car.getPosition()).toEqual(move)
}
})
it('자동차는 랜덤숫자가 4 미만이면 정지한다.', () => {
car.move(3)
expect(car.getPosition()).toEqual(0)
})
})
describe('자동차 경주', () => {
let raceTrack
beforeEach(() => {
const car1 = new Car(CAR_NAME_ARR[0])
const car2 = new Car(CAR_NAME_ARR[1])
raceTrack = new RaceTrack([car1, car2])
})
it('자동차 경주를 5회 진행한다.', () => {
raceTrack.race()
expect(raceTrack.getTurnCount()).toBe(5)
})
it('자동차 경주를 할 때 0 ~ 9 까지 랜덤 숫자를 부여해 전진 여부를 결정한다.', () => {
const testCount = 100
for (let i = 0; i < testCount; i++) {
const score = makeRandomNum()
expect(score >= 0 && score <= 9).toBe(true)
}
})
it('자동차 경주 게임 후 우승자를 가려낸다. (우승 자동차는 여러대일 수 있다.)', () => {
raceTrack.race()
const winners = raceTrack.getWinners()
expect(winners).toEqual(expect.arrayContaining(CAR_NAME_ARR))
})
})
사실은 아직도 작은 스노우볼을 만드는 중이기 때문에... 이번 주말까지 최대한 만들어서 첫 PR 을 올리는게 목표다...!
'TDD, Cleancode with JavaScript' 카테고리의 다른 글
# 5 / 객체간의 결합도 / 객체를 객체답게? (2) | 2023.08.17 |
---|---|
# 4 / 함께해요, 리팩토링 😇 + 사담 겸 푸념;; (2) | 2023.08.08 |
# 3 / 일급 컬렉션이 머에영? (우적우적 🍿) (3) | 2023.08.01 |
# 1 / OT. TDD 란 무엇인가. (0) | 2023.07.19 |