Airbnb JavaScript Style Guide를 통해 새롭게 알게 된 규칙

jiny·2023년 10월 20일
7
post-thumbnail

이번 시간에는 자바스크립트 문법을 공부한 이후 우아한테크코스에서 따르고 있는 컨벤션인 Airbnb JavaScript Style Guide를 학습하면서 새롭게 알게된 사실 들을 함께 공유하고자 글을 작성하게 되었습니다.

모든 Style Guide에 있는 내용이 아닌 '이런 규칙도 있었구나..!' 싶은 내용을 다룬다는 점을 먼저 알려드리며 시작해보겠습니다.

3.2 Use computed property names when creating objects with dynamic property names.

function getKey(k) {
  return `a key named ${k}`;
}

// bad
const obj = {
  id: 5,
  name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};

객체 생성 과정에서 computed property name을 사용할 경우, 리터럴 바깥이 아닌 리터럴 내부에서 값을 초기화 하도록 권장하고 있습니다.

지금은 코드가 매우 짧지만 객체의 모든 속성을 한 곳에서 정의함으로써, 코드의 일관성을 지키며 여러 군데 흩어져 있는 것이 아니기 때문에 코드를 읽는 사람 입장에서 가독성이 뛰어난 것을 알 수 있습니다.

3.7 Do not call Object.prototype methods directly

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

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

// better
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
console.log(has.call(object, key));

// best
console.log(Object.hasOwn(object, key)); // only supported in browsers that support ES2022

/* or */
import has from 'has'; // https://www.npmjs.com/package/has
console.log(has(object, key));
/* or */
console.log(Object.hasOwn(object, key)); // https://www.npmjs.com/package/object.hasown

Object의 prototype 메서드를 수정할 경우, { hasOwnProperty: false }인 객체에 대해 Object.prototype 메서드를 직접 호출하려고 하면, 이 메서드는 실제로 해당 객체의 속성이고 함수가 아니므로 예상치 못한 결과를 초래할 수 있습니다.

특히, hasOwnProperty의 경우 ES2022에 추가된 hasOwn의 사용을 권장하고 있습니다.

4.6 Use Array.from instead of spread ... for mapping over iterables, because it avoids creating an intermediate array.

// bad
const baz = [...foo].map(bar);

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

스프레드 연산자(...)를 사용하여 이터러블을 매핑하면, 이터러블을 배열로 변환한 다음 map 함수를 호출하기 때문에 중간 배열이 생성 되어 메모리 사용이 늘어날 수 있습니다.

반면 Array.from는, 이터러블을 배열로 변환하는 동시에 매핑 함수를 적용하기 때문에 중간 배열을 생성하지 않아, 메모리 사용을 줄일 수 있습니다.

Use object destructuring for multiple return values, not array destructuring.

// 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);

배열 보다 객체 구조 분해 할당을 선호하는 이유

  • 가독성 - 각 값에는 명시적인 이름이 부여되므로 코드를 읽는 사람이 이해하기 쉽다.
  • 유지 보수 - 함수가 반환하는 값의 순서를 변경하거나 새로운 값을 추가하더라도 기존 코드의 변경이 발생하지 않는다.
  • 유연성 - 필요한 값만 선택하여 추출할 수 있기 때문에, 불필요한 변수 선언을 줄이고 코드를 더욱 깔끔하게 만들 수 있다.

이러한 이유로 함수에서 값을 반환할 때, 배열 보단 객체 구조 분해 할당을 적용하는 것을 고려하는 것이 좋습니다.

7.9 Always put default parameters last

// bad
function handleThings(opts = {}, name) {
  // ...
}

// good
function handleThings(name, opts = {}) {
  // ...
}

기본 매개변수가 중간에 위치하면, 생략하려는 매개변수 뒤의 모든 매개변수에 대해 명시적으로 undefined를 전달하기 때문에 좋지 못합니다.

하지만 마지막에 위치할 경우, 함수를 호출할 때 필요한 매개변수만 전달하고 나머지는 생략할 수 있어 코드의 유연성을 향상 시킬 수 있습니다.

또한, 함수를 호출하는 코드에서 어떤 매개변수가 생략되었는지 쉽게 파악할 수 있습니다.

8.5 Avoid confusing arrow function syntax (=>) with comparison operators (<=, >=).

// bad
const itemHeight = (item) => item.height <= 256 ? item.largeSize : item.smallSize;

// bad
const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;

// good
const itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize);

// good
const itemHeight = (item) => {
  const { height, largeSize, smallSize } = item;
  return height <= 256 ? largeSize : smallSize;
};

자바스크립트 함수의 경우 리턴 문을 생략해서 표현하는 것이 가능하다. (case 1, 2, 3)

하지만, bad case 처럼 ()를 사용하지 않고 리턴 문을 생략할 경우 화살표 함수의 화살표와 비교 연산자가 헷갈릴 수 있기 때문에 다음과 같이 ()로 표현하여 가독성을 개선 시킬 수 있습니다.

9.7 Class methods should use this or be made into a static method unless an external library or framework requires using specific non-static methods. Being an instance method should indicate that it behaves differently based on properties of the receiver.

// bad
class Foo {
  bar() {
    console.log('bar');
  }
}

// good - this is used
class Foo {
  bar() {
    console.log(this.bar);
  }
}

// good - constructor is exempt
class Foo {
  constructor() {
    // ...
  }
}

// good - static methods aren't expected to use this
class Foo {
  static bar() {
    console.log('bar');
  }
}

클래스의 존재하는 메서드들은 인스턴스 메서드로써, 주로, 인스턴스에 존재하는 필드들을 참조하여 클래스를 만든 목적(역할)을 수행하기 위해 사용됩니다.

하지만, bad case의 경우 Foo의 this를 참조하고 있지 않는 메서드 이기 때문에, 클래스의 유틸리티 역할(헬퍼 함수)로써 제공되는 정적 메서드(static)를 사용해야 합니다.

10.5 Do not export mutable bindings.

// bad
let foo = 3;
export { foo };

// good
const foo = 3;
export { foo };

bad case에서 내보내려는 foo 함수의 경우 let 키워드로, 재할당이 가능한 변수입니다.

즉, 그 변수의 값이 코드의 다른 부분에서 변경될 수 있습니다. 이는 side effect를 만들어내 디버깅을 어렵게 만듭니다.

good base 처럼, const를 사용하면 let과 다르게 변수의 불변성을 보장하여 문제점을 해결할 수 있습니다.

10.10 Do not include JavaScript filename extensions

// bad
import foo from './foo.js';
import bar from './bar.jsx';
import baz from './baz/index.jsx';

// good
import foo from './foo';
import bar from './bar';
import baz from './baz';

bad case 처럼 자바스크립트 파일에 확장자를 포함할 경우, 해당 모듈을 사용하는 다른 모듈들이 의존성을 가지게 되어, 변경 가능성이 높아집니다.

예를 들어 js 파일을 ts로 마이그레이션 할 경우, 확장자를 명시한 모든 import문을 수정해야 해야하는 반면, 확장자를 생략한 경우 import한 코드를 수정할 필요가 없습니다.

또한, webpack이나 package.json을 통해 확장자를 생략하더라도 자동으로 확장자를 찾아 실행하도록 설정하는 것도 가능하기 때문에, 굳이 명시할 이유가 없습니다.

11.1 Don’t use iterators. Prefer JavaScript’s higher-order functions instead of loops like for-in or for-of

const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}
sum === 15;

// good
let sum = 0;
numbers.forEach((num) => {
  sum += num;
});
sum === 15;

// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;
// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
  increasedByOne.push(numbers[i] + 1);
}

// good
const increasedByOne = [];
numbers.forEach((num) => {
  increasedByOne.push(num + 1);
});

// best (keeping it functional)
const increasedByOne = numbers.map((num) => num + 1);

자바스크립트는 객체지향, 함수형 패러다임을 모두 사용할 수 있는 프로그래밍 언어입니다. 이로 인해, 함수를 일급 객체로써 사용이 가능하며 고차함수를 사용해 반복되는 로직을 개선할 수 있습니다.

특히, for문의 경우 forEach, reduce, filter 등 콜백 함수를 인자로 받는 고차 함수를 사용하게 되면 로직의 가독성과 재사용성을 증가 시킵니다.

또한, filter나 reduce의 경우 새로운 객체나 배열을 반환하기 때문에 불변성을 유지하여 side effect를 줄일 수 있습니다.

21.1 Yup(semicolon)

// bad - raises exception
const luke = {}
const leia = {}
[luke, leia].forEach((jedi) => jedi.father = 'vader') // ReferenceError: Cannot access 'leia' before initialization

// bad - raises exception
const reaction = "No! That’s impossible!"
(async function meanwhileOnTheFalcon() {
  // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
  // ...
}()) // TypeError: "No! That’s impossible!" is not a function

// bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI!
function foo() {
  return
    'search your feelings, you know it to be foo'
}

// good
const luke = {};
const leia = {};
[luke, leia].forEach((jedi) => {
  jedi.father = 'vader'; // 
});

// good
const reaction = 'No! That’s impossible!';
(async function meanwhileOnTheFalcon() {
  // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
  // ...
}());

// good
function foo() {
  return 'search your feelings, you know it to be foo';
}

JavaScript V8 엔진은 세미콜론이 누락된 경우 코드를 파싱하는 과정에서 세미콜론이 없을 경우 자동으로 삽입합니다.

이 기능은 코드의 가독성을 향상시키고 코드 작성을 쉽게 해주지만, 일부 상황에서는 예상치 못한 문제를 일으킬 수 있습니다.

예를 들어, 줄바꿈 후에 오는 토큰이 현재 문장의 일부로 해석될 수 없는 경우 해당 줄바꿈 직전에 세미콜론을 삽입합니다.

이 기능은 대부분의 경우 잘 작동하지만, 줄바꿈이 문장의 끝이 아니라 문장의 중간 부분인 경우 문제가 발생할 수 있습니다. 이러한 상황은 코드가 의도한 대로 작동하지 않게 만들어 디버깅을 어렵게 만듭니다.

그렇기 때문에, prettier와 같은 포맷터를 통해 semicolon을 컴파일 단에서 추가하는 것이 에러의 위험을 줄일 수 있습니다.

23.10 You may optionally uppercase a constant only if it (1) is exported, (2) is a const (it can not be reassigned), and (3) the programmer can trust it (and its nested properties) to never change.

// bad
const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file';

// bad
export const THING_TO_BE_CHANGED = 'should obviously not be uppercased';

// bad
export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables';

// ---

// allowed but does not supply semantic value
export const apiKey = 'SOMEKEY';

// better in most cases
export const API_KEY = 'SOMEKEY';

// ---

// bad - unnecessarily uppercases key while adding no semantic value
export const MAPPING = {
  KEY: 'value'
};

// good
export const MAPPING = {
  key: 'value',
};

1번째 bad case와 2번째 bad case를 통해 변경 가능성이 거의 없으며, export를 통해 재 사용 가능성이 있는 경우 상수로 사용해야 하는 것을 유추해볼 수 있습니다.

또한, 3번째 bad case 처럼 상수가 객체인 경우 내부 프로퍼티 까지 상수로 적용하는 것은 불필요하며, 이렇게 하는 것이 아무 의미 없기 때문에 소문자로 사용하는 것을 지양해야 합니다.

레퍼런스

AirBnb Style Guide

ESLint

0개의 댓글