0304

FE

Airbnb JavaScript를 사용하며

| 서론

우아한테크코스 8기 프리코스에 참여하면서 Airbnb JavaScript를 접하게 되었다. 처음 알게 된 스타일 가이드이다보니, 친해지는 과정과 고민이 되는 여러 부분들이 존재했다.

이번 포스팅에서는 Airbnb JavaScript에 대해서 접하고 공유하고 싶은 점들을 소개해보려고 한다.

| Airbnb란?

Airbnb의 자바스크립트 가이드는 단순히 들여쓰기나 세미콜론 여부를 정하는 문법 규칙이 아니다.

코드의 의도와 맬강르 명확하게 드러내는 설계 원칙이다. 특히 변수명, 상수, 함수명 등에서 "의미적 일관성(semantic consistency)"을 강조한다.

| 첫 번째 고민 : "상수는 대문자로 쓸까?"

Airbnb를 처음 정독한 뒤에는 모든 상수를 대문자로 작성했다. 하지만 놓치고 있는 부분이 있었다.

Only write constants in UPPER_CASEif and only if the are truly immutable and hard-coded literal values.

즉, 객체나 배열처럼 변경 가능한 값은 대문자를 쓰지 말라는 것이었다.

JavaScript
const ERROR_MESSAGES = {
  INVALID_FORMAT: '[ERROR] Invalid input format...',
  NEGATIVE_NUMBER: '[ERROR] Negative numbers are not allowed'
};

ERROR_MESSAGES와 같이 실제로 런타임 중 변경되지 않는 경우에는 대문자로 작성해야 하지만, 그렇지 않은 경우에는 Camel 케이스를 사용하기로 했다. 이를 통해 몇 가지 기준을 세웠다.

구분

예시

표기법

이유

전역 불변 값 (환경변수, 설정값)

ERROR_MESSAGES, API_URL

UPPER_CASE

불변 객체로서 전역에서 공유

지역 상수 (함수 내부 계산용)

customMatch, num, result

camelCase

매번 계산되는 값

진짜 하드코딩된 상수

MAX_COUNT, DEFAULT_LIMIT

UPPER_CASE

리터럴 불변 값

동적 구조를 가진 배열/객체

delimiters, options

camelCase

내부 변경 가능성 있음

| 두 번째 고민 : "변수명을 어떻게 결정할까?"

Airbnb는 변수명을 작성할 때, "의도를 담아라"고 말한다. 즉, 'a', 'b', 'data' 보다는 의미를 드러내는 단어를 써야 한다.

처음 내가 선언했던 변수명은 아래와 같았다.

JavaScript
const CUSTOMMATCH = input.match(/^\/\/(.+?)\\n([\d\D]+)$/);
const CUSTOM = customMatch[1];
const PARSING = customMatch[2];

하지만 이 경우, Airbnb 컨벤션 철학에 어긋났었다. 어떤 것은 명사, 어떤 것은 동사, 어떤 것은 혼합되어 선언된 변수명들이었다.

그래서 아래와 같이 수정하게 되었다.

JavaScript
const customMatch = input.match(/^\/\/(.+?)\\n([\d\D]+)$/);
const customDelimiter = customMatch[1];
const parsingString = customMatch[2];

| 세 번째 고민 : "화살표 함수 인자에 괄호를 항상 포함하기"

화살표 함수를 사용할 경우 아래와 같이 인자를 괄호 없이 선언해왔었다.

JavaScript
// bad 
[1, 2, 3].map(x => x * x);

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

하지만 Airbnb에서는 명확성과 일관성을 위해 항상 인자 주변에 괄호를 포함해야 한다고 명시했다.

괄호 생략 여부가 코드 스타일을 불안정하게 만들기도 하며, 인자를 추가하거나 제거할 때 Git diff에서 불필요한 수정 라인을 줄일 수 있다고 했다.

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

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

| 네 번째 고민 : "===와 !==를 사용하기"

Javascript는 다른 언어와 달리 타입 변환을 자동으로 수행한다.

이때, ==와 !=는 타입을 암묵적으로 변환한 뒤 비교하기 때문에, 의도하지 않은 결과가 자주 발생할 수 있다.

따라서 Airbnb는 엄격한 비교를 수행하는 ===와 !==를 사용하는 것으로 명시했다.

단, 값과 타입이 모두 같을 때에만 true로 평가되도록 하는 점이 전제이다.

그러나, 예외적으로 허용되는 경우도 있었다.

바로 == null이다. 이 경우에는 null과 undefined를 동시에 체크할 수 있기에, 특수한 경우로 허용한다.

| 다섯 번째 고민 : "부울, 문자열, 숫자 비교 시 명확한 기준을 유지하기"

조건문에 들어가는 조건을 명시할 때 사용되는 변수의 타입에 따라서 비교의 명확한 기준을 크게 고려하지 않아 왔었다.

1) Boolean 비교

JavaScript
if(isValid === true) {...}

이와 같이 Boolean으로 선언한 isValid라는 변수를 조건문에 사용할 때 true인지 아닌지를 명확하게 기준을 유지하려고 했었다.

하지만 이미 변수 그 자체가 Boolean이기에, === true는 불필요하기에, 아래와 같이 작성하는 것만으로도 의미가 완벽히 전달된다고 했다.

JavaScript
if(isValid) {...}

2) 문자열, 숫자 비교

하지만 반대로 문자열과 숫자의 경우 명시적으로 비교하라고 했다.

JavaScript
// bad
if (name) { ... }

// good
if (name !== '') { ... }

문자열은 빈 문자열(' ')이 falsy로 평가되지만, ""(공백)이나 "0"은 truthy이므로, 위와 같이 명확한 기준을 생략하면, 정확히 판단할 수 없게 된다.

그렇기에, 명시적 비교가 의도를 더 정확하게 표현하게 된다.

JavaScript
// bad
if (collection.length) { ... }

// good
if (collection.length > 0) { ... }

위의 예시에서 보면, 0은 falsy이기 때문에, if(collection.length)도 작동은 하지만, "길이가 0보다 크다"는 의미를 명확히 하기 위해서 아래와 같이 비교를 권장하게 된다.

| 기타 고민

1) 문자열은 작은 따옴표를 사용한다.

JavaScript
// bad
const name = "Capt. Janeway";

// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;

// good
const name = 'Capt. Janeway';

2) 기본 매개 변수는 마지막에 배치한다.

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

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

3) 함수 서명의 간격

JavaScript
// bad
const f = function(){};
const g = function (){};
const h = function() {};

// good
const x = function () {};
const y = function a() {};

4) 속성 접근 시, 점 표기법을 사용한다.

JavaScript
const luke = {
  jedi: true,
  age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;

5) 일항 증가 및 감소(++, --)는 사용하지 않는다.

JavaScript
// bad

const array = [1, 2, 3];
let num = 1;
num++;
--num;

let sum = 0;
let truthyCount = 0;
for (let i = 0; i < array.length; i++) {
  let value = array[i];
  sum += value;
  if (value) {
    truthyCount++;
  }
}

// good

const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;

const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;

6) 주석

(1) 다중 줄 주석 : /** ... */를 사용

JavaScript
/**
 * make() returns a new element
 * based on the passed-in tag name
 */
function make(tag) {
  // ...
  return element;
}

(2) 한 줄 주석 : //를 사용 (설명 코드 위에 배치, 오른쪽 끝에 작성 X, 첫 번째 줄이 아닌 경우 주석 앞에 한 줄 띄우기)

JavaScript
// is current tab
const active = true;

function getType() {
  console.log('fetching type...');

  // set the default type to 'no type'
  const type = this.type || 'no type';
  return type;
}

7) Tab size는 공백 2개로 통일한다.

| 결론

짧지 않은 내용들을 하나하나 다 확인해보려고 노력한 점이 좋은 포맷을 만들어준 것 같아서 뿌듯함도 동시에 있었다.

물론 더 익숙해지기 위해서는 더 고민해봐야 할 점들이 많겠지만, 화이팅 해보려고 한다!

https://github.com/airbnb/javascript?tab=readme-ov-file