우아한테크코스 프리코스 과제에는 모두 '자바스크립트 컨벤션'을 지키라고 말한다. 이와 함께 널리 알려진 Airbnb 자바스크립트 컨벤션 링크를 첨부하고 있다.
사실 자바스크립트 컨벤션은 프리코스 1주 차 때 공부했어야 했는데, 너무 길어서 다 읽지 않고 잠깐 훑어보고는 다 알겠지? 하고 생각했었다. 하지만 2주 차 피어 리뷰를 받고 나는 아무것도 모르고 있구나, 하고 깨달았다.
그때 마지막 줄은 띄우는 것, 쌍따옴표 대신 홑따옴표를 쓰는 것, else를 지양하는 것(얼리 리턴), for 대신 forEach를 쓰는 것.. 등등을 해야한다는 리뷰를 받았는데, 이것들이 모두 컨벤션이었다는 것을 처음 알게 되었다.
그래서 자바스크립트 컨벤션을 한번이라도 정독해야겠다는 생각을 하게 되었고, 이번 포스팅에서 공부한 내용을 정리하려고 한다.
모든 내용을 정리하는 것이 아니라 내가 몰랐던 것만 정리할 예정이다.
// const와 let은 선언된 블록 안에서만 존재합니다.
{
let a = 1;
const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError
// bad
const item = new Object();
// good
const item = {};
lukeSkywalker가 lukeSkywalker를 의미하면 굳이 할당해줄 필요가 없다는 뜻인 것 같다.
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker,
};
그리고 이것들은 객체 선언의 시작 부분에 모아주자.
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// 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,
};
// bad
const items = new Array();
// good
const items = [];
const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
...
을 사용하자// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
// bad
const arr = Array.prototype.slice.call(arrLike);
// good
const arr = Array.from(arrLike);
// bad
const arr = [
[0, 1], [2, 3], [4, 5],
];
const objectInArray = [{
id: 1,
}, {
id: 2,
}];
const numberInArray = [
1, 2,
];
// good
const arr = [[0, 1], [2, 3], [4, 5]];
const objectInArray = [
{
id: 1,
},
{
id: 2,
},
];
const numberInArray = [
1,
2,
];
// 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}`;
}
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
// bad
const name = "Capt. Janeway";
// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';
// bad
const errorMessage = 'This is a super long error that was thrown because \
of Batman. When you stop to think about how Batman had anything to do \
with this, you would get nowhere \
fast.';
// bad
const errorMessage = 'This is a super long error that was thrown because ' +
'of Batman. When you stop to think about how Batman had anything to do ' +
'with this, you would get nowhere fast.';
// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// bad
function sayHi(name) {
return `How are you, ${ name }?`;
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
// bad
function foo() {
// ...
}
// bad
const foo = function () {
// ...
};
// good
// 변수 참조 호출과 구분되는 이름
const short = function longUniqueMoreDescriptiveLexicalFoo() {
// ...
};
매개변수 이름으로 arguments 사용 금지
- 함수 스코프에 전해지는 arguments 객체의 참조를 덮어써 버린다.
예)
// bad
function foo(name, options, arguments) {
// ...
}
// good
function foo(name, options, args) {
// ...
}
```
arguments 대신 나머지 문법(rest syntax) ...
사용
- 예)
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
```
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};
// bad
function f1(obj) {
obj.key = 1;
}
// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}
매개변수를 재할당하는 것은 예측할 수 없는 결과를 불러 일으킨다.
// bad
function f1(a) {
a = 1;
// ...
}
function f2(a) {
if (!a) { a = 1; }
// ...
}
// good
function f3(a) {
const b = a || 1;
// ...
}
function f4(a = 1) {
// ...
}
...
을 사용하자// bad
const x = [1, 2, 3, 4, 5];
console.log.apply(console, x);
// good
const x = [1, 2, 3, 4, 5];
console.log(...x);
// bad
new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));
// good
new Date(...[2016, 8, 5]);
// bad
function foo(bar,
baz,
quux) {
// ...
}
// good
function foo(
bar,
baz,
quux,
) {
// ...
}
// bad
console.log(foo,
bar,
baz);
// good
console.log(
foo,
bar,
baz,
);
// bad
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// bad
[1, 2, 3].map(x => x * x);
// good
[1, 2, 3].map((x) => x * x);
// bad
[1, 2, 3].map(number => (
`A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
));
// good
[1, 2, 3].map((number) => (
`A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!`
));
// bad
[1, 2, 3].map(x => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
중복된 클래스 멤버를 선언하면 암묵적으로 마지막 멤버가 적용된다.
// bad
class Foo {
bar() { return 1; }
bar() { return 2; }
}
// good
class Foo {
bar() { return 1; }
}
// good
class Foo {
bar() { return 2; }
}
2주 차 코드 리뷰에서 class 안에서는 this를 사용하거나 static을 붙여줘야 한다는 리뷰를 받았는데, 이게 그 말인 것 같다.
// bad
class Foo {
bar() {
console.log('bar');
}
}
// good - this를 사용했습니다
class Foo {
bar() {
console.log(this.bar);
}
}
// good - constructor가 면제됩니다
class Foo {
constructor() {
// ...
}
}
// good - 정적 메소드는 this를 사용하지 않는다고 예상할 수 있습니다
class Foo {
static bar() {
console.log('bar');
}
}
// bad
const AirbnbStyleGuide = require('./AirbnbStyleGuide');
module.exports = AirbnbStyleGuide.es6;
// ok
import AirbnbStyleGuide from './AirbnbStyleGuide';
export default AirbnbStyleGuide.es6;
// best
import { es6 } from './AirbnbStyleGuide';
export default es6;
와일드카드란? 파일을 지정할 때, 구체적인 이름 대신에 여러 파일을 동시에 지정할 목적으로 사용하는 특수 기호. `*' 등
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
// good
import AirbnbStyleGuide from './AirbnbStyleGuide';
// bad
let foo = 3;
export { foo };
// good
const foo = 3;
export { foo };
하나만 export하는 파일의 가독성과 유지보수성이 더 좋기 때문
// bad
export function foo() {}
// good
export default function foo() {}
// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
// good
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';
확장자를 명시하면 import하는 모듈의 세부적 구현을 부적절하게 하드코딩하고, 리팩토링을 막게 됨
// 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';
고급함수는 불변 규칙을 적용한다.
사이드 이펙트에 대해 추측하는 것보다 값을 반환하는 순수 함수를 다루는 것이 더 간단하다.
여기서 나온 forEach와 reduce가 익숙하지 않아 따로 포스팅을 해봤는데, 이 포스팅은 이곳에서 확인할 수 있다! (for 대신 자바스크립트 함수를 사용하라는 Airbnb 자바스크립트 컨벤션에 부정적인 의견도 적어두었다.)
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);
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
속성에 접근할 때 .
과 []
로 접근하는 방법이 있다는 건 알았지만, 이 둘의 차이점은 처음 알았다.
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
// bad
const binary = Math.pow(2, 10);
// good
const binary = 2 ** 10;
이렇게 하면 쉽게 새로운 변수를 추가할 수 있고, ,를 ;로 바꿔버리는 것에 대해 걱정할 필요가 없다.
// bad
const items = getItems(),
goSportsTeam = true,
dragonball = 'z';
// bad
// (위 코드와 비교해 실수를 짚어보세요)
const items = getItems(),
goSportsTeam = true;
dragonball = 'z';
// good
const items = getItems();
const goSportsTeam = true;
const dragonball = 'z';
// bad
let i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
let i;
const items = getItems();
let dragonball;
const goSportsTeam = true;
let len;
// good
const goSportsTeam = true;
const items = getItems();
let dragonball;
let i;
let length;
// bad
(function example() {
// 자바스크립트는 이것을
// let a = ( b = ( c = 1 ) );
// 로 해석합니다.
// let 키워드는 변수 a에만 적용됩니다.
// 변수 b와 c는 전역 변수가 됩니다.
let a = b = c = 1;
}());
console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1
// good
(function example() {
let a = 1;
let b = a;
let c = a;
}());
console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError
// `const`에도 동일하게 적용됩니다
이건 코드 리뷰 스터디에서 지적받은 부분이다. ++, -- 대신 += 1, -= 1 등을 사용하자
// 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;
최근에 이중 삼항연산자를 접하고 해석하는데 오래걸렸는데, 하나만 쓰기를 권항한다는 것을 새롭게 알게되었다.
// bad
const foo = maybe1 > maybe2
? "bar"
: value1 > value2 ? "baz" : null;
// split into 2 separated ternary expressions
const maybeNull = value1 > value2 ? 'baz' : null;
// better
const foo = maybe1 > maybe2
? 'bar'
: maybeNull;
// best
const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
// bad
const foo = a ? a : b;
const bar = c ? true : false;
const baz = c ? false : true;
// good
const foo = a || b;
const bar = !!c;
const baz = !c;
// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;
// bad
const bar = a ** b - 5 % d;
// bad
// (a || b) && c 으로 혼동할 수 있습니다.
if (a || b && c) {
return d;
}
// good
const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
// good
const bar = (a ** b) - (5 % d);
// good
if (a || (b && c)) {
return d;
}
// good
const bar = a + b / c * d;
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
이또한 코드 리뷰에서 지적받은 부분이다. 불필요한 else문을 사용하지 말자.
// bad
function foo() {
if (x) {
return x;
} else {
return y;
}
}
// bad
function cats() {
if (x) {
return x;
} else if (y) {
return y;
}
}
// bad
function dogs() {
if (x) {
return x;
} else {
if (y) {
return y;
}
}
}
// good
function foo() {
if (x) {
return x;
}
return y;
}
// good
function cats() {
if (x) {
return x;
}
if (y) {
return y;
}
}
// good
function dogs(x) {
if (x) {
if (z) {
return y;
}
} else {
return z;
}
}
// bad
if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
thing1();
}
// bad
if (foo === 123 &&
bar === 'abc') {
thing1();
}
// bad
if (foo === 123
&& bar === 'abc') {
thing1();
}
// bad
if (
foo === 123 &&
bar === 'abc'
) {
thing1();
}
// good
if (
foo === 123
&& bar === 'abc'
) {
thing1();
}
// good
if (
(foo === 123 || bar === 'abc')
&& doesItLookGoodWhenItBecomesThatLong()
&& isThisReallyHappening()
) {
thing1();
}
// good
if (foo === 123 && bar === 'abc') {
thing1();
}
주석 전에 빈 행을 넣어주는지 몰랐다!
// bad
const active = true; // is current tab
// good
// is current tab
const active = true;
// bad
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this.type || 'no type';
return type;
}
// good
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
const type = this.type || 'no type';
return type;
}
// also good
function getType() {
// set the default type to 'no type'
const type = this.type || 'no type';
return type;
}
// bad
//is current tab
const active = true;
// good
// is current tab
const active = true;
// bad
/**
*make()는 전달된 태그명을 기반으로
*새로운 요소를 반환한다.
*/
function make(tag) {
// ...
return element;
}
// good
/**
* make()는 전달된 태그명을 기반으로
* 새로운 요소를 반환한다.
*/
function make(tag) {
// ...
return element;
}
// FIXME:
class Calculator extends Abacus {
constructor() {
super();
// FIXME: 전역 변수를 사용해서는 안 됨
total = 0;
}
}
// TODO:
class Calculator extends Abacus {
constructor() {
super();
// TODO: total은 옵션 파라메터로 설정해야함
this.total = 0;
}
}
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}
// bad
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog',
});
// good
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog',
});
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}
// bad
function fight () {
console.log ('Swooosh!');
}
// good
function fight() {
console.log('Swooosh!');
}
// bad
const x=y+5;
// good
const x = y + 5;
// bad
if (foo) {
return bar;
}
return baz;
// good
if (foo) {
return bar;
}
return baz;
// bad
const obj = {
foo() {
},
bar() {
},
};
return obj;
// good
const obj = {
foo() {
},
bar() {
},
};
return obj;
// bad
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
// good
const arr = [
function foo() {
},
function bar() {
},
];
return arr;
// bad
function bar() {
console.log(foo);
}
// bad
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
// bad
class Foo {
constructor(bar) {
this.bar = bar;
}
}
// good
function bar() {
console.log(foo);
}
// good
if (baz) {
console.log(qux);
} else {
console.log(foo);
}
대괄호 안쪽에 공백을 둔 코드를 많이 봤는데, 그렇게 하지 말아야겠다.
// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);
// good
const foo = [1, 2, 3];
console.log(foo[0]);
대괄호와 반대로 중괄호 안에는 공백을 두어야 한다!
// bad
const foo = {clark: 'kent'};
// good
const foo = { clark: 'kent' };
// bad
var obj = { foo : 42 };
var obj2 = { foo:42 };
// good
var obj = { foo: 42 };
// bad - 여러 개의 빈 줄
var x = 1;
var y = 2;
// bad - 파일 끝에 2개 이상의 빈 줄
var x = 1;
var y = 2;
// bad - 파일 시작에 1개 이상의 빈 줄
var x = 1;
var y = 2;
// good
var x = 1;
var y = 2;
// bad
const story = [
once
, upon
, aTime
];
// good
const story = [
once,
upon,
aTime,
];
// bad
const hero = {
firstName: 'Ada'
, lastName: 'Lovelace'
, birthYear: 1815
, superPower: 'computers'
};
// good
const hero = {
firstName: 'Ada',
lastName: 'Lovelace',
birthYear: 1815,
superPower: 'computers',
};
// bad - 마지막에 쉼표가 없는 경우 git diff
const hero = {
firstName: 'Florence',
- lastName: 'Nightingale'
+ lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing']
};
// good - 마지막에 쉼표가 있는 경우 git diff
const hero = {
firstName: 'Florence',
lastName: 'Nightingale',
+ inventorOf: ['coxcomb chart', 'modern nursing'],
};
// good ("나머지" 요소 뒤에 쉼표가 없다는 점에 주의하세요)
function createHero(
firstName,
lastName,
inventorOf,
...heroArgs
) {
// does nothing
}
구문의 끝을 명시하고, 빠뜨린 세미콜론을 잡도록 linter를 설정하자.
const inputValue = '4';
// bad
const val = new Number(inputValue);
// bad
const val = +inputValue;
// bad
const val = inputValue >> 0;
// bad
const val = parseInt(inputValue);
// good
const val = Number(inputValue);
// good
const val = parseInt(inputValue, 10);
// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}
// good
const thisIsMyObject = {};
function thisIsMyFunction() {}
// bad
function user(options) {
this.name = options.name;
}
const bad = new user({
name: 'nope',
});
// good
class User {
constructor(options) {
this.name = options.name;
}
}
const good = new User({
name: 'yup',
});
// 파일 1 내용
class CheckBox {
// ...
}
export default CheckBox;
// 파일 2 내용
export default function fortyTwo() { return 42; }
// 파일 3 내용
export default function insideDirectory() {}
// 다른 파일
// bad
import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export
// bad
import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
import forty_two from './forty_two'; // snake_case import/filename, camelCase export
import inside_directory from './inside_directory'; // snake_case import, camelCase export
import index from './inside_directory/index'; // requiring the index file explicitly
import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly
// good
import CheckBox from './CheckBox'; // PascalCase export/import/filename
import fortyTwo from './fortyTwo'; // camelCase export/import/filename
import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
// ^ supports both insideDirectory.js and insideDirectory/index.js
// 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' // 속성인 key는 대문자가 아님에 유의
};
이벤트로 payload의 값을 넘길 경우 raw값 보다는 해시값을 넘겨주자.
// bad
$(this).trigger('listingUpdated', listing.id);
// ...
$(this).on('listingUpdated', (e, listingID) => {
// do something with listingID
});
// good
$(this).trigger('listingUpdated', { listingID: listing.id });
// ...
$(this).on('listingUpdated', (e, data) => {
// do something with data.listingID
});
Airbnb 자바스크립트 컨벤션을 쭉 읽어보았다. 시간이 오래 걸렸지만, 몰랐던 부분을 많이 알게되어 보람찬 시간이었다! 앞으로는 이를 지켜면서 코딩해야겠다😉