우아한테크코스 레벨1 자동차경주
미션을 수행하며 코드리뷰를 받았다. 내 코드에 대한 리뷰 및 다른 크루들이 받은 리뷰들을 살펴보다가, 나중에 리마인드하면 좋을 내용들이 많아 보여서 메모한 내용을 포스팅한다.
isCarNameBlank
와 같이 is
라는 prefix가 붙은 네이밍은 car name이 비어있는지 여부에 따라서 true나 false를 반환한다고 예상된다.const validateDuplicateCarName = names => {
return new Set(names).size === names.length;
// before
static isInvalidInput(input) {
if(this.isA(input)) {
return true;
}
if(this.isB(input)) {
return true;
}
if(this.isC(input)) {
return true;
}
return false
}
// refactor 1 - 단순화
return this.isA(input) || this.isB(input) || this.isC(input);
// refactor 2 - or로 이어진 조건들을 some메서드로 리팩토링
return ['isA', 'isB', 'isC'].some((method) => this[method](input));
// before
static isInvalidInput(input) {
if(this.isA(input) && this.isB(input) && this.isC(input)) {
return true;
}
return false
}
// refactor 1 - 단순화
return this.isA(input) && this.isB(input) && this.isC(input);
// refactor 2 - and로 이어진 조건들을 some메서드로 리팩토링
return ['isA', 'isB', 'isC'].every((method) => this[method](input));
custom commands
의 경우 문서에서 support
폴더에 정의하길 권장한다. support/commands.js
파일에 정의하면 좋다.cy.get('.input-section').type('east,west,south,north{enter}');
// .input-section 클래스 엘리먼트에 'east,west,south,north'를 입력하고 enter를 누른다.
view에게 실행해!
라고만 알려주는 편이 역할 분리가 된 것이다.this.view.render(selector, position, template);
// before
const func = (input) => {
return input.split(',').map((input) => input.trim());
};
// refactor
const func = (input) => input.split(',').map((input) => input.trim());
특정 element
부터 탐색하는 방법을 고민해보자.element = baseElement.querySelector(selector);
// before : forEach메서드 사용
renderSomething(things) {
let template = '';
things.forEach((thing) => {
template += somethingTemplate(thing.data);
});
this.$targetElement.insertAdjacentHTML('beforeend', template);
}
// after: map으로 리팩토링
renderSomething(things) {
const template = things
.map((thing) => somethingTemplate(thing.data))
.join('');
this.$targetElement.insertAdjacentHTML('beforeend', template);
}
private
이나 상속
등 사용 측면에서 유리한(편한) 면이 더 있다고 생각해서, 팀내에서 class를 더 선호하기도 하다.Model
에서 제공하고, 검증은 Controller
에서 하는 방법도 있을 것 같다.직접적으로 객체를 변경하는 것
은 의도치 않은 사이드이펙트를 가져올 수 있기 때문이다.// before
function sortCars(cars) {
return cars.sort((a, b) => b.position - a.position);
}
// after - 복사본 만들어서 sort
function sortCars(cars) {
const carsCopy = [...cars];
return carsCopy.sort((a, b) => b.position - a.position);
}
nubmerOfUsers
같은 변수명보다는 더 간결히 userCount
가 더 낫다. 또는 number
를 앞에 쓰고 싶다 한다면, 약간 문법 파괴를 하더라도 numUsers
같이 짧게 쓰는 경우도 있다. 아무튼 numberOfUsers
처럼 전치사를 넣는 네이밍은 피하는 것이 좋다.#
, class의 .
를 이런 함수를 만들어서 편하게 사용할 수도 있다(전역 프로토타입 객체를 건드리는 것은 권장하는 방법까진 아니나, 이 정도 미션에서는 가능할 수도 있 다.).const DOM = {
CAR_NAME_INPUT = 'car_name_input',
}
String.prototype.toID = function() {
return `#${this}`
}
console.log(DOM.CAR_NAME_INPUT.todID()); // #car_name_input
// (1)
if (!conditionA || conditionB) return false;
return true;
// (2)
if (conditionA && !conditionB) return true;
return false;
// (3)
return conditionA && !conditionB;
hasSpaceInName = (names) => {
names.some((name) => Array.from(anme).some((ch) => ch.match(/ /)));
};
null
""
(empty)undefined
const arr = [
{ name: 'east', location: 2 },
{ name: 'west', location: 5 },
{ name: 'north', location: 3 },
];
function before() {
let maxLocation = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i].location >= maxLocation) {
maxLocation = arr[i].location;
}
}
return maxLocation;
}
function after() {
return Math.max(...arr.map(({ location }) => location));
}
console.log(before()); // 5
console.log(after()); // 5
class autoEventTargeting {
bindEventListener(type, selector, callback) {
const children = [...$$(selector)];
const isTarget = (target) =>
children.includes(target) || target.closest(selector);
this.$app.addEventListener(type, (e) => {
if (!isTarget(e.target)) return;
e.preventDefault();
callback(e);
});
}
bindEventListeners() {
this.bindEventListener(
'click',
'#car-name-button',
this.submitCarNames.bind(this),
);
this.bindEventListener(
'click',
'#racing-count-button',
this.submitRacingCount.bind(this),
);
}
}
type="submit"
으로 하거나, 전체를 form으로 감싸서 input에서 enter키를 입력해도 확인 버튼을 누른 것과 동일하게끔 구성하는 것이 좋다.rAF
를 왜 사용할까?
게임을 할때 FPS
(Frame Per Second)라는 단어를 많이 사용하는데 1초동안 몇번의 화면을 보여주는지를 알려주는 단어인데 이 FPS
가 낮아지게 되면 사용자 입장에서 부드러워야 할 화면이 뚝 끊기게 되어 사용자 입장에서 불편하게 느껴진다.
rAF
가 나왔다. rAF
는 사용자가 이러한 끊김을 느끼지 않도록 1프레임마다 호출을 보장해주는 web api
이다. 디스플레이의 환경에 맞게 최적의 빈도로 실행을 해줘서 사용자 경험을 챙겨줄 수 있다setTimeout
, setInterval
로 구현해도 되는거 아니냐! 라고 할수도 있겠지만 rAF
는 앞의 set~ api
와는 동작하는 방식이 다르기 때문에 이러한 차이가 있다. 자바스크립트는 기본적으로 싱글스레드로 동작하는데 setTimeout
, setInterval api
는 지정해준 시간이 지나면 해당 콜백함수들을 task queue
에 넣어주는데, 이전에 큐에 쌓여있던 함수에서 지연될 가능성을 배제하지 못하기 때문에 지정해준 타이머에 명확히 실행해준다는 보증이 없다. 그에 비해 rAF
는 Animation frames
라는 브라우저 렌더링과 관련된 task를 별도로 처리하는 queue
를 통해 실행시점을 보증할 수 있다.
rAF
와 묶여 있는 내용들은 브라우저의 렌더링 과정(+ reflow
, repaint
), 자바스크립트의 동작방식(이벤트 루프), 큐의 우선순위(Micro Task Queue
, Animation Frames
, Task Queue
) 등으로 참고하면 좋다.