Skip to content

๐Ÿ› JavaScript๋ฅผ ์œ„ํ•œ ํด๋ฆฐ ์ฝ”๋“œ

License

Notifications You must be signed in to change notification settings

dw3624/clean-code-javascript-kr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

clean-code-javascript

clean code Javascript (ํ•œ๊ธ€)

์ด ๊ธ€์€ Clean-Code-JavaScript๋ฅผ ๋ฒˆ์—ญํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

Table of Contents

  1. Introduction
  2. Variables
  3. Functions
  4. Objects and Data Structures
  5. Classes
  6. SOLID
  7. Testing
  8. Concurrency
  9. Error Handling
  10. Formatting
  11. Comments
  12. Translation

Introduction

Humorous image of software quality estimation as a count of how many expletives you shout when reading code

Robert C. Martin์˜ ์ €์„œ Clean Code์—์„œ ์†Œํ”„ํŠธ์›จ์–ด ์—”์ง€๋‹ˆ์–ด๋ง ์›๋ฆฌ๋ฅผ Javascript์— ์ ์šฉ์‹œํ‚จ ๊ทธ๋ฆผ์ž…๋‹ˆ๋‹ค. ์ด ๊ธ€์€ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ๊ฐ€๋…์„ฑ์ด ์ข‹๊ณ , ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ฆฌํŒฉํ† ๋ง์ด ๊ฐ€๋Šฅํ•œ JavaScript ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค.

์—ฌ๊ธฐ ๊ธฐ์žฌ๋œ ๋ชจ๋“  ์›๋ฆฌ๋ฅผ ์—„๊ฒฉํ•˜๊ฒŒ ๋”ฐ๋ผ์•ผ ํ•˜๋Š”๊ฑด ์•„๋‹™๋‹ˆ๋‹ค. ๋ณดํŽธ์ ์œผ๋กœ ํ•ฉ์˜๋œ ๊ทœ์น™์€ ๋” ์ ์Šต๋‹ˆ๋‹ค. ์–ด๋””๊นŒ์ง€๋‚˜ ๊ฐ€์ด๋“œ์— ๋ถˆ๊ณผํ•˜์ง€๋งŒ, _Clean Code_์˜ ์ €์ž๋“ค์ด ์˜ค๋žœ ์‹œ๊ฐ„๋™์•ˆ ์Œ“์•„์˜จ ๊ฒฝํ—˜์„ ์ •๋ฆฌํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

์†Œํ”„ํŠธ์›จ์–ด ์—”์ง€๋‹ˆ์–ด๋ง ๊ธฐ์ˆ ์€ ์ด์ œ ๋ง‰ 50์‚ด์ด ๋„˜์—ˆ๊ณ , ์šฐ๋ฆฌ๋Š” ์•„์ง ๋งŽ์€ ๊ฒƒ์„ ๋ฐฐ์›Œ๊ฐ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์†Œํ”„ํŠธ์›จ์–ด ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์•„ํ‚คํ…์ฒ˜ ์ž์ฒด๋งŒํผ์ด๋‚˜ ์˜ค๋ž˜๋๋‹ค๋ฉด, ์šฐ๋ฆฌ๋Š” ์•„๋งˆ ๋” ์—„๊ฒฉํ•œ ๊ทœ์น™์„ ๋”ฐ๋ผ์•ผ ํ–ˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ง€๊ธˆ์œผ๋กœ์„œ๋Š”, ๋ณธ ๊ฐ€์ด๋“œ๋ผ์ธ์ด ์—ฌ๋Ÿฌ๋ถ„๊ณผ ์—ฌ๋Ÿฌ๋ถ„์˜ ํŒ€์ด ์ž‘์„ฑํ•œ Javascript ์ฝ”๋“œ ํ’ˆ์งˆ์„ ํ‰๊ฐ€ํ•˜๊ธฐ ์œ„ํ•œ ํ•˜๋‚˜์˜ ๊ธฐ์ค€์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ•œ ๊ฐ€์ง€ ๋”: ์—ฌ๊ธฐ์— ์žˆ๋Š” ๋‚ด์šฉ์„ ์•ˆ๋‹ค๊ณ  ๋ฐ”๋กœ ๋” ๋‚˜์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ๋˜ํ•œ ์˜ค๋žซ๋™์•ˆ ๋ณธ ๊ทœ์น™๋“ค์„ ๋”ฐ๋ฅธ๋‹ค๊ณ  ํ•ด์„œ ์‹ค์ˆ˜๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ๋„ ์•„๋‹™๋‹ˆ๋‹ค. ๋ชจ๋“  ์ฝ”๋“œ ์กฐ๊ฐ์€ ์ –์€ ์ ํ† ๋ฅผ ์›ํ•˜๋Š” ๋ชจ์–‘์œผ๋กœ ๋งŒ๋“ค์–ด๋‚˜๊ฐ€๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ดˆ์•ˆ์œผ๋กœ์จ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ๋™๋ฃŒ๋“ค๊ณผ ๋ฆฌ๋ทฐ๋ฅผ ํ•˜๋ฉด์„œ ๋ถˆ์™„์ „ํ•œ ๋ถ€๋ถ„์„ ๊นŽ์•„๋ƒ…๋‹ˆ๋‹ค. ๊ฐœ์„ ์ด ํ•„์š”ํ•œ ์ดˆ์•ˆ ๋‹จ๊ณ„์—์„œ ๋„ˆ๋ฌด ์ž์‹ ์„ ๊ดด๋กญํžˆ์ง€ ๋งˆ์„ธ์š”. ๋Œ€์‹  ์ฝ”๋“œ๋ฅผ ๊ดด๋กญํž™์‹œ๋‹ค!

Variables

Use meaningful and pronounceable variable names

์˜๋ฏธ๊ฐ€ ์žˆ๊ณ  ๋ฐœ์Œํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜๋ช…์„ ์‚ฌ์šฉํ•  ๊ฒƒ

Bad:

const yyyymmdstr = moment().format("YYYY/MM/DD");

Good:

const currentDate = moment().format("YYYY/MM/DD");

โฌ† back to top

Use the same vocabulary for the same type of variable

๊ฐ™์€ ์ข…๋ฅ˜์˜ ๋ณ€์ˆ˜์—๋Š” ๊ฐ™์€ ๋‹จ์–ด๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ

Bad:

getUserInfo();
getClientData();
getCustomerRecord();

Good:

getUser();

โฌ† back to top

Use searchable names

๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ด๋ฆ„์„ ์‚ฌ์šฉํ•  ๊ฒƒ

์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ๋ณด๋‹ค ์ฝ๋Š” ์ผ์ด ํ›จ์”ฌ ๋งŽ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ฝ์„ ์ˆ˜ ์žˆ๊ณ  ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ ์ดํ•ด์— ๋„์›€์ด ์•ˆ ๋˜๋Š” ๋ณ€์ˆ˜๋ช…์€ ์ฝ๋Š”์ด๋ฅผ ๊ดด๋กญํžˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜๋ช…์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. buddy.js์™€ ESLint์™€ ๊ฐ™์€ ๋„๊ตฌ๋“ค์€ ์ด๋ฆ„์ด ์—†๋Š” ๋ณ€์ˆ˜ ์‹๋ณ„์„ ์ˆ˜์›”ํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

Bad:

// What the heck is 86400000 for?
setTimeout(blastOff, 86400000);

Good:

// Declare them as capitalized named constants.
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;

setTimeout(blastOff, MILLISECONDS_PER_DAY);

โฌ† back to top

Use explanatory variables

์„ค๋ช…์ ์ธ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ

Bad:

const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
  address.match(cityZipCodeRegex)[1],
  address.match(cityZipCodeRegex)[2]
);

Good:

const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/;
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);

โฌ† back to top

Avoid Mental Mapping

๋ฉ˜ํƒˆ๋งตํ•‘์„ ํ”ผํ•  ๊ฒƒ

์•”๋ฌต์ ์ธ ๊ฒƒ๋ณด๋‹ค ๋ช…์‹œ์ ์ธ ๊ฒƒ์ด ๋‚ซ์Šต๋‹ˆ๋‹ค.

๋ฉ˜ํƒˆ๋งตํ•‘(์‹ฌ์ƒ ์ง€๋„): ๊ฐœ์ธ์˜ โ€˜๊ณต๊ฐ„์— ๋Œ€ํ•œ ์ธ์ง€ ์ƒํƒœโ€™๋ฅผ ์ž์œ ๋กญ๊ฒŒ ํ‘œํ˜„ํ•œ ์ง€๋„. ์‹ค์ œ ์ธก๋Ÿ‰์— ์˜ํ•ด ์ œ์ž‘๋œ ์ง€๋„์™€๋Š” ๋‹ฌ๋ฆฌ ๊ฐ์ž์˜ ๊ฒฝํ—˜๊ณผ ํŒ๋‹จ, ์ง€์  ์ˆ˜์ค€ ๋“ฑ์— ์˜ํ•ด ๊ฒฐ์ •๋œ โ€˜์ง€์—ญ์˜ ์ด๋ฏธ์ง€โ€™.

Bad:

const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(l => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // Wait, what is `l` for again?
  dispatch(l);
});

Good:

const locations = ["Austin", "New York", "San Francisco"];
locations.forEach(location => {
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch(location);
});

โฌ† back to top

Don't add unneeded context

๋ถˆํ•„์š”ํ•œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์„ ๊ฒƒ

๋งŒ์•ฝ class/object๋ช…์ด ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ๋ณ€์ˆ˜๋ช…์œผ๋กœ ์ด๋ฅผ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Bad:

const Car = {
  carMake: "Honda",
  carModel: "Accord",
  carColor: "Blue"
};

function paintCar(car, color) {
  car.carColor = color;
}

Good:

const Car = {
  make: "Honda",
  model: "Accord",
  color: "Blue"
};

function paintCar(car, color) {
  car.color = color;
}

โฌ† back to top

Use default parameters instead of short circuiting or conditionals

๋‹จ์ถ•ํ‰๊ฐ€, ์กฐ๊ฑด ๋Œ€์‹  ๊ธฐ๋ณธ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’์„ ์‚ฌ์šฉํ•  ๊ฒƒ

๊ธฐ๋ณธ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’์€ ๋Œ€์ฒด๋กœ ๋‹จ์ถ•ํ‰๊ฐ€๋ณด๋‹ค ๊ฐ„๊ฒฐํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ, ํ•จ์ˆ˜๋Š” undefined ์ธ์ˆ˜์— ๋Œ€ํ•ด์„œ๋งŒ ๊ธฐ๋ณธ๊ฐ’์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ธฐํƒ€ '', "", false, null, 0, NaN ๋“ฑ์˜ "falsy" ๊ฐ’์€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋Œ€์ฒด๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Bad:

function createMicrobrewery(name) {
  const breweryName = name || "Hipster Brew Co.";
  // ...
}

Good:

function createMicrobrewery(name = "Hipster Brew Co.") {
  // ...
}

โฌ† back to top

Functions

Function arguments (2 or fewer ideally)

ํ•จ์ˆ˜์˜ ์ธ์ˆ˜ (2๊ฐœ ์ดํ•˜๊ฐ€ ๋ฐ”๋žŒ์ง)

ํ•จ์ˆ˜ ์ธ์ˆ˜์˜ ๊ฐœ์ˆ˜๋ฅผ ์ œํ•œํ•˜๋Š” ๊ฒƒ์€ ํ•จ์ˆ˜ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ธ์ˆ˜๊ฐ€ 3๊ฐœ ์ด์ƒ์ด ๋˜๋ฉด, ์„œ๋กœ ๋‹ค๋ฅธ ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ ์ˆ˜๋งŽ์€ ๊ฒฝ์šฐ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•ด, ์กฐํ•ฉ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์ง‘๋‹ˆ๋‹ค.

1๊ฐœ ํ˜น์€ 2๊ฐœ๊ฐ€ ์ ๋‹นํ•˜๋ฉฐ, 3๊ฐœ ์ด์ƒ์€ ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ํ”ผํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ด์ƒ์ธ ๊ฒฝ์šฐ์—๋Š” ํ†ตํ•ฉํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ธ์ˆ˜๊ฐ€ 2๊ฐœ ์ด์ƒ์ด๋ผ๋ฉด, ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€ ์—ญํ• ์„ ํ•˜๋ ค ํ•˜๋Š”๊ฑธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ, ๋Œ€๋ถ€๋ถ„ ์ƒ์œ„ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ธ์ˆ˜๋กœ ํ•˜๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

Javascript๋Š” class ์ƒ์šฉ๊ตฌ ํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ๋งŽ์ด ์—†๋”๋ผ๋„ object๋ฅผ ์žฌ๋นจ๋ฆฌ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ธ์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ํ•„์š”ํ•œ ๊ฒฝ์šฐ object๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ค ์†์„ฑ์„ ๊ธฐ๋Œ€ํ•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ES2015/ES6์˜ ๋ถ„ํ• ๋Œ€์ž… ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—” ๋ช‡ ๊ฐ€์ง€ ์žฅ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ํ•จ์ˆ˜ ์ •์˜๋ฅผ ๋ดค์„ ๋•Œ, ์–ด๋–ค ์†์„ฑ์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋Š”์ง€ ๋ช…ํ™•ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ด๋ฆ„์ด ์žˆ๋Š” ์ธ์ˆ˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  3. ๋ถ„ํ• ๋Œ€์ž…์€ ํ•จ์ˆ˜์— ๋„˜๊ฒจ์ง„ ์ธ์ˆ˜์˜ ์ง€์ •๋œ ์›์‹œ๊ฐ’์„ ๋ณต์ œํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ถ€์ž‘์šฉ์„ ๋ง‰์•„์ค๋‹ˆ๋‹ค. ์ฃผ์˜: ์ธ์ˆ˜ object์—์„œ ๋ถ„ํ• ๋Œ€์ž…๋œ object์™€ array๋Š” ๋ณต์ œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  4. Lint ํˆด์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š์€ ์†์„ฑ์— ๋Œ€ํ•ด ๊ฒฝ๊ณ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ ๋ถ„ํ• ๋Œ€์ž…์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Bad:

function createMenu(title, body, buttonText, cancellable) {
  // ...
}

createMenu("Foo", "Bar", "Baz", true);

Good:

function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});

โฌ† back to top

Functions should do one thing

ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋Š” ํ•œ ๊ฐ€์ง€ ์—ญํ• ๋งŒ

์ด๋Š” ์†Œํ”„ํŠธ์›จ์–ด ์—”์ง€๋‹ˆ์–ด๋ง์— ์žˆ์–ด ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ทœ์น™์ž…๋‹ˆ๋‹ค. ํ•จ์ˆ˜๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ด์ƒ์˜ ์—ญํ• ์„ ํ•œ๋‹ค๋ฉด, ์ด๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ ํ…Œ์ŠคํŠธํ•˜๊ฑฐ๋‚˜ ์ถ”๋ฆฌํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๊ฐ€ ํ•œ ๊ฐ€์ง€ ์—ญํ• ๋งŒ ํ•˜๋„๋ก ํ•˜๋ฉด, ๋ฆฌํŒฉํ† ๋ง์ด ์ˆ˜์›”ํ•ด์ง€๊ณ  ์ฝ”๋“œ ๊ฐ€๋…์„ฑ๋„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ๋‹ค๋ฅธ ๊ฐ€์ด๋“œ๋ฅผ ์ง€ํ‚ค์ง€ ์•Š๋”๋ผ๋„, ๋ณธ ๊ฐ€์ด๋“œ๋งŒ ์ง€ํ‚จ๋‹ค๋ฉด, ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋ณด๋‹ค ํ•œ๋ฐœ ๋” ์•ž์„œ์žˆ๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

Good:

function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

โฌ† back to top

Function names should say what they do

์—ญํ• ์„ ์•Œ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜ ์ด๋ฆ„์„ ์ง€์„ ๊ฒƒ

Bad:

function addToDate(date, month) {
  // ...
}

const date = new Date();

// It's hard to tell from the function name what is added
// ํ•จ์ˆ˜ ์ด๋ฆ„๋งŒ ๋ณด๋ฉด ๋ฌด์—‡์„ ์ถ”๊ฐ€ํ•˜๋Š”์ง€ ์•Œ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
addToDate(date, 1);

Good:

function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);

โฌ† back to top

Functions should only be one level of abstraction

ํ•จ์ˆ˜๊ฐ€ ํ•œ ๊ฐ€์ง€๋งŒ ์ถ”์ƒํ™”ํ•˜๋„๋ก ํ•  ๊ฒƒ

ํ•จ์ˆ˜๊ฐ€ ๋‘ ๊ฐ€์ง€ ์ด์ƒ์˜ ์ถ”์ƒ์„ ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€ ์ผ์„ ํ•˜๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋ฅผ ๋ถ„๋ฆฌํ•ด ์žฌ์‚ฌ์šฉ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

Bad:

function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      // ...
    });
  });

  const ast = [];
  tokens.forEach(token => {
    // lex...
  });

  ast.forEach(node => {
    // parse...
  });
}

Good:

function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => {
    // parse...
  });
}

function tokenize(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      tokens.push(/* ... */);
    });
  });

  return tokens;
}

function parse(tokens) {
  const syntaxTree = [];
  tokens.forEach(token => {
    syntaxTree.push(/* ... */);
  });

  return syntaxTree;
}

โฌ† back to top

Remove duplicate code

์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์—†์•จ ๊ฒƒ

์ตœ์„ ์„ ๋‹คํ•ด ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์—†์•ฑ๋‹ˆ๋‹ค. ์ค‘๋ณต ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฑด ๋กœ์ง์„ ๋ฐ”๊ฟ€ ๋•Œ ์—ฌ๋Ÿฌ ๊ตฐ๋ฐ๋ฅผ ๊ณ ์ณ์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ์˜๋ฏธํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ”๋žŒ์งํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„์ด ์‹๋‹น์„ ์šด์˜ํ•˜๊ณ  ์žˆ๊ณ  ํ† ๋งˆํ† ๋‚˜ ์–‘ํŒŒ, ๋งˆ๋Š˜, ์กฐ๋ฏธ๋ฃŒ ๋“ฑ์˜ ์žฌ๊ณ ๋ฅผ ๋ชจ๋‘ ์ถ”์ ํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ด ๋ด…์‹œ๋‹ค. ๋งŒ์•ฝ ์žฌ๊ณ  ๋ชฉ๋ก์ด ์—ฌ๋Ÿฌ๊ฐœ ์žˆ๋‹ค๋ฉด ํ† ๋งˆํ† ๊ฐ€ ๋“  ์š”๋ฆฌ๋ฅผ ์ œ๊ณตํ•  ๋•Œ๋งˆ๋‹ค ๋ชจ๋“  ๋ชฉ๋ก์„ ๊ฐฑ์‹ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ชฉ๋ก์ด ํ•˜๋‚˜๋งŒ ์žˆ๋‹ค๋ฉด ํ•˜๋‚˜๋งŒ ๊ฐฑ์‹ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค!

์ข…์ข… ๊ณตํ†ต์ ์ด ๋งŽ์Œ์—๋„, ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฅธ ๋ถ€๋ถ„๋•Œ๋ฌธ์— ์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜๋Š” ์ผ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ด ์ฐจ์ด๋กœ ์ธํ•ด ๋น„์Šทํ•œ ์—ญํ• ์„ ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ์ƒ๊ธฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค๋Š” ๊ฒƒ์€ ํ•œ ๊ฐ€์ง€ ํ•จ์ˆ˜/๋ชจ๋“ˆ/ํด๋ž˜์Šค๋งŒ์„ ์‚ฌ์šฉํ•ด, ์ด ์ฐจ์ด๋“ค์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ์ถ”์ƒํ™”๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค.

์ข‹์€ ์ถ”์ƒํ™”๋Š” ์ค‘์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Classes ์„น์…˜์—์„œ ์„ค๋ช…ํ•˜๋Š” SOLID ์›๋ฆฌ๋ฅผ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์•ˆ ์ข‹์€ ์ถ”์ƒํ™”๋Š” ์ค‘๋ณต ์ฝ”๋“œ๋ณด๋‹ค ๋” ์•ˆ ์ข‹์„ ์ˆ˜ ์žˆ๊ธฐ์— ์กฐ์‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค! ์‰ฝ์ง€๋Š” ์•Š๊ฒ ์ง€๋งŒ ๋งŒ์•ฝ ์ข‹์€ ์ถ”์ƒํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ๋„์ „ํ•ด ๋ด…์‹œ๋‹ค! ์Šค์Šค๋กœ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ•œ ๊ฐ€์ง€๋ฅผ ๋ฐ”๊ฟ€ ๋•Œ ์—ฌ๋Ÿฌ ๊ตฐ๋ฐ๋ฅผ ๋ฐ”๊ฟ”์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Bad:

function showDeveloperList(developers) {
  developers.forEach(developer => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    const data = {
      expectedSalary,
      experience,
      githubLink
    };

    render(data);
  });
}

function showManagerList(managers) {
  managers.forEach(manager => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    render(data);
  });
}

Good:

function showEmployeeList(employees) {
  employees.forEach(employee => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();

    const data = {
      expectedSalary,
      experience
    };

    switch (employee.type) {
      case "manager":
        data.portfolio = employee.getMBAProjects();
        break;
      case "developer":
        data.githubLink = employee.getGithubLink();
        break;
    }

    render(data);
  });
}

โฌ† back to top

Set default objects with Object.assign

Object.assign์œผ๋กœ ๊ธฐ๋ณธ object๋ฅผ ์„ค์ •ํ•  ๊ฒƒ

Bad:

const menuConfig = {
  title: null,
  body: "Bar",
  buttonText: null,
  cancellable: true
};

function createMenu(config) {
  config.title = config.title || "Foo";
  config.body = config.body || "Bar";
  config.buttonText = config.buttonText || "Baz";
  config.cancellable =
    config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);

Good:

const menuConfig = {
  title: "Order",
  // User did not include 'body' key
  buttonText: "Send",
  cancellable: true
};

function createMenu(config) {
  let finalConfig = Object.assign(
    {
      title: "Foo",
      body: "Bar",
      buttonText: "Baz",
      cancellable: true
    },
    config
  );
  return finalConfig
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

โฌ† back to top

Don't use flags as function parameters

flag๋ฅผ ํ•จ์ˆ˜ ์ธ์ž์ฒ˜๋Ÿผ ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ

flag๋Š” ํ•จ์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ ์—ญํ• ์„ ํ•œ๋‹ค๊ณ  ์œ ์ €์—๊ฒŒ ์ „ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋Š” ํ•œ ๊ฐ€์ง€ ์—ญํ• ๋งŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ boolean์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์ฝ”๋“œ ๊ฒฝ๋กœ๋ฅผ ๊ฒฝ์œ ํ•  ๊ฒฝ์šฐ, ํ•จ์ˆ˜๋ฅผ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

Bad:

function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

Good:

function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

โฌ† back to top

Avoid Side Effects (part 1)

๋ถ€์ž‘์šฉ์„ ํ”ผํ•  ๊ฒƒ (part 1)

ํ•จ์ˆ˜๋Š”, ๊ฐ’์„ ๋ฐ›์•„ ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ ์™ธ์˜ ์ผ์„ ํ•  ๋•Œ, ๋ถ€์ž‘์šฉ์„ ์ผ์œผํ‚ต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๋ถ€์ž‘์šฉ์ด๋ž€, ํŒŒ์ผ์— ๋‚ด์šฉ์„ ์ž‘์„ฑํ•œ๋‹ค๋˜์ง€, ๊ธ€๋กœ๋ฒŒ ๋ณ€์ˆ˜๋ฅผ ๋ฎ์–ด์“ด๋‹ค๋˜์ง€, ํ˜น์€ ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ณ„์ขŒ์—์„œ ๋ชจ๋ฅด๋Š” ์‚ฌ๋žŒ์—๊ฒŒ ๋ง๋ชป ์†ก๊ธˆํ•œ๋‹ค๋˜์ง€ ํ•˜๋Š” ๊ฒƒ๋“ค์ด๋ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์•ž์„  ์˜ˆ์‹œ์ฒ˜๋Ÿผ ํŒŒ์ผ์— ๋‚ด์šฉ์„ ์ž‘์„ฑํ•ด์•ผ ํ•  ๋•Œ ๋“ฑ, ์ข…์ข… ๋ถ€์กฑ์šฉ์ด ์žˆ๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ํ•„์š”ํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ ์—ฌ๋Ÿฌ๋ถ„์ด ํ•˜๊ณ  ์‹ถ์€ ์ผ์€ ์–ด๋””์„œ ์ด ๋™์ž‘์„ ํ• ์ง€ ์ง‘์ค‘์‹œํ‚ค๋Š” ์ผ์ž…๋‹ˆ๋‹ค. ํŒŒ์ผ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜๋Š” ํ•จ์ˆ˜์™€ class๋ฅผ ์—ฌ๋Ÿฌ๊ฐœ ๋งŒ๋“ค์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋™์ž‘์„ ํ•˜๋Š” ์„œ๋น„์Šค๋Š” ์˜ค์ง ํ•˜๋‚˜๋งŒ, ๋‹จ ํ•˜๋‚˜๋งŒ ๋งŒ๋“ค๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ฌด ๊ตฌ์กฐ ์—†์ด object ๊ฐ„ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜, ๋ญ๋“  ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€๋ณ€ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ํ˜น์€ ๋ถ€์ž‘์šฉ์ด ์—ฌ๋Ÿฌ๊ณณ์—์„œ ์ผ์–ด๋‚˜๋„๋ก ํ•œ๋‹ค๊ฑฐ๋‚˜ ํ•˜๋Š” ๋“ฑ์˜ ํ”ํ•œ ์‹ค์ˆ˜๋ฅผ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ด๋ฅผ ์ž˜ ์ง€ํ‚จ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ๋‹ค๋ฅธ ๋งŽ์€ ํ”„๋กœ๊ทธ๋ž˜๋จธ๋“ค๋ณด๋‹ค ํ–‰๋ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";

function splitIntoFirstAndLastName() {
  name = name.split(" ");
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

Good:

function splitIntoFirstAndLastName(name) {
  return name.split(" ");
}

const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

โฌ† back to top

Avoid Side Effects (part 2)

๋ถ€์ž‘์šฉ์„ ํ”ผํ•  ๊ฒƒ (part 2)

Javascript์—๋Š” ๋ถˆ๋ณ€์„ฑ์„ ์ง€๋‹Œ ๊ฐ’๊ณผ ๊ฐ€๋ณ€์„ฑ์„ ์ง€๋‹Œ ๊ฐ’์ด ์žˆ์Šต๋‹ˆ๋‹ค. object์™€ array๋Š” ๊ฐ€๋ณ€์„ฑ์„ ์ง€๋‹Œ ๊ฐ’๋“ค๋กœ, ์ด๋“ค์„ ํ•จ์ˆ˜ ์ธ์ˆ˜๋กœ ๋„˜๊ธธ ๋•Œ๋Š” ์ฃผ์˜๊นŠ๊ฒŒ ๋‹ค๋ค„์•ผ ํ•ฉ๋‹ˆ๋‹ค. Javascript ํ•จ์ˆ˜๋Š” object์˜ ํŠน์„ฑ์„ ๋ฐ”๊พธ๊ฑฐ๋‚˜ array์˜ ๋‚ด์šฉ์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋Š” ๋ฒ„๊ทธ๋ฅผ ์œ ๋ฐœํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

์‡ผํ•‘์นดํŠธ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” array๋ฅผ ์ธ์ˆ˜๋กœ ๋ฐ›๋Š” ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ •ํ•ด๋ด…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์–ด๋–ค ํ•จ์ˆ˜๊ฐ€ ์ด ์‡ผํ•‘์นดํŠธ array๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด - ์˜ˆ๋ฅผ ๋“ค์–ด ๊ตฌ๋งคํ•  ๋ฌผ๊ฑด์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋˜์ง€ - ๊ฐ™์€ cart array๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜๋“ค๋„ ๊ทธ ์˜ํ–ฅ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์ด๊ฑด ๊ต‰์žฅํ•ด ๋ณด์ด์ง€๋งŒ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์•ˆ ์ข‹์€ ๊ฒฝ์šฐ์„ ์ƒ์ƒํ•ด ๋ด…์‹œ๋‹ค:

ํ•œ ์œ ์ €๊ฐ€ "๊ตฌ๋งค" ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด, ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๋ฐœ์ƒ์‹œํ‚ค๊ณ  cart array๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋Š” purchase ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ, purchase ํ•จ์ˆ˜๋Š” ์š”์ฒญ์„ ๋‹ค์‹œ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด๋•Œ ๋งŒ์•ฝ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ์œ ์ €๊ฐ€ ์‹ค์ˆ˜๋กœ "์นดํŠธ์— ์ถ”๊ฐ€" ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์›ํ•˜์ง€ ์•Š๋Š” ๋ฌผ๊ฑด์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค๋ฉด ๋ฌด์Šจ ์ผ์ด ์ผ์–ด๋‚ ๊นŒ์š”? ์ด ๋•Œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์ด ์‹œ์ž‘๋˜๋ฉด, cart array๊ฐ€ ๋ณ€๊ฒฝ๋๊ธฐ ๋•Œ๋ฌธ์—, ๊ตฌ๋งค ํ•จ์ˆ˜๋Š” ์ž˜๋ชป ์ถ”๊ฐ€ํ•œ ๋ฌผ๊ฑด๋„ ๊ฐ™์ด ์†ก์‹ ํ•ฉ๋‹ˆ๋‹ค.

์ข‹์€ ํ•ด๋ฒ•์œผ๋กœ๋Š” addItemToCart ํ•จ์ˆ˜๋กœ ํ•˜์—ฌ๊ธˆ ํ•ญ์ƒ cart๋ฅผ ๋ณต์‚ฌํ•˜๋„๋ก ํ•˜๊ณ , ์ด๋ฆ„ ํŽธ์ง‘ ๋ฐ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ธฐ์กด cart๋ฅผ ์ฐธ์กฐํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋ณ€๊ฒฝ์— ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ณธ ์ ‘๊ทผ๋ฒ• ๊ด€๋ จ 2๊ฐ€์ง€ ์ฃผ์˜์ :

  1. ์ž…๋ ฅ๋œ object๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” ๋งค์šฐ ๋“œ๋ฌผ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ฆฌํŒฉํ† ๋ง์„ ํ†ตํ•ด ๋ถ€์ž‘์šฉ์„ ๋Œ€๋ถ€๋ถ„ ์—†์•จ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!
  2. ๋ฐฉ๋Œ€ํ•œ object๋ฅผ ๋ณต์‚ฌํ•˜๋Š” ์ผ์€ ํผํฌ๋จผ์Šค์ ์œผ๋กœ ๋ถ€๋‹ด์ด ๋˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋‹คํ–‰ํžˆ๋„ ๋ณธ ๊ธฐ๋ฒ•์— ์žˆ์–ด์„œ๋Š” ์ด๋Š” ํฐ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด, ์ด์™€ ๊ฐ™์€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์  ์ ‘๊ทผ์„ ๋”์šฑ ์‹ ์†ํ•˜๊ฒŒ ํ•ด์ฃผ๊ณ , ์ผ์ผ์ด object์™€ array๋ฅผ ๋ณต์‚ฌํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์ผ ์ˆ˜ ์žˆ๋Š” ํ›Œ๋ฅญํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Bad:

const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

Good:

const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};

โฌ† back to top

Don't write to global functions

๊ธ€๋กœ๋ฒŒ ํ•จ์ˆ˜์— ๊ธฐ์ž…ํ•˜์ง€ ๋ง ๊ฒƒ

๊ธ€๋กœ๋ฒŒ ํ™˜๊ฒฝ์„ ์˜ค์—ผํ•˜๋Š” ๊ฒƒ์€ Javascript์—์„œ๋Š” ๋‚˜์œ ์Šต๊ด€์ž…๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ์ถฉ๋Œ์„ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๊ณ  ์—ฌ๋Ÿฌ๋ถ„์˜ API ์‚ฌ์šฉ์ž๋Š” ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ ์˜ˆ์™ธ์ƒํ™ฉ์„ ๋งŒ๋‚˜๊ธฐ ์ „๊นŒ์ง€ ์ด์— ๋Œ€ํ•ด ์•„๋ฌด๊ฒƒ๋„ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์˜ˆ์‹œ๋ฅผ ํ•˜๋‚˜ ์ƒ๊ฐํ•ด๋ด…์‹œ๋‹ค: ๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด Javascript์˜ ๊ธฐ์กด array ํ•จ์ˆ˜๋ฅผ ํ™•์žฅํ•ด, ๋‘ array ๊ฐ„ ์ฐจ์ด๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” diff ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”? ์ƒˆ ํ•จ์ˆ˜๋ฅผ Array.prototype์— ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ๊ฐ™์€ ์ผ์„ ํ•˜๋Š” ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ์ถฉ๋Œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ diff๋ฅผ array์˜ ์ฒซ๋ฒˆ์งธ ์š”์†Œ์™€ ๋งˆ์ง€๋ง‰ ์ธ์ž์˜ ์ฐจ์ด๋ฅผ ์ฐพ๋Š”๋ฐ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ์ด๊ฒƒ์ด ๊ธ€๋กœ๋ฒŒ Array๋ฅผ ํ™•์žฅํ•˜๊ธฐ๋ณด๋‹ค ES2015/ES6์˜ class๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๋‚˜์€ ์ด์œ ์ž…๋‹ˆ๋‹ค.

Bad:

Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

Good:

class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

โฌ† back to top

Favor functional programming over imperative programming

๋ช…๋ นํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ๋ณด๋‹ค ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์šฐ์„ ํ•  ๊ฒƒ

Javascript๋Š” Haskell๊ณผ ๊ฐ™์€ ํ•จ์ˆ˜ํ˜• ์–ธ์–ด๋Š” ์•„๋‹ˆ์ง€๋งŒ ๋ถ€๋ถ„์ ์œผ๋กœ ๊ทธ ๊ธฐ๋Šฅ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ํ•จ์ˆ˜ํ˜• ์–ธ์–ด๋Š” ๋” ๋ช…ํ™•ํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์ข‹์Šต๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•˜๋Š”๊ฒŒ ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

Bad:

const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

let totalOutput = 0;

for (let i = 0; i < programmerOutput.length; i++) {
  totalOutput += programmerOutput[i].linesOfCode;
}

Good:

const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

const totalOutput = programmerOutput.reduce(
  (totalLines, output) => totalLines + output.linesOfCode,
  0
);

โฌ† back to top

Encapsulate conditionals

์กฐ๊ฑด์„ ์บก์Šํ™”ํ•  ๊ฒƒ

Bad:

if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}

Good:

function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

โฌ† back to top

Avoid negative conditionals

๋ถ€์ •์  ์กฐ๊ฑด์„ ํ”ผํ•  ๊ฒƒ

Bad:

function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}

Good:

function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

โฌ† back to top

Avoid conditionals

์กฐ๊ฑด๋ฌธ์„ ํ”ผํ•  ๊ฒƒ

์–ธ๋œป ๋ดค์„ ๋•Œ ๋ณธ ํ•ญ๋ชฉ์€ ๋ถˆ๊ฐ€๋Šฅํ•ด ๋ณด์ž…๋‹ˆ๋‹ค. ์ด๊ฑธ ์ฒ˜์Œ ๋“ค์—ˆ์„ ๋•Œ ๋Œ€๋ถ€๋ถ„ ์ด๋ ‡๊ฒŒ ๋งํ•ฉ๋‹ˆ๋‹ค. "if๋ฌธ ์—†์ด ๋ญ˜ ์–ด๋–ป๊ฒŒ ํ•ด?" ์—ฌ๊ธฐ์— ๋Œ€ํ•œ ๋Œ€๋‹ต์€, ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ, ๋‹คํ˜•์„ฑ(polymorphism)์„ ์ด์šฉํ•˜๋ฉด ๊ฐ™์€ ์ผ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‘ ๋ฒˆ์งธ ์งˆ๋ฌธ์€ ๋Œ€์ฒด๋กœ ์ด๋ ‡์Šต๋‹ˆ๋‹ค. "๊ทธ๊ฑด ๊ต‰์žฅํ•˜์ง€๋งŒ ์™œ ๊ทธ๋ž˜์•ผ ํ•˜์ฃ ...?" ๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด if๋ฌธ์ด ์žˆ๋Š” class์™€ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์˜ ํ•จ์ˆ˜๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ด์ƒ์˜ ์ผ์„ ํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํ•จ์ˆ˜๋Š” ํ•œ ๊ฐ€์ง€ ์ผ๋งŒ, ์žŠ์ง€๋งˆ์„ธ์š”.

Bad:

class Airplane {
  // ...
  getCruisingAltitude() {
    switch (this.type) {
      case "777":
        return this.getMaxAltitude() - this.getPassengerCount();
      case "Air Force One":
        return this.getMaxAltitude();
      case "Cessna":
        return this.getMaxAltitude() - this.getFuelExpenditure();
    }
  }
}

Good:

class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude();
  }
}

class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getFuelExpenditure();
  }
}

โฌ† back to top

Avoid type-checking (part 1)

ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ํ”ผํ•  ๊ฒƒ (part 1)

Javascript๋Š” ํƒ€์ž…์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฆ‰ ํ•จ์ˆ˜ ์ธ์ˆ˜๋กœ ๋ชจ๋“  ํƒ€์ž…์„ ๋„˜๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ž์œ ๋กœ์›€์œผ๋กœ ์ธํ•ด ํ•จ์ˆ˜ ์•ˆ์—์„œ ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ณ ์ž ํ•˜๋Š” ์œ ํ˜น์— ์ข…์ข… ๋น ์ง€๊ณค ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ”ผํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋กœ ์ผ๊ด€์„ฑ ์žˆ๋Š” API์ž…๋‹ˆ๋‹ค.

Bad:

function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location("texas"));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location("texas"));
  }
}

Good:

function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}

โฌ† back to top

Avoid type-checking (part 2)

ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ํ”ผํ•  ๊ฒƒ (part 2)

๋งŒ์•ฝ ๋‹น์‹ ์ด ๋ฌธ์ž์—ด, ์ˆซ์ž ๋“ฑ ๊ธฐ๋ณธ์ ์ธ ์›์‹œ๊ฐ’์„ ๋‹ค๋ฃจ๊ณ  ์žˆ๊ณ , ๋‹คํ˜•์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์œผ๋‚˜ ํƒ€์ž… ๊ฒ€์‚ฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ, Typescript ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Typescript๋Š” ์ •์  ํƒ€์ดํ•‘ ๊ธฐ๋Šฅ์„ ํ‘œ์ค€ Javascript์— ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์— Javascript์˜ ํ›Œ๋ฅญํ•œ ๋Œ€์ฒด์ œ๋ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง์ ‘ Javascript ํƒ€์ž… ๊ฒ€์‚ฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์˜ ๋ฌธ์ œ๋Š” ๋„ˆ๋ฌด ๋งŽ์€ ๊ตฌ๋ฌธ์ด ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•˜๋‹จ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ํ†ตํ•ด ์–ป์€ ๊ฐ€์งœ "ํƒ€์ž… ์•ˆ์ „์„ฑ"์€ ๊ฐ€๋…์„ฑ์„ ๋–จ์–ดํŠธ๋ฆฝ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ Javascript๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ณ , ์ข‹์€ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ  ์ข‹์€ ์ฝ”๋“œ ๋ฆฌ๋ทฐ๋ฅผ ํ•ฉ์‹œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด Typescript๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค(์ œ๊ฐ€ ๋งํ–ˆ๋˜ ๊ฒƒ ์ฒ˜๋Ÿผ ์ข‹์€ ๋Œ€์•ˆ์ด ๋  ๊ฒ๋‹ˆ๋‹ค!).

Bad:

function combine(val1, val2) {
  if (
    (typeof val1 === "number" && typeof val2 === "number") ||
    (typeof val1 === "string" && typeof val2 === "string")
  ) {
    return val1 + val2;
  }

  throw new Error("Must be of type String or Number");
}

Good:

function combine(val1, val2) {
  return val1 + val2;
}

โฌ† back to top

Don't over-optimize

๊ณผ๋„ํ•œ ์ตœ์ ํ™”๋ฅผ ํ”ผํ•  ๊ฒƒ

๋ชจ๋˜ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋Ÿฐํƒ€์ž„ ์•ˆ์—์„œ ๋งŽ์€ ์ตœ์ ํ™”๋ฅผ ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ตœ์ ํ™”๋ฅผ ๋งŽ์ด ๋ฐ˜๋ณตํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์ด๋Š” ์‹œ๊ฐ„ ๋‚ญ๋น„์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์— ์–ด๋”” ์ตœ์ ํ™”๊ฐ€ ๋ถ€์กฑํ•œ์ง€ ์•Œ์•„๋ณผ ์ข‹์€ ๋ฆฌ์†Œ์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋“ค์ด ์ˆ˜์ •๋˜๊ธฐ ์ „๊นŒ์ง€๋Š” ์ด๋“ค๋งŒ ์ตœ์ ํ™” ๋Œ€์ƒ์œผ๋กœ ๋ณด๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

Bad:

// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
// ์˜ˆ์ „ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์บ์‹œ๋˜์ง€์•Š์€ 'list.length'๋Š” ์žฌ๊ณ„์‚ฐ์œผ๋กœ ์ธํ•ด cost๊ฐ€ ์š”๊ตฌ๋ฉ๋‹ˆ๋‹ค.
// ๋ชจ๋˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ตœ์ ํ™”๋ผ ์žˆ์Šต๋‹ˆ๋‹ค.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

Good:

for (let i = 0; i < list.length; i++) {
  // ...
}

โฌ† back to top

Remove dead code

์•ˆ ์“ฐ๋Š” ์ฝ”๋“œ๋ฅผ ์‚ญ์ œํ•  ๊ฒƒ

์•ˆ ์“ฐ๋Š” ์ฝ”๋“œ๋Š” ์ค‘๋ณต ์ฝ”๋“œ๋งŒํผ ๋‚˜์ฉ๋‹ˆ๋‹ค. ๊ตณ์ด ์ด๋“ค์„ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ๋ฒ ์ด์Šค์— ๋‚จ๊ฒจ๋‘˜ ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ด๋“ค์„ ํ˜ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, ์‚ญ์ œํ•˜์‹ญ์‹œ์˜ค! ๋งŒ์•ฝ ์•„์ง ํ•„์š”ํ•˜๋‹ค๊ณ  ํ•ด๋„ ๋ฒ„์ „ ๊ด€๋ฆฌ ์ด๋ ฅ์— ์•ˆ์ „ํ•˜๊ฒŒ ๋‚จ์•„์žˆ์„ ๊ฒ๋‹ˆ๋‹ค.

Bad:

function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

Good:

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

โฌ† back to top

Objects and Data Structures

Use getters and setters

getters์™€ setters๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ

๋‹จ์ˆœํžˆ object์˜ property๋ฅผ ๋ณด๊ธฐ๋ณด๋‹ค getters์™€ setters๋ฅผ ์‚ฌ์šฉํ•ด object์ƒ์˜ data์— ์ ‘๊ทผํ•˜๋Š”๊ฒŒ ๋” ์ข‹์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. "์™œ?" ๊ถ๊ธˆํ•  ๊ฒ๋‹ˆ๋‹ค. ์•„๋ž˜ ๋ชฉ๋ก์— ๊ทธ ์ด์œ ๋ฅผ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค:

  • object propert๋ฅผ ์–ป๋Š” ๊ฒƒ ๋ฟ๋งŒ ์•„๋‹Œ ๋” ๋งŽ์€ ์ผ์„ ํ•˜๋ คํ•  ๋•Œ ์ฝ”๋“œ๋ฒ ์ด์Šค ๋‚ด ๋ชจ๋“  accessor๋ฅผ ์ฐพ์„ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • set์„ ์‚ฌ์šฉํ•  ๋•Œ validation ์ถ”๊ฐ€๊ฐ€ ๊ฐ„ํŽธํ•ฉ๋‹ˆ๋‹ค.
  • ๋‚ด๋ถ€๋ฅผ ์บก์Šํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • getting, setting์„ ํ•  ๋•Œ ๋กœ๊น…๊ณผ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ์ถ”๊ฐ€๊ฐ€ ๊ฐ„ํŽธํ•ฉ๋‹ˆ๋‹ค.
  • sever์—์„œ ๊ฐ€์ ธ์˜ค๋Š” object property์˜ lazy load๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Bad:

function makeBankAccount() {
  // ...

  return {
    balance: 0
    // ...
  };
}

const account = makeBankAccount();
account.balance = 100;

Good:

function makeBankAccount() {
  // this one is private
  let balance = 0;

  // a "getter", made public via the returned object below
  function getBalance() {
    return balance;
  }

  // a "setter", made public via the returned object below
  function setBalance(amount) {
    // ... validate before updating the balance
    balance = amount;
  }

  return {
    // ...
    getBalance,
    setBalance
  };
}

const account = makeBankAccount();
account.setBalance(100);

โฌ† back to top

Make objects have private members

object๋Š” private member๋ฅผ ๊ฐ–๊ฒŒ ํ•  ๊ฒƒ

์ด๋ฅผ ์œ„ํ•ด ํด๋กœ์ €๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (ES5 ์ด์ „์˜ ๊ฒฝ์šฐ).

Bad:

const Employee = function(name) {
  this.name = name;
};

Employee.prototype.getName = function getName() {
  return this.name;
};

const employee = new Employee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: undefined

Good:

function makeEmployee(name) {
  return {
    getName() {
      return name;
    }
  };
}

const employee = makeEmployee("John Doe");
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe
delete employee.name;
console.log(`Employee name: ${employee.getName()}`); // Employee name: John Doe

โฌ† back to top

Classes

Prefer ES2015/ES6 classes over ES5 plain functions

ES5 ํ•จ์ˆ˜๋ณด๋‹ค ES2015/ES6 class๋ฅผ ์šฐ์„ ํ•  ๊ฒƒ

๊ธฐ์กด์˜ ES5 class์—์„œ๋Š” ๊ฐ€๋…์„ฑ์ด ์ข‹์€ class ๊ณ„์Šน, ๊ตฌ์ถ•, ๋ฉ”์†Œ๋“œ ์ •์˜๋ฅผ ํ•˜๊ธฐ๊ฐ€ ๋งค์šฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๊ณ„์Šน์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ(๊ทธ๋ ‡์ง€ ์•Š๋‹ค ํ•˜๋”๋ผ๋„), ES2015/ES6 class๋ฅผ ์šฐ์„ ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ, ํฌ๊ณ  ๋ณต์žกํ•œ object์˜ ํ•„์š”์„ฑ์„ ๋Š๋ผ๊ธฐ ์ „์—๋Š” class๋ณด๋‹ค ์ž‘์€ ํ•จ์ˆ˜ ๋‹จ์œ„๋ฅผ ์šฐ์„ ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

Bad:

const Animal = function(age) {
  if (!(this instanceof Animal)) {
    throw new Error("Instantiate Animal with `new`");
  }

  this.age = age;
};

Animal.prototype.move = function move() {};

const Mammal = function(age, furColor) {
  if (!(this instanceof Mammal)) {
    throw new Error("Instantiate Mammal with `new`");
  }

  Animal.call(this, age);
  this.furColor = furColor;
};

Mammal.prototype = Object.create(Animal.prototype);
Mammal.prototype.constructor = Mammal;
Mammal.prototype.liveBirth = function liveBirth() {};

const Human = function(age, furColor, languageSpoken) {
  if (!(this instanceof Human)) {
    throw new Error("Instantiate Human with `new`");
  }

  Mammal.call(this, age, furColor);
  this.languageSpoken = languageSpoken;
};

Human.prototype = Object.create(Mammal.prototype);
Human.prototype.constructor = Human;
Human.prototype.speak = function speak() {};

Good:

class Animal {
  constructor(age) {
    this.age = age;
  }

  move() {
    /* ... */
  }
}

class Mammal extends Animal {
  constructor(age, furColor) {
    super(age);
    this.furColor = furColor;
  }

  liveBirth() {
    /* ... */
  }
}

class Human extends Mammal {
  constructor(age, furColor, languageSpoken) {
    super(age, furColor);
    this.languageSpoken = languageSpoken;
  }

  speak() {
    /* ... */
  }
}

โฌ† back to top

Use method chaining

๋ฉ”์†Œ๋“œ ์ฒด์ธ์„ ์‚ฌ์šฉํ•  ๊ฒƒ

๋ณธ ํŒจํ„ด์€ Javascript์—์„œ ๋งค์šฐ ์œ ์šฉํ•œ ํŒจํ„ด์œผ๋กœ jQuery๋‚˜ Lodash ๋“ฑ ๋งŽ์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‚ด์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ๋ฅผ ์•Œ๊ธฐ ์‰ฝ๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ์ด์— ์ €๋Š” "๋ฉ”์†Œ๋“œ ์ฒด์ธ์„ ํ†ตํ•ด ์ฝ”๋“œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ๊น”๋”ํ•ด์ง€๋Š”์ง€ ๋ด๋‹ฌ๋ผ"๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค. class ๋‚ด ๋ชจ๋“  ํ•จ์ˆ˜์— ๋‹จ์ˆœํžˆ this ๋ฐ˜ํ™˜์„ ์ถ”๊ฐ€ํ•ด class์˜ method๋ฅผ chain์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
  }

  setModel(model) {
    this.model = model;
  }

  setColor(color) {
    this.color = color;
  }

  save() {
    console.log(this.make, this.model, this.color);
  }
}

const car = new Car("Ford", "F-150", "red");
car.setColor("pink");
car.save();

Good:

class Car {
  constructor(make, model, color) {
    this.make = make;
    this.model = model;
    this.color = color;
  }

  setMake(make) {
    this.make = make;
    // NOTE: Returning this for chaining
    return this;
  }

  setModel(model) {
    this.model = model;
    // NOTE: Returning this for chaining
    return this;
  }

  setColor(color) {
    this.color = color;
    // NOTE: Returning this for chaining
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // NOTE: Returning this for chaining
    return this;
  }
}

const car = new Car("Ford", "F-150", "red").setColor("pink").save();

โฌ† back to top

Prefer composition over inheritance

๊ณ„์Šน๋ณด๋‹ค ์กฐํ•ฉ์„ ์šฐ์„ ํ•  ๊ฒƒ

์œ ๋ช…ํ•œ Gang of Four์˜ ์ €์„œ Design Patterns์— ๋”ฐ๋ฅด๋ฉด, ๊ฐ€๋Šฅํ•œ ๊ณ„์Šน๋ณด๋‹ค ์กฐํ•ฉ์„ ์šฐ์„ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ณ„์Šน์ด ์ข‹์€ ์ด์œ ๋„ ๋งŽ๊ณ  ์กฐํ•ฉ์ด ์ข‹์€ ์ด์œ ๋„ ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณธ ํ•ญ๋ชฉ์˜ ์š”์ ์€, ์—ฌ๋Ÿฌ๋ถ„์ด ๋ณธ๋Šฅ์ ์œผ๋กœ ๊ณ„์Šน์„ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•  ๋•Œ, ์กฐํ•ฉ์ด ๊ทธ ๋ฌธ์ œ๋ฅผ ๋” ์ž˜ ๋ชจ๋ธํ™”ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ณ ๋ คํ•ด ๋ณด๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ๊ฐ€ ์ข…์ข… ์žˆ์Šต๋‹ˆ๋‹ค.

"์–ธ์ œ ๊ณ„์Šน์„ ์จ์•ผ ํ•˜์ง€?" ์—ฌ๋Ÿฌ๋ถ„์€ ๊ถ๊ธˆํ•ด ํ•˜์‹ค ๊ฒ๋‹ˆ๋‹ค. ์ด๋Š” ์ง๋ฉดํ•œ ์ƒํ™ฉ์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒ ์ง€๋งŒ, ๋‹ค์Œ์€ ๊ณ„์Šน์ด ์กฐํ•ฉ๋ณด๋‹ค ์ ํ•ฉํ•œ ๊ฒฝ์šฐ์˜ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค.

  1. ๊ณ„์Šน์ด "has-a" ๊ด€๊ณ„๊ฐ€ ์•„๋‹Œ "is-a" ๊ด€๊ณ„๋ฅผ ๋‚˜ํƒ€๋‚ผ ๋•Œ (์‚ฌ์šฉ์ž->์‚ฌ์šฉ์ž ์ƒ์„ธ์ •๋ณด vs. ์‚ฌ๋žŒ->๋™๋ฌผ).
  2. ๊ธฐ๋ณธ class์˜ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๋•Œ (์‚ฌ๋žŒ์€ ๋ชจ๋“  ๋™๋ฌผ์ฒ˜๋Ÿผ ์›€์ง์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค).
  3. ๊ธฐ๋ณธ class๋ฅผ ๋ณ€๊ฒฝํ•ด, ํŒŒ์ƒ class๋ฅผ ์ „์—ญ์ ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ณ ์ž ํ•  ๋•Œ (๋ชจ๋“  ๋™๋ฌผ์˜ ์ด๋™ ์นผ๋กœ๋ฆฌ ์†Œ๋ชจ๋ฅผ ๋ณ€๊ฒฝ).

Bad:

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  // ...
}

// Bad because Employees "have" tax data. EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
  constructor(ssn, salary) {
    super();
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

Good:

class EmployeeTaxData {
  constructor(ssn, salary) {
    this.ssn = ssn;
    this.salary = salary;
  }

  // ...
}

class Employee {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }

  setTaxData(ssn, salary) {
    this.taxData = new EmployeeTaxData(ssn, salary);
  }
  // ...
}

โฌ† back to top

SOLID

Single Responsibility Principle (SRP)

๋‹จ์ผ ์ฑ…์ž„์˜ ์›์น™ (SRP)

Clean Code์— ์ ํ˜€ ์žˆ๋“ฏ์ด, "class๋ฅผ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ์ด์œ ๋Š” 1๊ฐ€์ง€๋ฅผ ๋„˜์œผ๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค". ๋น„ํ–‰๊ธฐ ์—ฌํ–‰ ๋•Œ ํ•˜๋‚˜์˜ ์ง๋งŒ ๊ฐ–๊ณ  ํƒˆ ์ˆ˜ ์žˆ์„ ๋•Œ์ฒ˜๋Ÿผ, ํ•˜๋‚˜์˜ class์— ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ์„ ๋„ฃ๋Š” ๊ฒƒ์€ ๋งค์šฐ ์œ ํ˜น์ ์ž…๋‹ˆ๋‹ค. ์ด ๋•Œ ๋ฌธ์ œ๋Š” ํ•ด๋‹น class๊ฐ€ ๊ฐœ๋…์ ์œผ๋กœ ์ผ๊ด€๋˜์ง€ ์•Š๊ณ  ์—ฌ๋Ÿฌ ์ด์œ ๋กœ ์ธํ•ด ๋ฐ”๋€” ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. class ๋ณ€๊ฒฝ ํšŸ์ˆ˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฒƒ์€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ํ•˜๋‚˜์˜ class์— ๋„ˆ๋ฌด ๋งŽ์€ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋ผ ์žˆ๊ณ  ๊ทธ ์ค‘ ์ผ๋ถ€๋ฅผ ๋ฐ”๊ฟ€ ๊ฒฝ์šฐ, ์ฝ”๋“œ๋ฒ ์ด์Šค ๋‚ด ๋‹ค๋ฅธ ์˜์กด์  ๋ชจ๋“ˆ์— ์–ด๋–ค ์˜ํ–ฅ์„ ์ค„ ์ง€ ์•Œ๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Bad:

class UserSettings {
  constructor(user) {
    this.user = user;
  }

  changeSettings(settings) {
    if (this.verifyCredentials()) {
      // ...
    }
  }

  verifyCredentials() {
    // ...
  }
}

Good:

class UserAuth {
  constructor(user) {
    this.user = user;
  }

  verifyCredentials() {
    // ...
  }
}

class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }

  changeSettings(settings) {
    if (this.auth.verifyCredentials()) {
      // ...
    }
  }
}

โฌ† back to top

Open/Closed Principle (OCP)

๊ฐœ๋ฐฉ/ํ์‡„ ์›์น™ (OCP)

Bertrand Meyer์— ์˜ํ•˜๋ฉด, "์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ์ฒด (class, ๋ชจ๋“ˆ, ํ•จ์ˆ˜ ๋“ฑ)์€ ํ™•์žฅ์— ๋Œ€ํ•ด์„œ ์—ด๋ ค ์žˆ์–ด์•ผ ํ•˜์ง€๋งŒ ๋ณ€๊ฒฝ์— ๋Œ€ํ•ด์„œ๋Š” ๋‹ซํ˜€ ์žˆ์–ด์•ผ" ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒŒ ๋ฌด์Šจ ๋ง์ผ๊นŒ์š”? ์ด ์›์น™์€ ๊ธฐ๋ณธ์ ์œผ๋กœ, ์œ ์ €๊ฐ€ ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ๋ฐ”๊พธ์ง€ ์•Š๊ณ ๋„ ํ•จ์ˆ˜๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.

Bad:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    if (this.adapter.name === "ajaxAdapter") {
      return makeAjaxCall(url).then(response => {
        // transform response and return
      });
    } else if (this.adapter.name === "nodeAdapter") {
      return makeHttpCall(url).then(response => {
        // transform response and return
      });
    }
  }
}

function makeAjaxCall(url) {
  // request and return promise
}

function makeHttpCall(url) {
  // request and return promise
}

Good:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
    this.name = "ajaxAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
    this.name = "nodeAdapter";
  }

  request(url) {
    // request and return promise
  }
}

class HttpRequester {
  constructor(adapter) {
    this.adapter = adapter;
  }

  fetch(url) {
    return this.adapter.request(url).then(response => {
      // transform response and return
    });
  }
}

โฌ† back to top

Liskov Substitution Principle (LSP)

๋ฆฌ์Šค์ฝ”ํ”„ ์น˜ํ™˜ ์›์น™ (LSP)

์ด๊ฒƒ์€ ์–ด๋ ค์šด ์ด๋ฆ„์„ ๊ฐ€์กŒ์ง€๋งŒ ๋งค์šฐ ๊ฐ„๋‹จํ•œ ์ปจ์…‰์˜ ์›์น™์ž…๋‹ˆ๋‹ค. ๊ธฐ์กด์— ์ •์˜๋œ ๋ฐ”์— ์˜ํ•˜๋ฉด "๋งŒ์•ฝ S๊ฐ€ T์˜ ํ•˜์œ„ํ˜•์ด๋ผ๋ฉด, ํ”„๋กœ๊ทธ๋žจ์ด ๊ฐ–์ถฐ์•ผ ํ•  ์†์„ฑ๋“ค(์ •ํ™•์„ฑ, ์ˆ˜ํ–‰๋˜๋Š” ์ž‘์—… ๋“ฑ)์„ ๋ฐ”๊พธ์ง€ ์•Š๋”๋ผ๋„, ์ž๋ฃŒํ˜• T์˜ ๊ฐ์ฒด๋Š” ์ž๋ฃŒํ˜• S์˜ ๊ฐ์ฒด๋กœ ๊ต์ฒด(์น˜ํ™˜)๋  ์ˆ˜ ์žˆ๋‹ค"๋Š” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.

ํ•œ ๊ฐ€์ง€ ์˜ˆ๋ฅผ ๋“ค์–ด ์„ค๋ช…ํ•˜์ž๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์ด ๋ถ€๋ชจ ํด๋ž˜์Šค์™€ ์ž์‹ ํด๋ž˜์Šค๋ฅผ ๊ฐ–๊ณ  ์žˆ์„ ๋•Œ ๋ฒ ์ด์Šค ํด๋ž˜์Šค์™€ ์ž์‹ ํด๋ž˜์Šค๋Š” ์ž˜๋ชป๋œ ๊ฒฐ๊ณผ ์—†์ด ์„œ๋กœ ๊ตํ™˜ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ์ „ํžˆ ์ž˜ ์ดํ•ด๊ฐ€ ์•ˆ๋œ๋‹ค๋ฉด, ์ •์‚ฌ๊ฐํ˜•-์ง์‚ฌ๊ฐํ˜• ์˜ˆ์‹œ๋ฅผ ์ƒ๊ฐํ•ด ๋ด…์‹œ๋‹ค. ์ˆ˜ํ•™์ ์œผ๋กœ, ์ •์‚ฌ๊ฐํ˜•์€ ์ง์‚ฌ๊ฐํ˜•์ด์ง€๋งŒ, ๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ์ƒ์†์„ ํ†ตํ•œ "is-a" ๊ด€๊ณ„๋ฅผ ์‚ฌ์šฉํ•ด ๋ชจ๋ธ๋งํ•œ๋‹ค๋ฉด, ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

Bad:

class Rectangle {
  constructor() {
    this.width = 0;
    this.height = 0;
  }

  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }

  setWidth(width) {
    this.width = width;
  }

  setHeight(height) {
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Rectangle {
  setWidth(width) {
    this.width = width;
    this.height = width;
  }

  setHeight(height) {
    this.width = height;
    this.height = height;
  }
}

function renderLargeRectangles(rectangles) {
  rectangles.forEach(rectangle => {
    rectangle.setWidth(4);
    rectangle.setHeight(5);
    const area = rectangle.getArea(); // BAD: Returns 25 for Square. Should be 20.
    rectangle.render(area);
  });
}

const rectangles = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangles(rectangles);

Good:

class Shape {
  setColor(color) {
    // ...
  }

  render(area) {
    // ...
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }
}

class Square extends Shape {
  constructor(length) {
    super();
    this.length = length;
  }

  getArea() {
    return this.length * this.length;
  }
}

function renderLargeShapes(shapes) {
  shapes.forEach(shape => {
    const area = shape.getArea();
    shape.render(area);
  });
}

const shapes = [new Rectangle(4, 5), new Rectangle(4, 5), new Square(5)];
renderLargeShapes(shapes);

โฌ† back to top

Interface Segregation Principle (ISP)

์ธํ„ฐํŽ˜์ด์Šค ๋ถ„๋ฆฌ ์›์น™ (ISP)

JavaScript๋Š” ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์ด ์›์น™์€ ๋‹ค๋ฅธ ์›์น™๋“ค์ฒ˜๋Ÿผ ์—„๊ฒฉํ•˜๊ฒŒ ์ ์šฉ์‹œํ‚ฌ ์ˆœ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ JavaScript์— ํƒ€์ž… ์‹œ์Šคํ…œ์ด ์—†๋”๋ผ๋„ ์ด๋Š” ๋งค์šฐ ์ค‘์š”ํ•˜๊ณ  ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

ISP๋Š” "ํด๋ผ์ด์–ธํŠธ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ธํ„ฐํŽ˜์ด์Šค์— ์˜์กดํ•˜๋„๋ก ๊ฐ•์š”๋ฐ›์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค"๊ณ  ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. ๋• ํƒ€์ดํ•‘ ๋•Œ๋ฌธ์— ์ธํ„ฐํŽ˜์ด์Šค๋Š” JavaScript์—์„œ๋Š” ๊ทธ์ € ์•”์‹œ์ ์ธ ๊ณ„์•ฝ์ž…๋‹ˆ๋‹ค.

JavaScript์—์„œ ์ด๋ฅผ ์ž˜ ๋ณด์—ฌ์ฃผ๋Š” ์ข‹์€ ์˜ˆ์‹œ๋Š” ๋งŽ์€ ์–‘์˜ ์„ค์ • ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•œ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐฉ๋Œ€ํ•œ ์–‘์˜ ์˜ต์…˜์„ ์„ค์ •ํ•˜์ง€ ์•Š์•„๋„ ๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋ž์งํ•ฉ๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๋งŽ์€ ๊ฒฝ์šฐ ์„ค์ •๋“ค์ด ๋ชจ๋‘ ํ•„์š”ํ•œ ๊ฑด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์„ค์ •์„ ์„ ํƒ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด "๋ฌด๊ฑฐ์šด ์ธํ„ฐํŽ˜์ด์Šค (fat interface)"๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์„ ํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.settings.animationModule.setup();
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  animationModule() {} // Most of the time, we won't need to animate when traversing.
  // ...
});

Good:

class DOMTraverser {
  constructor(settings) {
    this.settings = settings;
    this.options = settings.options;
    this.setup();
  }

  setup() {
    this.rootNode = this.settings.rootNode;
    this.setupOptions();
  }

  setupOptions() {
    if (this.options.animationModule) {
      // ...
    }
  }

  traverse() {
    // ...
  }
}

const $ = new DOMTraverser({
  rootNode: document.getElementsByTagName("body"),
  options: {
    animationModule() {}
  }
});

โฌ† back to top

Dependency Inversion Principle (DIP)

์˜์กด์„ฑ ์—ญ์ „ ์›์น™ (DIP)

์ด ์›์น™์€ ๋‘ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ๋‚ด์šฉ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค.

  1. ์ƒ์œ„ ๋ชจ๋“ˆ์€ ํ•˜์œ„ ๋ชจ๋“ˆ์— ์ข…์†๋˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์ถ”์ƒํ™”์— ์˜์กดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ถ”์ƒํ™”๋Š” ์„ธ๋ถ€์‚ฌํ•ญ์— ์ข…์†๋˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์„ธ๋ถ€์‚ฌํ•ญ์€ ์ถ”์ƒํ™”์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฒ˜์Œ์—๋Š” ์ด๊ฒƒ์„ ์ดํ•ดํ•˜๋Š”๊ฒŒ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋งŒ์•ฝ AngularJS๋ฅผ ์จ๋ณธ ์ ์ด ์žˆ๋‹ค๋ฉด ์˜์กด์„ฑ ์ฃผ์ž…(Dependency Injection) ํ˜•ํƒœ๋กœ ์ด ์›๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ๋ณธ ์ ์ด ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค. DIP๋Š” ๋™์ผํ•œ ๊ฐœ๋…์€ ์•„๋‹ˆ์ง€๋งŒ ์ƒ์œ„ ๋ชจ๋“ˆ์ด ํ•˜์œ„ ๋ชจ๋“ˆ์˜ ์„ธ๋ถ€์‚ฌํ•ญ์„ ์•Œ์ง€ ๋ชปํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” DI๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. DI์˜ ์žฅ์ ์€ ์ด๋ฅผ ํ†ตํ•ด ๋ชจ๋“ˆ ๊ฐ„ ์˜์กด์„ฑ์„ ๊ฐ์†Œ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ฐ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“ˆ ๊ฐ„ ์˜์กด์„ฑ์€ ๋ฆฌํŒฉํ† ๋ง์„ ์–ด๋ ต๊ฒŒ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋งค์šฐ ๋‚˜์œ ๊ฐœ๋ฐœ ํŒจํ„ด์ž…๋‹ˆ๋‹ค.

์œ„์—์„œ ๋งํ–ˆ๋“ฏ์ด, JavaScript๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ–์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ถ”์ƒํ™”์— ์˜์กดํ•˜๋Š” ๊ฒƒ์€ ์•”์‹œ์ ์ธ ์•ฝ์†์ž…๋‹ˆ๋‹ค. ์ด๋ง์ธ์ฆ‰์Šจ, ๋‹ค๋ฅธ ๊ฐ์ฒด๋‚˜ ํด๋ž˜์Šค์— ๋…ธ์ถœ๋˜๋Š” ๋ฉ”์†Œ๋“œ์™€ ์†์„ฑ์ด ์•”์‹œ์ ์ธ ์•ฝ์†(์ถ”์ƒํ™”)์— ํ•ด๋‹น๋œ๋‹ค๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์‹œ์—์„œ, ์•”์‹œ์  ์•ฝ์†์€ InventoryTracker์— ๋Œ€ํ•œ ๋ชจ๋“  ์š”์ฒญ ๋ชจ๋“ˆ์ด requestItems ๋ฉ”์†Œ๋“œ๋ฅผ ๊ฐ€์ง„๋‹ค๋Š” ๊ฒƒ์„ ๋งํ•ฉ๋‹ˆ๋‹ค.

Bad:

class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryTracker {
  constructor(items) {
    this.items = items;

    // BAD: We have created a dependency on a specific request implementation.
    // We should just have requestItems depend on a request method: `request`
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

const inventoryTracker = new InventoryTracker(["apples", "bananas"]);
inventoryTracker.requestItems();

Good:

class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach(item => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ["HTTP"];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ["WS"];
  }

  requestItem(item) {
    // ...
  }
}

// By constructing our dependencies externally and injecting them, we can easily
// substitute our request module for a fancy new one that uses WebSockets.
const inventoryTracker = new InventoryTracker(
  ["apples", "bananas"],
  new InventoryRequesterV2()
);
inventoryTracker.requestItems();

โฌ† back to top

Testing

ํ…Œ์ŠคํŠธ

ํ…Œ์ŠคํŠธ๋Š” ๋ฐฐํฌ๋ณด๋‹ค ๋” ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ถฉ๋ถ„ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ์—†๋‹ค๋ฉด, ๋ฐฐํฌ๋ฅผ ํ•  ๋•Œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฝ”๋“œ์— ์ด์ƒ์ด ์—†๋Š”์ง€ ํ™•์‹ ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์‹œ๊ฐ„์„ ํˆฌ์žํ• ์ง€๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ํ•จ๊ป˜ ์ผํ•˜๋Š” ํŒ€์˜ ๊ฒฐ์ •์— ๋‹ฌ๋ ธ์ง€๋งŒ, Coverage๊ฐ€ 100%๋ผ๋Š” ๊ฒƒ์€ ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ ๋†’์€ ์ž์‹ ๊ฐ๊ณผ ์•ˆ๋„๊ฐ์„ ์ค๋‹ˆ๋‹ค. ์ด ๋ง์€ ํ›Œ๋ฅญํ•œ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ข‹์€ Coverage ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค๋Š” ๋ง์ž…๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ๋ณ€๋ช…์€ ์—†์Šต๋‹ˆ๋‹ค. [์ข‹์€ JS ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ]((https://jstherightway.org/#testing-tools)๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ค‘์—์„œ ํŒ€์˜ ๊ธฐํ˜ธ์— ๋งž๋Š” ๊ฒƒ์„ ๊ณ ๋ฅด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ ํŒ€์— ๋งž๋Š” ๊ฒƒ์„ ์ฐพ์€ ๋’ค์—๋Š”, ์ƒˆ๋กœ ๊ธฐ๋Šฅ/๋ชจ๋“ˆ์„ ๋„์ž…ํ•  ๋•Œ๋งˆ๋‹ค ํ•ญ์ƒ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก (Test Driven Development, TDD)์„ ์„ ํ˜ธํ•œ๋‹ค๋ฉด ํ›Œ๋ฅญํ•˜์ง€๋งŒ, ์ค‘์š”ํ•œ ์ ์€ ๊ธฐ๋Šฅ์„ ๋ฐฐํฌํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ๊ธฐ๋Šฅ์„ ๋ฆฌํŒฉํ† ๋งํ•˜๊ธฐ ์ „์— Coverage ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ–ˆ๋Š”์ง€ ํ™•์‹คํ•˜๊ฒŒ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Single concept per test

ํ…Œ์ŠคํŠธ ์ปจ์…‰

Bad:

import assert from "assert";

describe("MomentJS", () => {
  it("handles date boundaries", () => {
    let date;

    date = new MomentJS("1/1/2015");
    date.addDays(30);
    assert.equal("1/31/2015", date);

    date = new MomentJS("2/1/2016");
    date.addDays(28);
    assert.equal("02/29/2016", date);

    date = new MomentJS("2/1/2015");
    date.addDays(28);
    assert.equal("03/01/2015", date);
  });
});

Good:

import assert from "assert";

describe("MomentJS", () => {
  it("handles 30-day months", () => {
    const date = new MomentJS("1/1/2015");
    date.addDays(30);
    assert.equal("1/31/2015", date);
  });

  it("handles leap year", () => {
    const date = new MomentJS("2/1/2016");
    date.addDays(28);
    assert.equal("02/29/2016", date);
  });

  it("handles non-leap year", () => {
    const date = new MomentJS("2/1/2015");
    date.addDays(28);
    assert.equal("03/01/2015", date);
  });
});

โฌ† back to top

Concurrency

๋™์‹œ์„ฑ

Use Promises, not callbacks

Callback ๋Œ€์‹  Promise๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ

Callback์€ ๊น”๋”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์—„์ฒญ๋‚œ ์–‘์˜ ์ค‘์ฒฉ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ES201/ES6์—์„œ๋Š” Promise๊ฐ€ ๋‚ด์žฅ๋ผ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฑธ ์‚ฌ์šฉํ•ฉ์‹œ๋‹ค!

Bad:

import { get } from "request";
import { writeFile } from "fs";

get(
  "https://en.wikipedia.org/wiki/Robert_Cecil_Martin",
  (requestErr, response, body) => {
    if (requestErr) {
      console.error(requestErr);
    } else {
      writeFile("article.html", body, writeErr => {
        if (writeErr) {
          console.error(writeErr);
        } else {
          console.log("File written");
        }
      });
    }
  }
);

Good:

import { get } from "request-promise";
import { writeFile } from "fs-extra";

get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
  .then(body => {
    return writeFile("article.html", body);
  })
  .then(() => {
    console.log("File written");
  })
  .catch(err => {
    console.error(err);
  });

โฌ† back to top

Async/Await are even cleaner than Promises

Promise๋ณด๋‹ค ๋” ๊น”๋”ํ•œ Async/Await

Promise๋Š” Callback์— ๋น„ํ•ด ๋งค์šฐ ๊น”๋”ํ•˜์ง€๋งŒ ES2017/ES8์—”๋” ๊น”๋”ํ•œ async์™€ await๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•จ์ˆ˜ ์•ž์— async๋ฅผ ๋ถ™์ด๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋”์ด์ƒ ํ•จ์ˆ˜ ์—ฐ๊ฒฐ์„ ์œ„ํ•ด then์„ ์“ธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ES2017/ES8์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ด๊ฑธ ์“ฐ์„ธ์š”!

Bad:

import { get } from "request-promise";
import { writeFile } from "fs-extra";

get("https://en.wikipedia.org/wiki/Robert_Cecil_Martin")
  .then(body => {
    return writeFile("article.html", body);
  })
  .then(() => {
    console.log("File written");
  })
  .catch(err => {
    console.error(err);
  });

Good:

import { get } from "request-promise";
import { writeFile } from "fs-extra";

async function getCleanCodeArticle() {
  try {
    const body = await get(
      "https://en.wikipedia.org/wiki/Robert_Cecil_Martin"
    );
    await writeFile("article.html", body);
    console.log("File written");
  } catch (err) {
    console.error(err);
  }
}

getCleanCodeArticle()

โฌ† back to top

Error Handling

์—๋Ÿฌ ์ฒ˜๋ฆฌ (Error Handling)

์—๋Ÿฌ๋ฅผ ๋ฑ‰๋Š”๋‹ค๋Š”๊ฑด ์ข‹์€๊ฒ๋‹ˆ๋‹ค! ํ”„๋กœ๊ทธ๋žจ์—์„œ ๋ญ”๊ฐ€ ์ž˜๋ชป๋์„ ๋•Œ, ๋Ÿฐํƒ€์ž„์ด ์ด๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ํ™•์ธํ–ˆ๋‹ค๋Š” ๊ฑธ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋Ÿฐํƒ€์ž„์€ ํ˜„์žฌ ์Šคํƒ์—์„œ ํ•จ์ˆ˜ ์‹คํ–‰์„ ์ค‘๋‹จ, (๋…ธ๋“œ์—์„œ) ํ”„๋กœ์„ธ์Šค๋ฅผ ์ข…๋ฃŒํ•œ ๋’ค, ์Šคํƒ ์ถ”์ ์œผ๋กœ ์ฝ˜์†”์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ทธ ์ด์œ ๋ฅผ ์•Œ๋ ค์ค๋‹ˆ๋‹ค.

Don't ignore caught errors

์—๋Ÿฌ๋ฅผ ๋ฌด์‹œํ•˜์ง€์ง€ ๋ง ๊ฒƒ

์—๋Ÿฌ๋ฅผ ํ™•์ธํ•˜๊ณ ๋„ ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ํ•œ๋‹ค๋ฉด, ์ด๋ฅผ ์ฒ˜๋ฆฌ๊ฑฐ๋‚˜ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. console.log๋ฅผ ํ†ตํ•ด ์ฝ˜์†”์— ์—๋Ÿฌ๋ฅผ ๋กœ๊น…ํ•˜๋Š” ๊ฒƒ์€ ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ์žƒ์–ด๋ฒ„๋ฆฌ๊ธฐ ์‰ฌ์›Œ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋งŒ์•ฝ try/catch๋กœ ์–ด๋–ค ์ฝ”๋“œ๋ฅผ ๊ฐ์ŒŒ๋‹ค๋ฉด ๊ทธ๊ฑด ๋‹น์‹ ์ด ๊ทธ ์ฝ”๋“œ์— ๋Œ€ํ•ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ• ์ง€๋„ ๋ชจ๋ฅด๊ธฐ ๋–„๋ฌธ์— ๊ฐ์‹ผ ๊ฒƒ์ด๋ฏ€๋กœ, ์ด์— ๋Œ€ํ•ด ๊ณ„ํš์„ ๋งˆ๋ จํ•˜๊ฑฐ๋‚˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

Bad:

try {
  functionThatMightThrow();
} catch (error) {
  console.log(error);
}

Good:

try {
  functionThatMightThrow();
} catch (error) {
  // One option (more noisy than console.log):
  console.error(error);
  // Another option:
  notifyUserOfError(error);
  // Another option:
  reportErrorToService(error);
  // OR do all three!
}

Don't ignore rejected promises

Promise ์‹คํŒจ๋ฅผ ๋ฌด์‹œํ•˜์ง€ ๋ง ๊ฒƒ

๊ฐ™์€ ์ด์œ ๋กœ try/catch์—์„œ ๋ฐœ์ƒํ•œ ์—๋Ÿฌ๋Ÿฌ๋„ ๋ฌด์‹œํ•˜๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค.

Bad:

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    console.log(error);
  });

Good:

getdata()
  .then(data => {
    functionThatMightThrow(data);
  })
  .catch(error => {
    // One option (more noisy than console.log):
    console.error(error);
    // Another option:
    notifyUserOfError(error);
    // Another option:
    reportErrorToService(error);
    // OR do all three!
  });

โฌ† back to top

Formatting

ํฌ๋งคํŒ…

ํฌ๋งทํŒ…์€ ์ฃผ๊ด€์ ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ ์žˆ๋Š” ๋งŽ์€ ๊ทœ์น™๋“ค์ฒ˜๋Ÿผ ๊ผญ ๋”ฐ๋ผ์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ์ ์€ ํฌ๋งทํŒ…์— ๊ณผํ•˜๊ฒŒ ์ง‘์ฐฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํฌ๋งคํŒ… ์ฒดํฌ๋ฅผ ์ž๋™์œผ๋กœ ํ•ด์ฃผ๋Š” [๋งŽ์€ ๋„๊ตฌ๋“ค]((https://standardjs.com/rules.html)์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ์ค‘์—์„œ ํ•˜๋‚˜๋ฅผ ๊ณจ๋ผ ์‚ฌ์šฉํ•˜์„ธ์š”! ์—”์ง€๋‹ˆ์–ด๊ฐ€ ํฌ๋งทํŒ…์— ๊ด€ํ•ด ๋…ผ์Ÿํ•˜๋Š” ๊ฒƒ์€ ์‹œ๊ฐ„๊ณผ ๋ˆ์˜ ๋‚ญ๋น„์ž…๋‹ˆ๋‹ค.

์ž๋™ ์„œ์‹ ๊ต์ •์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฒƒ๋“ค(๋“ค์—ฌ์“ฐ๊ธฐ, ํƒญ vs ์ŠคํŽ˜์ด์Šค, ํฐ vs ์ž‘์€ ๋”ฐ์˜ดํ‘œ ๋“ฑ)์— ๊ด€ํ•œ ๋ช‡ ๊ฐ€์ง€ ์ง€์นจ์„ ์†Œ๊ฐœํ•ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

Use consistent capitalization

์ผ๊ด€๋œ ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ

JavaScript์—๋Š” ์ •ํ•ด์ง„ ํƒ€์ž…์ด ์—†๊ธฐ ๋•Œ๋ฌธ์—, ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋‹น์‹ ์˜ ๋ณ€์ˆ˜๋‚˜ ํ•จ์ˆ˜ ๋“ฑ์— ๋Œ€ํ•ด ๋งŽ์€ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ทœ์น™์€ ์ฃผ๊ด€์ ์ธ ๊ฒƒ์ด๋ฉฐ, ๋‹น์‹ ์˜ ํŒ€์ด ์ž์œ ๋กญ๊ฒŒ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ๊ฒƒ์€ ์–ด๋””๊นŒ์ง€๋‚˜ ๊ทœ์น™์ด ์ผ๊ด€๋˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Bad:

const DAYS_IN_WEEK = 7;
const daysInMonth = 30;

const songs = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const Artists = ["ACDC", "Led Zeppelin", "The Beatles"];

function eraseDatabase() {}
function restore_database() {}

class animal {}
class Alpaca {}

Good:

const DAYS_IN_WEEK = 7;
const DAYS_IN_MONTH = 30;

const SONGS = ["Back In Black", "Stairway to Heaven", "Hey Jude"];
const ARTISTS = ["ACDC", "Led Zeppelin", "The Beatles"];

function eraseDatabase() {}
function restoreDatabase() {}

class Animal {}
class Alpaca {}

โฌ† back to top

Function callers and callees should be close

ํ•จ์ˆ˜ํ˜ธ์ถœ์ž์™€ ํ”ผํ˜ธ์ถœ์ž๋Š” ๊ฐ€๊นŒ์šธ ๊ฒƒ

์–ด๋–ค ํ•จ์ˆ˜๊ฐ€ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด, ๊ทธ ํ•จ์ˆ˜๋“ค์ด ์†Œ์Šค ํŒŒ์ผ ์•ˆ์•ˆ์—์„œ ์ˆ˜์ง์œผ๋กœ ๊ทผ์ ‘ํ•ด ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. ํ˜ธ์ถœ ํ•จ์ˆ˜๋ฅผ ํ”ผํ˜ธ์ถœ ํ•จ์ˆ˜ ๋ฐ”๋กœ ์œ„์— ๋‘๋Š” ๊ฒƒ์ด ์ด์ƒ์ ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ฝ”๋“œ๋ฅผ ์ฝ์„ ๋•Œ ์‹ ๋ฌธ์ฒ˜๋Ÿผ ์œ„์—์„œ ์•„๋ž˜๋กœ ์ฝ๊ธฐ ๋•Œ๋ฌธ์—, ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋„ ์ด๋ฅผ ๊ณ ๋ คํ•ด ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Bad:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  lookupPeers() {
    return db.lookup(this.employee, "peers");
  }

  lookupManager() {
    return db.lookup(this.employee, "manager");
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

Good:

class PerformanceReview {
  constructor(employee) {
    this.employee = employee;
  }

  perfReview() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();
  }

  getPeerReviews() {
    const peers = this.lookupPeers();
    // ...
  }

  lookupPeers() {
    return db.lookup(this.employee, "peers");
  }

  getManagerReview() {
    const manager = this.lookupManager();
  }

  lookupManager() {
    return db.lookup(this.employee, "manager");
  }

  getSelfReview() {
    // ...
  }
}

const review = new PerformanceReview(employee);
review.perfReview();

โฌ† back to top

Comments

์ฃผ์„

Only comment things that have business logic complexity.

๋ณต์žกํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—๋งŒ ์ฃผ์„์„ ์ž‘์„ฑํ•  ๊ฒƒ

์ฃผ์„์€ ํ•„์ˆ˜ ์‚ฌํ•ญ์ด ์•„๋‹ˆ๋ฉฐ, ์ผ์ข…์˜ ์‚ฌ๊ณผ์ž…๋‹ˆ๋‹ค. ์ข‹์€ ์ฝ”๋“œ๋Š” ๋Œ€์ฒด๋กœ ๊ทธ ์ž์ฒด๋กœ๋„ ๋ฌธ์„œ ์—ญํ• ์„ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

Bad:

function hashIt(data) {
  // The hash
  let hash = 0;

  // Length of string
  const length = data.length;

  // Loop through every character in data
  for (let i = 0; i < length; i++) {
    // Get character code.
    const char = data.charCodeAt(i);
    // Make the hash
    hash = (hash << 5) - hash + char;
    // Convert to 32-bit integer
    hash &= hash;
  }
}

Good:

function hashIt(data) {
  let hash = 0;
  const length = data.length;

  for (let i = 0; i < length; i++) {
    const char = data.charCodeAt(i);
    hash = (hash << 5) - hash + char;

    // Convert to 32-bit integer
    hash &= hash;
  }
}

โฌ† back to top

Don't leave commented out code in your codebase

์ฃผ์„์ฒ˜๋ฆฌ๋œ ์ฝ”๋“œ๋ฅผ ์ง€์šธ ๊ฒƒ

๋ฒ„์ „ ๊ด€๋ฆฌ ๋„๊ตฌ๊ฐ€ ํžˆ์Šคํ† ๋ฆฌ์— ์ฝ”๋“œ๋ฅผ ๋‚จ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์„์ฒ˜๋ฆฌ๋œ ์ฝ”๋“œ๋ฅผ ๋‚จ๊ธธ ์ด์œ ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

Bad:

doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();

Good:

doStuff();

โฌ† back to top

Don't have journal comments

์ฃผ์„์œผ๋กœ ๊ธฐ๋ก์„ ๋‚จ๊ธฐ์ง€ ๋ง ๊ฒƒ

๋ฒ„์ „ ๊ด€๋ฆฌ ๋„๊ตฌ๋ฅผ ์ด์šฉํ•˜์„ธ์š”! ์ฃฝ์€ ์ฝ”๋“œ, ์ฃผ์„์ฒ˜๋ฆฌ๋œ ์ฝ”๋“œ, ๊ทธ๋ฆฌ๊ณ  ํŠนํžˆ ์ฝ”๋“œ์— ๋Œ€ํ•œ ๊ธฐ๋ก์„ ๋‚จ๊ธธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ ๊ธฐ๋ก์„ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด git log๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”!

Bad:

/**
 * 2016-12-20: Removed monads, didn't understand them (RM)
 * 2016-10-01: Improved using special monads (JP)
 * 2016-02-03: Removed type-checking (LI)
 * 2015-03-14: Added combine with type-checking (JR)
 */
function combine(a, b) {
  return a + b;
}

Good:

function combine(a, b) {
  return a + b;
}

โฌ† back to top

Avoid positional markers

์œ„์น˜ ์กฐ์ ˆ์šฉ ์ฃผ์„์„ ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ

์ด๋“ค์€ ๋Œ€๋ถ€๋ถ„ ์ฝ”๋“œ๋ฅผ ์–ด์ง€๋Ÿฝ๊ฒŒ ํ•  ๋ฟ์ž…๋‹ˆ๋‹ค. ์ ์ ˆํ•œ ๋“ค์—ฌ์“ฐ๊ธฐ์™€ ํฌ๋งคํŒ…์„ ์ ์šฉํ•œ ํ•จ์ˆ˜์™€ ๋ณ€์ˆ˜๋ช…์œผ๋กœ ์‹œ๊ฐ์ ์ธ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“œ์„ธ์š”.

Bad:

////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
$scope.model = {
  menu: "foo",
  nav: "bar"
};

////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
const actions = function() {
  // ...
};

Good:

$scope.model = {
  menu: "foo",
  nav: "bar"
};

const actions = function() {
  // ...
};

โฌ† back to top

Translation

๋ฒˆ์—ญ

๋‹ค๋ฅธ ์–ธ์–ด๋กœ๋„ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

โฌ† back to top

About

๐Ÿ› JavaScript๋ฅผ ์œ„ํ•œ ํด๋ฆฐ ์ฝ”๋“œ

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published