NEXTSTEP CleanCode JS Mission3 - 자동차 경주 회고

저번 로또 미션 주간때 코로나에 걸렸어서 🤦‍♀️ 제대로된 미션을 진행하는건 이번이 처음이였다. 그래서 여러모로 부족한것도 궁금한것도 많았다.

🔗 PR 링크

🎯 1단계 요구사항

  • 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
  • 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
  • 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
  • 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
    이 부분은 애니메이션을 넣는 경우 STEP3의 미션으로 알아 현재는 따로 구현하지 않았다!
  • 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.

➰ every()

자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.

처음에는 자동차의 이름 길이 유효여부를 검증할때 이름이 들어간 배열을 순회한후 각 요소의 length가 5자 이하인지 true/false를 return하려 했는데, 뭔가 좀 더 좋은 방법이 있지 않나 고민하다가 예~전에 코드리뷰 스터디에서 받았던 피드백 내용인 용도에 맞는 배열 메서드를 사용하기가 기억나 every()를 사용해봤다.

function isCheckCarNameLength(carName) {
	return carName.every((item) => item.length <= MAX_CAR_NAME_LENGTH);
}

확실히 every를 사용하니 내가 원하는 의도로 작동하게 됐다. some()이라던가 every()는 사실 잘 사용을 안해봤는데, 이번기회에 사용해봐서 좋다. 앞으로 사용하는 연습을 해봐야지!

trim()

자동차의 이름을 쉼표로 구분했을때 아래 사진과 같은 모습으로 입력을 하다보니, 이름과 이름 사이에 쉼표를 붙여쓸 경우에 대해 생각을 못했다.

다른 분의 코드리뷰를 살펴보다가 해당 부분에 지적을 받으신 분들도 있어서 나도 이부분을 수정했다. 기존에는 이름을 문자열로 입력하면 "EAST, WEST, SOUTH, NORTH".split(", ")이런식으로 배열로 쪼개줬는데, 이번에는 "EAST, WEST, SOUTH, NORTH".split(',').map((item) => item.trim());처럼 쪼개줬다. 이 과정에서 해당함수를 두번이상 사용하게 되어 유틸부분으로 분리해봤다!

➰ Array.from

자동차의 전진 횟수를 나타내는 부분을 원래는 배열을 입력한 전진 횟수만큼 생성한다음 map으로 순회하는 방식을 사용했는데, Array.from을 사용하면 해당 부분을 줄일 수 있는걸 알게 됐다.

// 원래 방식
${Array(Number($carTryInput.value))
	.fill(0)
	.map(() =>
		isMoveCar() ? `<div class="forward-icon mt-2">⬇️️</div>` : null
	)
	.join('')}
// 바꾼 방식
${Array.from({ length: Number($carTryInput.value) }, () =>
	isMoveCar() ? `<div  class="forward-icon mt-2">⬇️️</div>` : null
).join('')}

Array.from은 DOM List같이 유사 배열을 진짜 배열로 바꿀때 빼곤 사용해보지 않았는데, 이런 방법이 있다는걸 알고 놀랐다. 검색해보니 대략 배열 자체에서 length라는 key로 길이 속성을 사용하는데, 이런 원리를 사용한것 같다는 요약이였다. 그래서 연속된 수로 이뤄진 배열을 만들때 Array.from을 사용하면 쉽게 작성할 수 있다고..! 😶

🧪 alert 테스트

Cypress에서 alert을 테스트할때는 stub이라는걸 사용한다고 페어프로그래밍때 배웠다. 여기에 to.be.calledWith로 체이닝을 해주면 alert내 메세지까지 검증을 할 수 있다!

it('자동차의 이름이 5글자를 초과하면 경고창을 띄워준다.', () => {
    const alertStub = cy.stub();
    cy.on('window:alert', alertStub);

    cy.get('#car-names-input').type('EASTTT, WEST, SOUTH, NORTH');
    cy.get('#car-names-submit')
        .click()
        .then(() => {
            expect(alertStub.getCall(0)).to.be.calledWith(
                ERR_MSG.OVER_CAR_NAME_MAX_LENGTH
            );
        });
});

📝 리뷰 받은 내용

코드를 작성하면서 구조에 대한 고민이 많았다.

  • 컴포넌트 방식으로 작성을 하자니 컴포넌트 방식에서 오는 재활용성의 장점이라던가 복잡한 구조를 쪼갬으로써 오는 장점이 별로 없었고,
  • 그렇다고 class안에다가 몰아넣자니 class를 굳이 사용할 이유가 있을까? 싶었다.

그래서 각각의 함수를 쪼갠다음, 이벤트가 트리거될때 해당 함수를 호출하는 방식으로 작성했었다. STEP1단계에서는 이렇게만 작성을 해도 괜찮겠지만, 후에 기능이 좀더 추가된다면 해당 구조가 썩 좋을것 같지는 않아 다른분들의 구조를 참고해봤다. MVC를 적용하신 분들도 있었고, 도메인을 기준으로 설계하신 분들도 있었는데 여기서 도메인이라는 개념이 내겐 많이 모호해 리뷰어님께 질문드렸다.

도메인

  • 도메인
    💡 도메인은 현재 해결하고자 하는 문제 (비즈니스 도메인) 이다.
  • 도메인을 분리한다
    먼저 전체 요구사항을 보고 어떤 도메인들이 필요할지 생각해본다.
    자동차 경주 게임을 예로 들었을때,
    • 주행을 할 수 있는 자동차
      - 우승자를 가려내기 위한 자동차를 관리하는 도메인
      • 여기서 모든걸 해결할 수 있지 않을까? 싶은데,
      • 도메인도 특정 데이터에 대한 관리 및 메서드를 통해 역할에 대한 책임을 나눠주는 목적이 있다.

결론적으로 도메인을 나누는 목적은,

책임을 나눠줌으로써 도메인에 대한 개별적인 책임과 역할을 나눠 유지보수를 높이는데 이점이 크다.

이라는 점이였다.
설명을 엄청 이해되게 쉽게 해주셨지만 아직 개념이 좀 두루뭉실하게 느껴져서 전에 사뒀던 객체지향의 사실과 오해에서 도메인에 대한 부분만 따로 읽어봤는데, 그중에서 인상깊은 문장이 있었다.

불행하게도 요구사항은 변경된다. 소프트웨어 분야에서 예외가 없는 유일한 규칙은 요구사항이 항상 변경된다는 것이다.
... (중략)
미래에 대비하는 가장 좋은 방법은 변경을 예측하는 거싱 아니라 변경을 수용할 수 있는 선택의 여지를 설계에 마련해 놓는 것이다. 훌륭한 설계자는 미래에 구체적으로 어떤 변경이 발생할 것인지를 예측하지 않는다.
좋은 설계는 나중에라도 변경할 수 있는 여지를 남겨놓는 설계다. 변경에 대비하고 변경에 여지를 남겨 놓는 가장 좋은 방법은 자주 변경되는 가능이 아닌 안정적인 구조를 중심으로 설계하는 것이다.
p.182

사용자가 프로그램을 사용하는 대상 분야를 도메인이라고 한다. 도메인 모델은 소프트웨어가 목적하는 영역 내의 개념과 개념 간의 관계, 다양한 규칙이나 제약 등을 주의깊게 추상화한 것이다.

다시 한 번 강조하지만 소프트웨어 객체는 현실 객체에 대한 추상화가 아니다. 소프트웨어 객체와 현실 객체 사이의 관계를 가장 효과적으로 표현할 수 있는 단어는 바로 은유다.
... (중략)
그렇다면 우리가 은유를 통해 투영해야 하는 대상은 무엇인가? 그것은 바로 사용자가 도메인에 대해 생각하는 개념들이다. 즉, 소프트웨어 객체를 창조하기 위해 우리가 은유해야 하는 대상은 바로 도메인 모델이다.
p.188

읽고 나도 도메인이라는 개념이 확실히 이해가 되진 않았지만, 그래도 여러모로 좋은 문장이 많았어서 감탄하면서 읽었다. 앞에 챕터는 읽지 않고 도메인 내용이 있는 챕터만 읽었는데도 좋은 얘기가 많았어서 얼른 다른 부분들도 읽고싶다..!

테스트코드

추가로 테스트코드를 작성하면서도 고민이 많았다.

  • 어느 부분을 테스트하고, 어디까지 테스트해야하는가?
  • 테스트코드의 중복을 어떻게 줄이는가?

이 부분도 리뷰어님이 친절하게 답변을 달아주셨다.

첫번째 의문점의 경우, 의도한 형태로 결과값이 나오는가 / 플로우에 따라서 테스트했을때 정상적으로 동장하는가 / 정상적인 플로우를 벗어나면 그에 대한 예외처리가 잘 되는가? 같은 부분이라 하셨다. 그런데 만약 실무에서 이런 부분들을 일일히 테스트하려고 하면 테스트코드도, 요구사항도 끝없이 늘어날텐데 괜찮을까? 질문드렸더니 리뷰어님의 경우 단위 테스트를 사용할 것 같다 하셨다. 해당 얘기를 듣고 단위 테스트에도 흥미가 생겼다!

또 중복을 줄이는 경우 각 컨텍스트 내에서 beforeEach를 사용해 중복된 작업을 미리 사전에 진행한다던가,, Commands기능을 사용한다던가,, 여러 부분이 있다고 하셨다.

✨ 끝내며

사실 아직 STEP1 미션이 머지된 상태가 아니라 2차로 리뷰를 받으면 수정할 부분이 있을 것 같은데, 미리 회고글을 올려뒀다. 이번 미션을 진행하면서 도메인이라는 개념에 대해 관심을 갖고, 테스트코드에 대해 고려할 사항들과 여러 문제들을 고민하게 될 수 있어 얻어가는게 참 많았다. 너무너무 재밌는 경험이었다 😊😊

추가로 이번에는 미션을 진행할때, 미루다가 황급하게 몰아서 하는게 너무 싫어서 칸반을 만든다음 각 기능별로 상태를 관리하는 일정 관리를 해봤다. 확실히 이렇게 하니까 밀리는게 덜하고, 미션에 대한 부담감도 덜어져서 너무 좋았다!


⬆ 이렇게 관리했다.

여러모로 배울점이 많았던 스텝이라 재밌게 진행했다. 끝!

좋은 웹페이지 즐겨찾기