자바스크립트 스타일 가이드 학습하기 #1 (Airbnb) - 객체, 배열

REASON·2022년 11월 3일
1

STUDY

목록 보기
108/127

유-명한 자바스크립트 코드 스타일 가이드 중 하나인 에어비엔비 스타일 가이드 학습하기

코드 스타일은 정말 중요하다고 생각하면서도
어떻게 작성하는 것이 좋은지에 대한 고민을 특별히 해본 적은 없었던 것 같다.
보통 중괄호를 엔터 치냐, 스페이스를 넣냐, 탭을 두칸쓰냐 네칸쓰냐와 같은 코딩 컨벤션에 대해서만 생각해봤던 것 같다.

학습 목표 ✨

이번에 에어비앤비의 자바스크립트 스타일 가이드 학습을 통해 평소 내가 작성하던 코드 스타일의 문제점에는 무엇이 있는지, 스타일 가이드에서 제시한 bad 코드로 작성해왔는지 살펴보고 코드 스타일을 개선 하고자 한다.


객체

단축해서 작성하는 경우, 객체 선언의 첫번째부터 시작하도록 작성한다.


// bad
const obj = {
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  lukeSkywalker,
  episodeThree: 3,
  mayTheFourth: 4,
  anakinSkywalker,
};

// good
const obj = {
  lukeSkywalker,
  anakinSkywalker,
  episodeOne: 1,
  twoJediWalkIntoACantina: 2,
  episodeThree: 3,
  mayTheFourth: 4,
};

단축 구문이랑 같이 작성한 적은 아직 없었던 것 같지만 혹시 몰라서 일단 메모..!
아마 몰랐어도 생긴게 불-편 하게 생겨서 good 예시로 했을 것 같기는 한데

갑자기 다른 데로 새버렸다.

이거보니까 떠오른게 얼마 전에 리액트 props 중에 default 프롭스를 뒤로 몰아야지 했던게 생각났다.
생각난 김에 고치고 와야지~! 하고 고치러 갔는데 애초에 잘 적어놨네..

기억력 왜이래ㅠㅠ btnSize 만 size로 변경시키고 코드 리팩토링 조금 해서 그냥 메인 브랜치에 바로 푸쉬시켰다. 아무튼 지금 리액트를 하려고 했던 건 아니였는데.. ㅋㅋㅋㅋ 다시 본론으로 돌아가자.

Object.prototype 메소드를 직접 호출하지 말 것

이게 무슨 말이지 하고 코드를 살펴봤다.

// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// best
const has = Object.prototype.hasOwnProperty; // 캐싱해서 쓰기
console.log(has.call(object, key));

hasOwnProperty 를 아직까지 써본 적은 없는데 인스턴스에서 직접 호출하지 말고
Object로부터 호출시켜서 this를 바인딩해서 사용하라는 건가
근데 call어떻게 썼더라 기억이 안나네..하고 MDN 다시 보고왔다. ㅋㅋㅋㅋ
한번 사용해보면 더 좋을 것 같아서 best 케이스 코드를 따라해봤다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const person = new Person("아이유", 30);
console.log(person)
console.log(person.hasOwnProperty('name')); // X

const has = Object.prototype.hasOwnProperty;
console.log(has.call(person, 'name')) // O

hasOwnProperty를 어느 경우에 사용하는지도 궁금했는데
function으로 만든 생성자 함수에 prototype으로 함수를 추가해서
for..in 문을 돌렸을 때 다음과 같은 문제점이 있었다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function(){
  console.log(`안녕! ${this.name}!`);
}

const person = new Person('아이유', 30);

for(let key in person){
  console.log(key); // name, age, sayHi
}

Person 생성자 함수에 sayHi라는 메서드를 추가하고 person 인스턴스를 만들었다.
그리고 해당 인스턴스를 for..in 문으로 순회해보면
name, age, sayHi가 출력되는 것을 확인할 수 있다.

sayHi 메서드처럼 prototype에 있는 것을 제외하기 위해서
hasOwnProperty를 사용해서 조건문을 걸어줄 수 있다.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function(){
  console.log(`안녕! ${this.name}!`);
}

const person = new Person('아이유', 30);

const has = Object.prototype.hasOwnProperty;
for(let key in person){
  if(has.call(person, key)){
    console.log(key); // name, age
  }
}

아무튼 이런 경우에 hasOwnProperty를 사용해볼 수 있지 않을까..
물론 아직까지 써본 적은 없어서 이번 예시 코드 작성해보면서 처음 써봤다. ㅎㅎ

객체 복사에는 Object.assign 대신 구조분해할당

예전에 이 ... 처음 봤을 때가 생각난다.
아니 무슨 이런 기괴한 문법이있지? 했었는데 지금은 언제 그랬냐는듯 너무 잘 쓰는중.

// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // `original`을 변조
delete copy.a;

// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }

// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }

const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

이 예제 코드는 심지어 very bad도 있다.
사실 저렇게 쓰는 건 한번도 본 적도 없을 뿐더러 delete를 사용해야 한다니 ㅋㅋㅋ

배열

배열에 직접 값을 할당하는 것 보다 push 사용하기

const someStack = [];

// bad
someStack[someStack.length] = 'abracadabra';

// good
someStack.push('abracadabra');

배열에 직접 값 할당하는 경우는 생각보다 많이 했었는데 사실 이 경우엔
배열의 가장 맨 마지막 요소로 추가할 때나 push를 사용해야 된다는 것 같다.
마지막에 추가하는 경우가 아니라면, 어쩔 수 없이 직접 접근 해야될테니.

Array.from 대신 ... 도 사용해보자.

이번에도 어김없이 등장한 ... 사실 ...이 정말 유용하긴 한 것 같다.
Array.from은 set으로 만든 것을 다시 배열로 만들어야 한다거나,
이 비슷한 경우에 종종 사용하곤 했는데 ...을 사용하는 방법이 더 권장되는 경우가 있구나.

아래 코드는 Array.from을 써도 되지만 ...을 더 권장하고 있다.

  • 순회 가능한 객체를 배열로 변환할 때는 ...
const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];

무조건 적으로 ...을 Array.from을 대신해서 사용할 필요는 없는 것 같다.

Array.from을 사용해야 하는 경우

  • 객체를 배열로 변환할 때
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };

// bad
const arr = Array.prototype.slice.call(arrLike);

// good
const arr = Array.from(arrLike);

위 코드는 arrLike에 length값이 있어야 Array.from을 사용해서 배열로 만들 수 있다.
length가 없으면 빈 배열이 생성되기 때문에 어떤 상황에서 이렇게 length 값을 추가해서 객체를 배열로 변환 시키는지는 아직 잘 모르겠다. 인풋 값이 저런 형태로 들어오는 경우가 있는걸까..?

  • 매핑할 때
// bad
const baz = [...foo].map(bar);

// good
const baz = Array.from(foo, bar);

무조건 Array.from 대신 ...을 권장하지 않는 이유중 하나가 여기 있을 것 같다.
근데 뭘 매핑하는 예시 코드인지 이해를 못하겠다...

배열 메서드에는 return을 쓰자

단일 표현식인 경우에만 return을 생략할 수 있다.

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map(x => x + 1);

// bad - no returned value means `acc` becomes undefined after the first iteration
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
  acc[index] = flatten;
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
  acc[index] = flatten;
  return flatten;
});

// bad
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  } else {
    return false;
  }
});

// good
inbox.filter((msg) => {
  const { subject, author } = msg;
  if (subject === 'Mockingbird') {
    return author === 'Harper Lee';
  }

  return false;
});

평소에 단일 표현식도 습관때문에 return을 꼭 명시해주는 경우가 있는데
새로운 배열을 반환하는 배열 내장 메서드들의 경우 return을 꼭 사용해라. 라는 것 같다.
내부에서 리턴도 안 하고 변경만 시킬거면 쓰지마!이런 예시인듯하다.
그리고 if-else 구문도 마찬가지로 불필요한 else를 만들어서 return 하지 말고 else를 생략해서 쓰라는 것 같다.

디스트럭처링

비구조화할당, 구조분해할당과 같은 이름으로도 불리는 destructuring
리액트 공부하면서 정말 많이 사용하게 돼서 지금은 익숙해졌다.

디스트럭처링을 사용하면 단순히 코드만 간결해진다고 생각했는데
비구조화를 하면 임시 참조를 만들지 않아서
객체에 반복적인 접근도 방지된다는 장점이 있다는 것은 처음 알았다.


// bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;

  return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
  const { firstName, lastName } = user;
  return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName }) {
  return `${firstName} ${lastName}`;
}

예시 코드를 보니 bad, good, best 케이스 다 한번씩은 사용해봤던 것 같다.
요즘엔 best 케이스로 사용하는 쪽이 더 편한 것 같아서 아까 저렇게 리액트 코드를 리팩토링 시켰었는데 오.. best 케이스로 리팩토링 한 거였다니.
다행이다 싶으면서도, 가끔 한번씩 나도 모르게 bad 케이스로 작성하는 경우가 있어서 bad 케이스로 사용하는 경우를 조심하는 게 좋을 것 같다.

const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

이건 한번씩 망각하고 배열[0] 과 같은 형태로 사용하던 적이 있었는데
요즘엔 의식하면서 하니까 확실히 const [변수명] = 배열 로 사용하게 되는 것 같다.
이건 정말 사용해보면서 절실히 느낀 거지만, 배열[0], 배열[1] 로 사용했을 때 아니 이게 뭐였지? 하고 코드 한참 다시 들여다본 적이 있어서 good 케이스로 작성했을 때 진짜 너무 좋다..
다시 안 봐도 된다. 작명의 장점을 아주 잘 뽑아먹을 수 있었다.

여러개의 값을 반환할 때는 객체 비구조화를 사용할 것.

배열 디스트럭처링을 해버리면 데이터 순서를 생각해야 하기 때문에
값이 여러개인 경우 객체 디스트럭처링을 해야 된다는 내용이다.

// bad
function processInput(input) {
  // then a miracle occurs
  return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // then a miracle occurs
  return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);

배열 비구조화 할당을 잘 사용하고 있어서 망각하고 있었던 부분인데,
배열은 순서가 중요하다. 라는 점을 다시금 상기시키게 해주었다.
객체 비구조화 할당이 필요한 경우도 잘 고민해서 사용해봐야겠다.


참고 자료
Airbnb JavaScript Style Guide

0개의 댓글