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.
즉, 객체나 배열처럼 변경 가능한 값은 대문자를 쓰지 말라는 것이었다.
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' 보다는 의미를 드러내는 단어를 써야 한다.
처음 내가 선언했던 변수명은 아래와 같았다.
const CUSTOMMATCH = input.match(/^\/\/(.+?)\\n([\d\D]+)$/);
const CUSTOM = customMatch[1];
const PARSING = customMatch[2];하지만 이 경우, Airbnb 컨벤션 철학에 어긋났었다. 어떤 것은 명사, 어떤 것은 동사, 어떤 것은 혼합되어 선언된 변수명들이었다.
그래서 아래와 같이 수정하게 되었다.
const customMatch = input.match(/^\/\/(.+?)\\n([\d\D]+)$/);
const customDelimiter = customMatch[1];
const parsingString = customMatch[2];| 세 번째 고민 : "화살표 함수 인자에 괄호를 항상 포함하기"
화살표 함수를 사용할 경우 아래와 같이 인자를 괄호 없이 선언해왔었다.
// bad
[1, 2, 3].map(x => x * x);
[1, 2, 3].map(x => {
const y = x + 1;
return x * y;
});하지만 Airbnb에서는 명확성과 일관성을 위해 항상 인자 주변에 괄호를 포함해야 한다고 명시했다.
괄호 생략 여부가 코드 스타일을 불안정하게 만들기도 하며, 인자를 추가하거나 제거할 때 Git diff에서 불필요한 수정 라인을 줄일 수 있다고 했다.
// 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 비교
if(isValid === true) {...}이와 같이 Boolean으로 선언한 isValid라는 변수를 조건문에 사용할 때 true인지 아닌지를 명확하게 기준을 유지하려고 했었다.
하지만 이미 변수 그 자체가 Boolean이기에, === true는 불필요하기에, 아래와 같이 작성하는 것만으로도 의미가 완벽히 전달된다고 했다.
if(isValid) {...}2) 문자열, 숫자 비교
하지만 반대로 문자열과 숫자의 경우 명시적으로 비교하라고 했다.
// bad
if (name) { ... }
// good
if (name !== '') { ... }문자열은 빈 문자열(' ')이 falsy로 평가되지만, ""(공백)이나 "0"은 truthy이므로, 위와 같이 명확한 기준을 생략하면, 정확히 판단할 수 없게 된다.
그렇기에, 명시적 비교가 의도를 더 정확하게 표현하게 된다.
// bad
if (collection.length) { ... }
// good
if (collection.length > 0) { ... }위의 예시에서 보면, 0은 falsy이기 때문에, if(collection.length)도 작동은 하지만, "길이가 0보다 크다"는 의미를 명확히 하기 위해서 아래와 같이 비교를 권장하게 된다.
| 기타 고민
1) 문자열은 작은 따옴표를 사용한다.
// bad
const name = "Capt. Janeway";
// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';2) 기본 매개 변수는 마지막에 배치한다.
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}3) 함수 서명의 간격
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};4) 속성 접근 시, 점 표기법을 사용한다.
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;5) 일항 증가 및 감소(++, --)는 사용하지 않는다.
// 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) 다중 줄 주석 : /** ... */를 사용
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}(2) 한 줄 주석 : //를 사용 (설명 코드 위에 배치, 오른쪽 끝에 작성 X, 첫 번째 줄이 아닌 경우 주석 앞에 한 줄 띄우기)
// 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개로 통일한다.
| 결론
짧지 않은 내용들을 하나하나 다 확인해보려고 노력한 점이 좋은 포맷을 만들어준 것 같아서 뿌듯함도 동시에 있었다.
물론 더 익숙해지기 위해서는 더 고민해봐야 할 점들이 많겠지만, 화이팅 해보려고 한다!