Skip to content

Conversation

@InSeong-So
Copy link
Member

@InSeong-So InSeong-So commented Mar 19, 2022

안녕하세요, 클린코드 2기 소인성입니다.
잘 부탁드려요!😁

1차 피드백

  • Cypress 테스트 함수를 대폭 수정했습니다.
    • Command를 추가하고 상황에 맞는 케이스를 늘렸습니다.
  • 컴포넌트를 일관되게 수정했습니다.
  • 렌더링과 이벤트 바인딩을 담당할 함수를 작성했습니다.
    • 현재 네이밍이 이상할 수 있습니다. 엘리먼트를 반환하는데 렌더링은 옳지 않은데 라는 생각이 들고 있어요.
  • valid 함수의 조건문을 변경했습니다.
    • 에러 메세지를 상수화하였습니다.

구현

실행화면 바로가기

웹 VSCode로 보기


기능 요구사항

  • 로또 구입 금액을 입력하면, 금액에 해당하는 로또를 발급해야 한다.
  • 로또 1장의 가격은 1,000원이다.
  • 소비자는 자동 구매를 할 수 있어야 한다.
  • 복권 번호는 번호보기 토글 버튼을 클릭하면, 볼 수 있어야 한다.

테스트 요구사항

  • 위 기능들이 정상적으로 동작하는지 Cypress를 이용해 테스트한다.

질문

cypress의 stub 함수를 사용해 구현한 기능을 테스트하려고 했는데... document를 찾아도 잘 적용을 못하겠네요. 이 부분에 대해 예제나 좋은 방법론을 소개 받을 수 있을까요?


@InSeong-So InSeong-So changed the base branch from main to inseong-so March 19, 2022 06:17
@pocojang pocojang requested a review from Tanney-102 March 19, 2022 06:37
@pocojang pocojang changed the title [클린코드 2기 소인성] 로또 미션 [클린코드 2기 소인성] 로또 미션 STEP1 Mar 19, 2022
Copy link

@Tanney-102 Tanney-102 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인성님 안녕하세요!
로또 미션 리뷰어 오태은입니다!

전반적으로 역할에 맞게 모듈을 잘 분리해주셨고, 코드도 깔끔하게 잘 작성해주셔서 읽기 좋았습니다!
다만 컴포넌트들을 조금 더 일관성 있게 작성해주셨으면 어땠을까 싶네요~
어떤 컴포넌트는 DOM 엘리먼트를 반환하고 어떤 컴포넌트는 단순히 유틸함수만을 포함하는 객체를 반환하기도 합니다.(이 경우 사실 컴포넌트라고 불러도 될지 모르겠네요~)
인성님이 정의하시는 컴포넌트는 무엇인지 먼저 정리해보시면 좋을 것 같습니다

자세한 코멘트 남겼으니 확인 부탁드려요!

const lotto = {
numbers: [],
getNumbers: count => {
const array = [

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

testSet으로 사용될 array인가요? 사소한 로직이더라도 분명한 이름을 주는 습관을 가지시면 좋을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purchasedNumber로 변경해보겠습니다!👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purchaseNumbers 제안해봅니다 ㅎㅎ 배열이라!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트를 수정하다 보니 mock 함수의 필요성을 느끼지 못해 제거했습니다!


const lotto = {
numbers: [],
getNumbers: count => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드인데 화살표함수를 사용하신 이유가 있나요?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 getNumbers가 이름과는 다르게 많은 일을하고 있네요.
일반적으로 get~ 라는 네이밍은 특정 값이나 멤버에 대한 접근자를 의미하는데 멤버를 초기화하는 로직까지 포함하는 것 같습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cypress mock 테스트를 해보려다가 실패하면서 시간에 쫓겨 커밋했네요. 일관된 책임 부여와 역할 분리를 더 신경 쓰겠습니다. 그리고 화살표 함수를 너무 남용했네요😅

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 알고 계실지도 모르겠지만 기왕 언급된거 function 키워드 vs 화살표 함수 vs 메서드 축약표현의 차이를 정리해보시면 좋겠습니다!

Comment on lines 12 to 14
for (let i = 0; i < count; i++) {
lotto.numbers.push(array[i]);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNumbers가 화살표 함수가 아니었다면 this.numbers로 나타낼 수 있었을 것 같아요!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array[i]는 배열아닌가요?
현재 상황에서는 크게 상관 없긴 하겠지만 객체나 배열의 참조값을 직접 전달하는 것은 좋은 습관이 아닙니다.
같은 참조 값이 여러 곳에서 관리되면 언제 어떻게 변경될지 예측할 수 없으니까요~

Suggested change
for (let i = 0; i < count; i++) {
lotto.numbers.push(array[i]);
}
for (let i = 0; i < count; i++) {
lotto.numbers.push([...array[i]]);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞는 말입니다! 현재 이 함수는 상수로써 사용하려고 만들어둔 변수라 참조값을 사용했고, 몇 천개가 넘어가는 배열을 넣으려고 한 게 아니라 빠르게 쓰는 것만 생각했어요. 조심해야겠습니다🙇‍♂️

Comment on lines 19 to 21
fail: () => {
throw new Error('fail get number');
},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail은 언제 호출되나요?

Copy link
Member Author

@InSeong-So InSeong-So Mar 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail 테스트 커맨드도 만들어 두었다가... 사용을 제대로 못해 제거했는데 선언부를 놓쳤네요😅 mock 테스트를 좀 더 찾아보고 적용해볼게요!

Comment on lines 30 to 34
it('로또 구입 금액을 입력하면, 금액에 해당하는 로또를 발급해야 한다.', () => {
cy.get('[data-props="amount-input"]').type('3000');
cy.get('[data-props="confirm-button"]').click();
cy.get('[data-props="count-span"]').should('have.text', '3');
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

실제 로또 티켓(.lotto-numbers-wrapper > span)은 확인 안해도 괜찮을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.lotto-numbers-wrapper > span소비자는 자동 구매를 할 수 있어야 한다.에서 확인하기 위해 분리해두었습니다. 하지만 테스트 코드 자체를 제대로 작성하지 못해 혼란이 야기된 것 같네요😅

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

금액에 해당하는 로또 구매자동구매라는 표현을 비교해봤을때 구체적으로 어떤점이 다른지 조금 헷갈립니다!
발급한 로또 수를 보여준다 라든지 실제 테스트 되는 내용을 적어주면 어떨까요?

import { isSpecial } from './valid.js';

export const validCount = amount => {
if (isNaN(amount) || isSpecial(amount)) throw new Error('숫자를 입력해주세요.');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSpecial이 꼭 필요한 validation일까요?
만약 음수와 소수를 거르기 위함이라면 L5와 L7로 충분할 것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 이라는 문구가 허용되므로 추가했습니다! 그 외의 특수문자는 isNaN에서 걸러지긴 합니다 ㅎㅎ

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Number로 형변환을 하는데도 예외처리를 해주어야하나요? 🤔
혹시 필요하다고 하더라도 필요한 부분에 대해서만 정규식을 작성해주는 게 어떨까요?

const count = amount / 1000;
if (!Number.isInteger(amount / 1000))
throw new Error('로또 구입 금액을 1,000원 단위로 입력해 주세요.');
return Math.trunc(count);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기까지 도달하면 이미 integer일텐데 trunc가 필요한가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const count = amount / 1000;
if (!Number.isInteger(count)) throw new Error('로또 구입 금액을 1,000원 단위로 입력해 주세요.');
return count;

이게 맞겠네요!

@@ -0,0 +1,7 @@
import { REGEXP_SPECIAL } from '../constants.js';

export const isNull = value => value === null || value === undefined;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수 이름과 로직이 잘 호응하지 않는 것 같습니다.
사용처에서 isNull이라는 이름을 보고 undefined가 걸러진다는 사실까지 예측해야하는 걸까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lodash에도 isUndefined로 분리해놨네요! 수정하겠습니다.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신 lodash에서는 null과 undefined를 함께 확인해주는 메서드로 isNil을 정의하긴합니다! 참고하시면 좋을 것 같아용


export const isNull = value => value === null || value === undefined;

export const isEquals = (target1, target2) => target1 === target2;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

꼭 함수로 정의해야하는 로직인지 잘 모르겠습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다! 이 부분은 과도한 리팩토링이라고 생각 되네요.


export const isEquals = (target1, target2) => target1 === target2;

export const isSpecial = value => REGEXP_SPECIAL.test(value);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSpecial이라는 이름은 좀 모호하네요~
일반적으로 special이라는 표현을 보고 특수문자를 떠올릴 수 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 네이밍이 좋을까요? isSpecialCharacter를 고민하다가 줄였는데 네이밍을 어떻게 지어야 할까 답이 나오진 않았습니다🙄

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isSpecialCharacter 로만 네이밍 되어도 훨씬 이해하기 쉬울 것 같아요!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹은 special symbol이라고도 표현하는 것 같습니다!

@InSeong-So InSeong-So requested a review from Tanney-102 March 20, 2022 14:59
Copy link

@SINHOLEE SINHOLEE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

클래스형과는 또 다른 느낌의 스타일이네요. 배워갑니다.

@@ -0,0 +1,10 @@
export const safeExecutor =

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

safeExcutor 참신하군요. 해당 방식은 유저경험이 좋아지겠네요.

};

// TODO: DOM Parser를 만들어 렌더링을 루트 앱에서 진행합니다.
export const $eventBindedComponent = getElement => args => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기엔 safeExcutor 안입힌 이유가 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한 번 커링을 적용한 녀석이라 너무 깊어지지 않을까 하는 마음에 분리해뒀습니다ㅎㅎ

const $events = [
{
type: 'submit',
callback: event => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 callback에도 safeExcutor안붙인 이유가 있을까용?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기는 미처 생각하지 못했네요...! $addEvent 부분에 적용해봐야겠네요☺

const lottoList = LottoList(count);
const lottoCheck = LottoCheck();

$('.lotto-section').replaceChildren(lottoList, lottoCheck);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$('.lotto-section') 를 두 번 함수실행 하는것보다,

$elementRemoveClass($('.lotto-section'), 'hidden').replaceChildren(lottoList, lottoCheck);

이게 더 함수형 같은 느낌의 코드일거같아여

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 바꿀게요! 체이닝을 할 수 있음에도 생각하지 못했습니다🙇‍♂️

import { AMOUNT_UNIT, ERROR_MESSAGE } from './constants.js';
import { isEmpty } from '../helper/index.js';

const useLottoService = () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 리액트처럼 훅 스럽게 만드신 이유가 있을까요?
결국 lottoService에 public메서드 두개와 private 메서드 하나를 지닌 서비스 함수라서. 파일 단위로 모듈화 하면 똑같은 효과일거같은데 따로 의도한 내용이 있나 궁금합니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

프로젝트가 깊어지거나 DOM 렌더링을 "변경 사항만 비교하며" 작업할 때를 대비한 네이밍이에요! 훅처럼 만들어두고 외부에 useCallback 등의 메모이제이션 함수로 이를 개선해보고자 했습니다 ㅎㅎ 하지만 현재의 상태는 시노리님 말대로 클래스형 모듈이나 객체가 더 맞아요!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 최대한 리액트와 친근하게 접근하려는 의도였군요.

return Array.from(numbers);
};

const purchasesLotto = number => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그리고 만약 확장 가능하게 만들것 같으면 getLottoNumbers를 lottoAlgorithm이라고 바꾸고, purchasesLotto에서 인자로 알고리즘을 받는 형식으로 해서 로또 넘버를 구하는 방법은 purchasesLotto와 의존성을 약하게 관리하는게 유연한 코드가 될거같아요. 지금 당장은 오버엔지니어링 이겠네요 ㅋㅋㅋㅋ큐ㅠㅠㅠㅠ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니에요! 그런 세세한 생각을 했어야 현재 코드에도 잘 녹아났을텐데... 네이밍부터 이 부분은 제 미스입니다 ㅎㅎ

const [$element, $events = []] = getElement(args);
if (!isDOMElement($element)) throw new Error('첫 번째 인자로 DOM 요소를 선언해주세요.');
$events.forEach(({ type, callback }) => $addEvent($element, type, callback));
return $element;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

계속 뭔가 $element에 데코레이팅 혹은 HOC느낌의 동작을 하는 함수들이네요. 이게 함수형인가? 뭔가 아웃풋은 결국 $element라는 일관성이 가독성을 높여주는 느낌입니다.

Copy link

@Tanney-102 Tanney-102 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인성님! 확인이 늦어 죄송합니다ㅠㅠ
피드백 반영 뿐만 아니라 많은 부분에서 개선이 있었네요!
safeExecutor를 구현하신 게 인상 깊습니다! ㅎㅎ
component들도 이제 나름 일관성을 갖추게 되어 좋아보이네요~
이만 머지할테니 다음 스텝 진행하시면 되겠습니다 ㅎㅎ

@Tanney-102 Tanney-102 merged commit 5beae85 into next-step:inseong-so Mar 22, 2022
@InSeong-So
Copy link
Member Author

InSeong-So commented Mar 23, 2022

클래스형과는 또 다른 느낌의 스타일이네요. 배워갑니다.

리뷰 부탁 들어줘서 고마워욯ㅎㅎ🙇‍♂️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants