우아한테크코스 레벨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)undefinedconst 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) 등으로 참고하면 좋다.