-
Notifications
You must be signed in to change notification settings - Fork 172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1단계 - 자동차 경주 구현] 하루(김하루) 미션 제출합니다. #6
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안녕하세요! 👋
하루님 코드를 리뷰하게된 Chris(양성민)입니다.
리뷰가 조금 늦어져서 죄송합니다.
첫번째 미션 수행하시느라 고생하셨습니다.
세부적인 코멘트는 각 라인에 달았습니다.
전체적인 구조 피드백은
- $cars 등 직접 DOM을 다루고 DOM에 직접 접근해서 data 값을 변경하다보니 Model과 View가 너무 강결합 되어있다는 생각이 듭니다.
- 다른 수강생들의 코드도 참고해보고, Car 를 Object나 Class로 작성해서 Model과 View를 분리하려고 노력해보세요. 👍
- Car, Game 등을 객체나 클래스로 선언했을 때 각각 어떤 속성들을 가질 수 있는지, 그리고 왜 Model과 View를 분리하는게 중요한지 좀더 고민해보면 좋을 것같습니다. 👍
고민사항 - 추상화하고, 함수로 분리하고, 중복을 제거하는 작업은 항상 미래의 유지보수성에 도움이 됩니다. 하지만 이는 가독성을 해칠 수 있습니다. 이 둘은 tradeoff입니다.
중복이 보인다고, 중복이 생긴다고 섣부르게 추상화하거나, 과도하게 추상화하면 오히려 요구사항이 추가되거나 바뀌었을 때, 중복을 허용하고 작성하는 것보다 더 더러운 코드가 될 수 있습니다.
어느시점에 중복을 제거하고 함수로 분리해야할지, 추상화해야할지는 많은 코드를 작성하고 연습하면서 몸으로 체득해야합니다. 이건 저도 아직 잘 못하는 부분이예요.
위와 같은 고민을 하고있다면
Kent C.Dodds 의 AHA Programming 이라는 발표를 꼭 들어보세요.
(https://www.youtube.com/watch?v=wuVy7rwkCfc)
다른 수강생 분들께도 정말 추천합니다.
그 외 함수의 네이밍이나 컨벤션, 테스트코드는 훌륭합니다 👍
첫번째 미션 정말 수고하셨습니다. :)
src/js/utils/constant.js
Outdated
export const VALIDATOR = { | ||
MAX_NAME_LENGTH: 5, | ||
MIN_NAME_LENGTH: 1, | ||
MIN_COUNT: 1, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VALIDATOR
라는 이름은 일반적으로 어떤 값을 검증하는 작업을 수행하는 함수나 객체 이름으로 쓰입니다.
여기서는 그냥 상수의 역할만 하고있는것같아서 CAR
나 CAR_NAME
같은 이름이 더 적절할 것 같습니다! 👍
src/js/utils/constant.js
Outdated
|
||
export const ERR_MESSAGE = { | ||
NAME_TOO_LONG: `이름은 ${VALIDATOR.MAX_NAME_LENGTH}글자 이하로 입력해 주세요.`, | ||
NAME_BLANK: '공백만으로는 이름을 구성할 수 없습니다.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러메시지의 key값은 조금 길어지더라도 어떤 에러메시지인지 key값만 보고도 명확하게 알 수 있도록
NAME_CANNOT_BE_BLANK
와 같은 좀더 명확한 이름으로 짓는게 어떨까요? 👍
const isValidRacingCount = (racingCount) => { | ||
if (racingCount < VALIDATOR.MIN_COUNT) { | ||
alert(ERR_MESSAGE.COUNT_TOO_SMALL); | ||
return false; | ||
} | ||
return true; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
함수의 이름은 값을 검증만 하는 것처럼 작성하셨는데,
실제로 함수 내부에서는 alert이라는 사이드이펙트가 존재하는 것 같아요.
함수의 이름만 보고도 함수가 무슨 일을 하는지 명확하게 알 수 있도록하고,
그 이름에 맞게 한가지의 일만 수행하도록 작성하는게 좋습니다!
alert 함수를 함수 외부로 추출하는게 좋겠어요! 👍
if (!isValidRacingCount(racingCount)) { | ||
return ($racingCountInput.value = ''); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 작성하더라도 handleRacingCountInput
함수를 사용하는 쪽에서 항상 return 값 없이 사용만 한다면 문제가 없겠지만,
실제 이 $racingCountInput.value
값을 함수 외부에서 받아서 사용할 의도가 아니라면 그 의도가 명확히 드러나도록 아래와 같이 분리해서 작성하게 좋을 것 같습니다. 👍
코드를 항상 짧게 작성하는 것이 좋은 코드라는 생각을 버리고, 코드에 의도를 명확히 드러내도록 작성하는 습관을 들여보세요! 👍
if (!isValidRacingCount(racingCount)) { | |
return ($racingCountInput.value = ''); | |
} | |
if (!isValidRacingCount(racingCount)) { | |
$racingCountInput.value = ''; | |
return | |
} |
src/js/input/handleCarNameInput.js
Outdated
const isValidCarName = (carNames) => { | ||
if (!carNames.every((carName) => isValidLength(carName))) { | ||
alert(ERR_MESSAGE.NAME_TOO_LONG); | ||
return false; | ||
} | ||
if (!carNames.every((carName) => isNotBlank(carName))) { | ||
alert(ERR_MESSAGE.NAME_BLANK); | ||
return false; | ||
} | ||
return true; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기도 마찬가지로 함수이름에 드러난 의도(CarName을 검증)에 비해서 함수가 그외 다른일(alert)들을 하고있는 것 같습니다.
alert 을 함수 외부로 분리하면 좋을것같아요 👍
const typeCarNameAndSubmit = (carNames = defaultCarNames) => { | ||
cy.get('#car-name-input').type(carNames.join(',')); | ||
cy.get('#car-name-submit').click(); | ||
cy.get('#car-name-submit').click(); | ||
return cy.get('#car-name-submit').click(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
몇줄이 중복되어 불필요한 라인이 들어간것같네요!
cy.get('.car-player').each(($div, index) => | ||
cy.get($div).should('have.text', defaultCarNames[index]), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
} | ||
}); | ||
|
||
it('전진횟수를 결정하는 함수가 "[1, 3, 3, 7]"을 입력받았을 때 "1"을 반환하는지 확인하는 테스트한다.', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트 설명이 잘못된 것 같습니다!
전진을 결정하는 함수가 3이하의 값을 받았을 때 false를 반환하고, 4이상의 값을 받았을때 true를 반환하는지 확인한다
가 아래 코드를 더 잘 설명할 것 같습니다.
cy.get('.car').then(($cars) => { | ||
const counts = [...$cars].map(($car) => { | ||
return $car.querySelectorAll('.forward-icon').length; | ||
}); | ||
const maxScore = Math.max(...counts); | ||
const winners = []; | ||
|
||
counts.forEach((carCount, index) => { | ||
if (carCount === maxScore) { | ||
winners.push(defaultCarNames[index]); | ||
} | ||
}); | ||
cy.get('#game-result-text').should( | ||
'have.text', | ||
`🏆 최종 우승자: ${winners.join(', ')} 🏆`, | ||
); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utils/getWinner 함수에서 하는일과 거의 동일한 로직으로 보이는데요.
이렇게 똑같은 로직을 다시 작성하기보다는
getWinner
함수를 리팩토링하여 DOM에 직접 접근하는 부분과 maxScore인 car들을 뽑아내는 로직을 분리하고,
위 isEffectiveScore 함수를 테스트하신 것처럼, getWinner 함수를 여기서 사용하여 테스트하면 같은 로직을 두번 작성할 필요가 없어질 것 같아요. 👍
src/js/utils/toggleVisibility.js
Outdated
export const toggleVisibility = (target) => { | ||
const sectionList = { | ||
$racingCountSection, | ||
$gameProcessSection, | ||
$gameResultSection, | ||
}; | ||
|
||
sectionList[target].toggleAttribute('hidden'); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
일반적으로 List라는 이름은 배열 자료구조를 담을때 많이 쓰입니다. 현재는 object 형태의 자료구조를 List라는 이름의 변수에 담고있네요.
또한 target 파라미터가 $racingCountSection, $gameProcessSection, $gameResultSection 셋중 하나가 아닐 경우에 대한 예외처리도 되어있지 않은 것같은데요.
위에서 말씀드린대로 setVisibility라는 이름의 다른 형태의 함수로 리팩토링해보시는걸 추천드립니다! 👍
(dom 과 Visibility(boolean) 값을 받아서 해당 값을 적용하는 형태)
안녕하세요! 그리고 Chris님께서 제 코드를 정말 꼼꼼하게 봐주신게 느껴져서 감동 받았습니다,,, 🥰🥰 너무 부끄러운 코드지만... 시간내어 꼼꼼히 봐주셔서 정말 감사합니다. 제게 피드백 주신 것과 다른 크루분들 코드와 피드백 주신 것 참고해서 수정해봤는데, Model과 View를 분리하도록 구조를 변경하는 부분이 특히 어렵게 느껴졌습니다. 제가 제대로 한건지 자신이 없는데 한 번 봐주시면 감사하겠습니다 👍 👍 👍 📝 1단계 피드백 반영 목록
|
export const setVisibility = ($target, isToBeVisible) => { | ||
if (isToBeVisible) { | ||
$target.hidden = false; | ||
} else { | ||
$target.hidden = true; | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export const setVisibility = ($target, isToBeVisible) => { | |
if (isToBeVisible) { | |
$target.hidden = false; | |
} else { | |
$target.hidden = true; | |
} | |
}; | |
export const setVisibility = ($target, visibility) => { | |
$target.hidden = !visibility; | |
}; |
또는
export const setVisibility = ($target, isToBeVisible) => { | |
if (isToBeVisible) { | |
$target.hidden = false; | |
} else { | |
$target.hidden = true; | |
} | |
}; | |
export const setHidden = ($target, hidden) => { | |
$target.hidden = hidden | |
}; |
이렇게 짧게 작성하는게 더 직관적이겠죠?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헛 한 줄로 더 가독성 좋게 작성할 수 있었군요..!! 감사합니다!! 👍👍👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍 첫번째 미션 피드백까지 잘 수행해주셨습니다.
벌써 많이 나아진게 보이네요!
코드를 작성하는 시간을 줄이고, 다른 수강생은 어떻게 작성했는지, 왜그렇게 작성했는지, 다른 리뷰어분들께 어떤 피드백을 받았는지, 좋은 코드를 더 많이 보고, 왜 그게 더 좋은 코드인지 고민하는데에 시간을 많이 투자하시면 좋을 것 같아요. 👍
@ysm0622 우와 첫 머지 감격스럽네요...!! 말씀해주신대로 무작정 코드를 작성하기보다 좋은 코드와 리뷰를 보고 앞으로 더 많이 흡수하겠습니다! 도움이되는 조언 감사합니다 :) 새해복 많이 받으세요 🥰🥰🥰 |
Chris님, 안녕하세요!
처음 리뷰요청 드리는 🙋🏻♀️ 하루(@365kim) 입니다.
저는 🙆🏻♂️ 유조(@yujo42)와 페어 프로그래밍을 진행하고 약간의 리팩토링을 거쳐 PR 올리게 되었습니다.
요약
구현기능 목록
고민사항 / 미해결과제
typeCarNameAndSubmit()
,typeRacingCountAndSubmit()
라는 메서드를 따로 분리해서 사용했는데 실은 이 두 함수도 매우 유사해서 다시 한번 별도의 메서드로 분리할 수 있어 보였습니다. 뿐만 아니라 얼럿창의 메세지 내용을 확인하는 테스트에도 같은 구문이 반복되어서 사용되었기 때문에 이 부분도 분리할 수 있을 것 같았습니다.시간 내서 리뷰해주셔서 감사합니다.
크고 작은 코멘트 많이 많이 부탁드리겠습니다!
그리고 새해 복 많이받으세요 😆