GithubHelp home page GithubHelp logo

devyuns / clean-code-typescript Goto Github PK

View Code? Open in Web Editor NEW

This project forked from 738/clean-code-typescript

0.0 0.0 0.0 1.53 MB

๐Ÿšฟ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์œ„ํ•œ ํด๋ฆฐ์ฝ”๋“œ - ํ•œ๊ธ€ ๋ฒˆ์—ญํŒ ๐Ÿ‡ฐ๐Ÿ‡ท

Home Page: https://738.github.io/clean-code-typescript

License: Other

TypeScript 100.00%

clean-code-typescript's Introduction

clean-code-typescript

Tweet Tweet

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์œ„ํ•œ ํด๋ฆฐ์ฝ”๋“œ

clean-code-javascript์—์„œ ์˜๊ฐ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.

๋ชฉ์ฐจ

  1. ์†Œ๊ฐœ
  2. ๋ณ€์ˆ˜
  3. ํ•จ์ˆ˜
  4. ๊ฐ์ฒด์™€ ์ž๋ฃŒ๊ตฌ์กฐ
  5. ํด๋ž˜์Šค
  6. SOLID
  7. ํ…Œ์ŠคํŠธ
  8. ๋™์‹œ์„ฑ
  9. ์—๋Ÿฌ ์ฒ˜๋ฆฌ
  10. ์„œ์‹
  11. ์ฃผ์„
  12. ๋ฒˆ์—ญ
  13. ๋ฒˆ์—ญ์— ๋„์›€์„ ์ฃผ์‹  ๋ถ„๋“ค

์†Œ๊ฐœ

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

Robert C. Martin์˜ ์ฑ…์ธ ํด๋ฆฐ ์ฝ”๋“œ์— ์žˆ๋Š” ์†Œํ”„ํŠธ์›จ์–ด ๊ณตํ•™ ๋ฐฉ๋ฒ•๋ก ์„ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์— ์ ์šฉํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค. ์ด ๊ธ€์€ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. ์ด ๊ธ€์€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ ์ฝ๊ธฐ ์‰ฝ๊ณ , ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ฆฌํŒฉํ† ๋ง ๊ฐ€๋Šฅํ•œ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ€์ด๋“œ์ž…๋‹ˆ๋‹ค.

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

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

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋ณ€์ˆ˜

์˜๋ฏธ์žˆ๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์„ธ์š”

์ฝ๋Š” ์‚ฌ๋žŒ์œผ๋กœ ํ•˜์—ฌ๊ธˆ ๋ณ€์ˆ˜๋งˆ๋‹ค ์–ด๋–ค ์ ์ด ๋‹ค๋ฅธ์ง€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก ์ด๋ฆ„์„ ๊ตฌ๋ณ„ํ•˜์„ธ์š”.

Bad:

function between<T>(a1: T, a2: T, a3: T): boolean {
  return a2 <= a1 && a1 <= a3;
}

Good:

function between<T>(value: T, left: T, right: T): boolean {
  return left <= value && value <= right;
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋ฐœ์Œํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์„ธ์š”

๋ฐœ์Œํ•  ์ˆ˜ ์—†๋Š” ์ด๋ฆ„์€ ๊ทธ ๋ณ€์ˆ˜์— ๋Œ€ํ•ด์„œ ๋ฐ”๋ณด ๊ฐ™์ด ์†Œ๋ฆฌ๋ฅผ ๋‚ด ํ† ๋ก ํ•  ์ˆ˜๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค.

Bad:

type DtaRcrd102 = {
  genymdhms: Date;
  modymdhms: Date;
  pszqint: number;
}

Good:

type Customer = {
  generationTimestamp: Date;
  modificationTimestamp: Date;
  recordId: number;
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋™์ผํ•œ ์œ ํ˜•์˜ ๋ณ€์ˆ˜๋Š” ๋™์ผํ•œ ๋‹จ์–ด๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

Bad:

function getUserInfo(): User;
function getUserDetails(): User;
function getUserData(): User;

Good:

function getUser(): User;

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

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

์ฝ”๋“œ๋ฅผ ์“ธ ๋•Œ๋ณด๋‹ค ์ฝ์„ ๋•Œ๊ฐ€ ๋” ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๊ฐ€ ์“ฐ๋Š” ์ฝ”๋“œ๋Š” ์ฝ์„ ์ˆ˜ ์žˆ๊ณ  ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋žจ์„ ์ดํ•ดํ•  ๋•Œ ์˜๋ฏธ์žˆ๋Š” ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์ง“์ง€ ์•Š์œผ๋ฉด ์ฝ๋Š” ์‚ฌ๋žŒ์œผ๋กœ ํ•˜์—ฌ๊ธˆ ์–ด๋ ค์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์„ ์ง€์œผ์„ธ์š”. TSLint์™€ ๊ฐ™์€ ๋„๊ตฌ๋Š” ์ด๋ฆ„์ด ์—†๋Š” ์ƒ์ˆ˜๋ฅผ ์‹๋ณ„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

Bad:

// 86400000์ด ๋„๋Œ€์ฒด ๋ญ์ง€?
setTimeout(restart, 86400000);

Good:

// ๋Œ€๋ฌธ์ž๋กœ ์ด๋ฃจ์–ด์ง„ ์ƒ์ˆ˜๋กœ ์„ ์–ธํ•˜์„ธ์š”.
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000;

setTimeout(restart, MILLISECONDS_IN_A_DAY);

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์˜๋„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

Bad:

declare const users: Map<string, User>;

for (const keyValue of users) {
  // users ๋งต์„ ์ˆœํšŒ
}

Good:

declare const users: Map<string, User>;

for (const [id, user] of users) {
  // users ๋งต์„ ์ˆœํšŒ
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์•”์‹œํ•˜๋Š” ์ด๋ฆ„์€ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”

๋ช…์‹œ์ ์ธ ๊ฒƒ์ด ์•”์‹œ์ ์ธ ๊ฒƒ๋ณด๋‹ค ์ข‹์Šต๋‹ˆ๋‹ค.
๋ช…๋ฃŒํ•จ์€ ์ตœ๊ณ ์ž…๋‹ˆ๋‹ค.

Bad:

const u = getUser();
const s = getSubscription();
const t = charge(u, s);

Good:

const user = getUser();
const subscription = getSubscription();
const transaction = charge(user, subscription);

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋ถˆํ•„์š”ํ•œ ๋ฌธ๋งฅ์€ ์ถ”๊ฐ€ํ•˜์ง€ ๋งˆ์„ธ์š”

ํด๋ž˜์Šค/ํƒ€์ž…/๊ฐ์ฒด์˜ ์ด๋ฆ„์— ์˜๋ฏธ๊ฐ€ ๋‹ด๊ฒจ์žˆ๋‹ค๋ฉด, ๋ณ€์ˆ˜ ์ด๋ฆ„์—์„œ ๋ฐ˜๋ณตํ•˜์ง€ ๋งˆ์„ธ์š”.

Bad:

type Car = {
  carMake: string;
  carModel: string;
  carColor: string;
}

function print(car: Car): void {
  console.log(`${car.carMake} ${car.carModel} (${car.carColor})`);
}

Good:

type Car = {
  make: string;
  model: string;
  color: string;
}

function print(car: Car): void {
  console.log(`${car.make} ${car.model} (${car.color})`);
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

short circuiting์ด๋‚˜ ์กฐ๊ฑด๋ฌธ ๋Œ€์‹  ๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” short circuiting๋ณด๋‹ค ๋ณดํ†ต ๋ช…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

Bad:

function loadPages(count?: number) {
  const loadCount = count !== undefined ? count : 10;
  // ...
}

Good:

function loadPages(count: number = 10) {
  // ...
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์˜๋„๋ฅผ ์•Œ๋ ค์ฃผ๊ธฐ ์œ„ํ•ด enum์„ ์‚ฌ์šฉํ•˜์„ธ์š”

์˜ˆ๋ฅผ ๋“ค์–ด ๊ทธ๊ฒƒ๋“ค์˜ ๊ฐ’ ์ž์ฒด๋ณด๋‹ค ๊ฐ’์ด ๊ตฌ๋ณ„๋˜์–ด์•ผ ํ•  ๋•Œ์™€ ๊ฐ™์ด ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ์•Œ๋ ค์ฃผ๋Š”๋ฐ์— enum์€ ๋„์›€์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

const GENRE = {
  ROMANTIC: 'romantic',
  DRAMA: 'drama',
  COMEDY: 'comedy',
  DOCUMENTARY: 'documentary',
}

projector.configureFilm(GENRE.COMEDY);

class Projector {
  // Projector์˜ ์„ ์–ธ
  configureFilm(genre) {
    switch (genre) {
      case GENRE.ROMANTIC:
        // ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋กœ์ง
    }
  }
}

Good:

enum GENRE {
  ROMANTIC,
  DRAMA,
  COMEDY,
  DOCUMENTARY,
}

projector.configureFilm(GENRE.COMEDY);

class Projector {
  // Projector์˜ ์„ ์–ธ
  configureFilm(genre) {
    switch (genre) {
      case GENRE.ROMANTIC:
        // ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š” ๋กœ์ง
    }
  }
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•จ์ˆ˜

ํ•จ์ˆ˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” 2๊ฐœ ํ˜น์€ ๊ทธ ์ดํ•˜๊ฐ€ ์ด์ƒ์ ์ž…๋‹ˆ๋‹ค

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

ํ•œ ๊ฐœ ํ˜น์€ ๋‘ ๊ฐœ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์ด์ƒ์ ์ธ ๊ฒฝ์šฐ๊ณ , ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ์„ธ ๊ฐœ๋Š” ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ด์ƒ์˜ ๊ฒฝ์šฐ์—๋Š” ํ•ฉ์ณ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‘ ๊ฐœ ์ด์ƒ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์งˆ ๊ฒฝ์šฐ, ํ•จ์ˆ˜๊ฐ€ ๋งŽ์€ ๊ฒƒ์„ ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ, ๋Œ€๋ถ€๋ถ„ ์ƒ์œ„ ๊ฐ์ฒด๋Š” ํ•˜๋‚˜์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์ถฉ๋ถ„ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋งŽ์€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋ฉด ๊ฐ์ฒด ๋ฆฌํ„ฐ๋Ÿด์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”.

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

  1. ์–ด๋–ค ์‚ฌ๋žŒ์ด ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ณ(๋งค๊ฐœ๋ณ€์ˆ˜์˜ ํƒ€์ž…, ๋ฐ˜ํ™˜๊ฐ’์˜ ํƒ€์ž… ๋“ฑ)๋ฅผ ๋ณผ ๋•Œ, ์–ด๋–ค ์†์„ฑ์ด ์‚ฌ์šฉ๋˜๋Š”์ง€ ์ฆ‰์‹œ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  2. ๋ช…๋ช…๋œ ๋งค๊ฐœ๋ณ€์ˆ˜์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  3. ๋˜ํ•œ ๊ตฌ์กฐ ๋ถ„ํ•ด๋Š” ํ•จ์ˆ˜๋กœ ์ „๋‹ฌ๋œ ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ์ฒด์˜ ํŠน์ •ํ•œ ์›์‹œ ๊ฐ’์„ ๋ณต์ œํ•˜๋ฉฐ ์ด๊ฒƒ์€ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ๋ฐฉ์ง€ํ•˜๋Š”๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค. ์œ ์˜์‚ฌํ•ญ: ๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ์ฒด๋กœ๋ถ€ํ„ฐ ๊ตฌ์กฐ ๋ถ„ํ•ด๋œ ๊ฐ์ฒด์™€ ๋ฐฐ์—ด์€ ๋ณต์ œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  4. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์†์„ฑ์— ๋Œ€ํ•ด์„œ ๊ฒฝ๊ณ ๋ฅผ ์ฃผ๋ฉฐ, ๊ตฌ์กฐ ๋ถ„ํ•ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

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

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

Good:

function createMenu(options: { title: string, body: string, buttonText: string, cancellable: boolean }) {
  // ...
}

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

ํƒ€์ž… ์•จ๋ฆฌ์–ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ€๋…์„ฑ์„ ๋” ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

type MenuOptions = { title: string, body: string, buttonText: string, cancellable: boolean };

function createMenu(options: MenuOptions) {
  // ...
}

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•จ์ˆ˜๋Š” ํ•œ ๊ฐ€์ง€๋งŒ ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค

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

Bad:

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

Good:

function emailClients(clients: Client[]) {
  clients.filter(isActiveClient).forEach(email);
}

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•จ์ˆ˜๊ฐ€ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋„๋ก ํ•จ์ˆ˜ ์ด๋ฆ„์„ ์ง€์œผ์„ธ์š”

Bad:

function addToDate(date: Date, month: number): Date {
  // ...
}

const date = new Date();

// ๋ฌด์—‡์ด ์ถ”๊ฐ€๋˜๋Š”์ง€ ํ•จ์ˆ˜ ์ด๋ฆ„๋งŒ์œผ๋กœ ์œ ์ถ”ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค
addToDate(date, 1);

Good:

function addMonthToDate(date: Date, month: number): Date {
  // ...
}

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•จ์ˆ˜๋Š” ๋‹จ์ผ ํ–‰๋™์„ ์ถ”์ƒํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

ํ•จ์ˆ˜๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ด์ƒ์„ ์ถ”์ƒํ™”ํ•œ๋‹ค๋ฉด ๊ทธ ํ•จ์ˆ˜๋Š” ๋„ˆ๋ฌด ๋งŽ์€ ์ผ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์‰ฌ์šด ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด์„œ ํ•จ์ˆ˜๋ฅผ ์ชผ๊ฐœ์„ธ์š”.

Bad:

function parseCode(code: string) {
  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:

const REGEXES = [ /* ... */ ];

function parseCode(code: string) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);

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

function tokenize(code: string): Token[] {
  const statements = code.split(' ');
  const tokens: Token[] = [];

  REGEXES.forEach((regex) => {
    statements.forEach((statement) => {
      tokens.push( /* ... */ );
    });
  });

  return tokens;
}

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

  return syntaxTree;
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์ค‘๋ณต๋œ ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•ด์ฃผ์„ธ์š”

์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜์ง€ ์•Š๋„๋ก ์ตœ์„ ์„ ๋‹คํ•˜์„ธ์š”. ์ค‘๋ณต๋œ ์ฝ”๋“œ๋Š” ์–ด๋–ค ๋กœ์ง์„ ๋ณ€๊ฒฝํ•  ๋•Œ ํ•œ ๊ณณ ์ด์ƒ์„ ๋ณ€๊ฒฝํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

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

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

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

Bad:

function showDeveloperList(developers: Developer[]) {
  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: Manager[]) {
  managers.forEach((manager) => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();

    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    render(data);
  });
}

Good:

class Developer {
  // ...
  getExtraDetails() {
    return {
      githubLink: this.githubLink,
    }
  }
}

class Manager {
  // ...
  getExtraDetails() {
    return {
      portfolio: this.portfolio,
    }
  }
}

function showEmployeeList(employee: Developer | Manager) {
  employee.forEach((employee) => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();
    const extra = employee.getExtraDetails();

    const data = {
      expectedSalary,
      experience,
      extra,
    };

    render(data);
  });
}

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

Object.assign ํ˜น์€ ๊ตฌ์กฐ ๋ถ„ํ•ด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ธฐ๋ณธ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“œ์„ธ์š”

Bad:

type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

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

  // ...
}

createMenu({ body: 'Bar' });

Good:

type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

function createMenu(config: MenuConfig) {
  const menuConfig = Object.assign({
    title: 'Foo',
    body: 'Bar',
    buttonText: 'Baz',
    cancellable: true
  }, config);

  // ...
}

createMenu({ body: 'Bar' });

๋Œ€์•ˆ์œผ๋กœ, ๊ธฐ๋ณธ ๊ฐ’์„ ๊ตฌ์กฐ ๋ถ„ํ•ด๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

type MenuConfig = { title?: string, body?: string, buttonText?: string, cancellable?: boolean };

function createMenu({ title = 'Foo', body = 'Bar', buttonText = 'Baz', cancellable = true }: MenuConfig) {
  // ...
}

createMenu({ body: 'Bar' });

์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ์™€ undefined ํ˜น์€ null ๊ฐ’์„ ๋ช…์‹œ์ ์œผ๋กœ ๋„˜๊ธฐ๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ํ–‰๋™์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด์„œ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ปดํŒŒ์ผ๋Ÿฌ์—๊ฒŒ ๊ทธ๊ฒƒ์„ ํ—ˆ๋ฝํ•˜์ง€ ์•Š๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ --strictNullChecks ์˜ต์…˜์„ ํ™•์ธํ•˜์„ธ์š”.

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”

ํ”Œ๋ž˜๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ด์ƒ์˜ ์ผ์„ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ๋œปํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜๋Š” ํ•œ ๊ฐ€์ง€์˜ ์ผ์„ ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. boolean ๋ณ€์ˆ˜๋กœ ์ธํ•ด ๋‹ค๋ฅธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ๋‹ค๋ฉด ๊ทธ ํ•จ์ˆ˜๋ฅผ ์ชผ๊ฐœ๋„๋ก ํ•˜์„ธ์š”.

Bad:

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

Good:

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

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ํ”ผํ•˜์„ธ์š” (ํŒŒํŠธ 1)

ํ•จ์ˆ˜๋Š” ๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ ๋‹ค๋ฅธ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ ์ด์™ธ์— ๋‹ค๋ฅธ ๊ฒƒ์„ ํ•  ๊ฒฝ์šฐ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋Š” ํŒŒ์ผ์„ ์“ด๋‹ค๊ฑฐ๋‚˜, ์ „์—ญ ๋ณ€์ˆ˜๋ฅผ ์กฐ์ž‘ํ•œ๋‹ค๊ฑฐ๋‚˜, ๋œปํ•˜์ง€ ์•Š๊ฒŒ ๋‚ฏ์„  ์‚ฌ๋žŒ์—๊ฒŒ ๋‹น์‹ ์˜ ์ „์žฌ์‚ฐ์„ ์†ก๊ธˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹น์‹ ์€ ๊ฐ€๋” ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ๊ฐ€์งˆ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ „์˜ ์‚ฌ๋ก€์—์„œ์™€ ๊ฐ™์ด ๋‹น์‹ ์€ ํŒŒ์ผ์„ ์จ์•ผํ•  ๋•Œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์ด ํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์€ ์ด๊ฒƒ์„ ์ค‘์•™ํ™”ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํŠน์ • ํŒŒ์ผ์„ ์“ฐ๊ธฐ ์œ„ํ•ด ๋ช‡ ๊ฐœ์˜ ํ•จ์ˆ˜์™€ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์ง€ ๋งˆ์„ธ์š”. ๊ทธ๊ฒƒ์„ ํ–‰ํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ๋‹จ ํ•˜๋‚˜๋งŒ ๋งŒ๋“œ์„ธ์š”.

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

Bad:

// ์•„๋ž˜์˜ ํ•จ์ˆ˜์—์„œ ์ฐธ์กฐํ•˜๋Š” ์ „์—ญ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค.
let name = 'Robert C. Martin';

function toBase64() {
  name = btoa(name);
}

toBase64();
// ์ด ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ํ•จ์ˆ˜๊ฐ€ ์žˆ๋‹ค๋ฉด, ๊ทธ๊ฒƒ์€ Base64 ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค

console.log(name); // 'Robert C. Martin'์ด ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ์˜ˆ์ƒํ–ˆ์ง€๋งŒ 'Um9iZXJ0IEMuIE1hcnRpbg=='๊ฐ€ ์ถœ๋ ฅ๋จ

Good:

const name = 'Robert C. Martin';

function toBase64(text: string): string {
  return btoa(text);
}

const encodedName = toBase64(name);
console.log(name);

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๋ฅผ ํ”ผํ•˜์„ธ์š” (ํŒŒํŠธ 2)

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

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

ํ›Œ๋ฅญํ•œ ํ•ด๋ฒ•์€ addItemToCart ํ•จ์ˆ˜์—์„œ cart ๋ฐฐ์—ด์„ ๋ณต์ œํ•˜๊ณ  ๊ทธ๊ฒƒ์„ ์ˆ˜์ •ํ•˜๊ณ  ๊ทธ ๋ณต์ œํ•œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์‡ผํ•‘ ์žฅ๋ฐ”๊ตฌ๋‹ˆ ๋ฐฐ์—ด์„ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋Š” ๊ฐ’์„ ๋“ค๊ณ  ์žˆ๋Š” ์–ด๋–ค ๋‹ค๋ฅธ ํ•จ์ˆ˜๋„ ๋‹ค๋ฅธ ๋ณ€๊ฒฝ์— ์˜ํ•ด ์˜ํ–ฅ์„ ๋ฐ›์ง€ ์•Š๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

์ด ์ ‘๊ทผ๋ฒ•์— ๋Œ€ํ•œ ๋‘ ๊ฐ€์ง€ ์ฃผ์˜์‚ฌํ•ญ:

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

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

Bad:

function addItemToCart(cart: CartItem[], item: Item): void {
  cart.push({ item, date: Date.now() });
};

Good:

function addItemToCart(cart: CartItem[], item: Item): CartItem[] {
  return [...cart, { item, date: Date.now() }];
};

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์ „์—ญ ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜์ง€ ๋งˆ์„ธ์š”

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

Bad:

declare global {
  interface Array<T> {
    diff(other: T[]): Array<T>;
  }
}

if (!Array.prototype.diff) {
  Array.prototype.diff = function <T>(other: T[]): T[] {
    const hash = new Set(other);
    return this.filter(elem => !hash.has(elem));
  };
}

Good:

class MyArray<T> extends Array<T> {
  diff(other: T[]): T[] {
    const hash = new Set(other);
    return this.filter(elem => !hash.has(elem));
  };
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

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

๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฐฉ์‹์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์ง€ํ–ฅํ•˜์„ธ์š”.

Bad:

const contributions = [
  {
    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 < contributions.length; i++) {
  totalOutput += contributions[i].linesOfCode;
}

Good:

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

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์กฐ๊ฑด๋ฌธ์„ ์บก์Šํ™”ํ•˜์„ธ์š”

Bad:

if (subscription.isTrial || account.balance > 0) {
  // ...
}

Good:

function canActivateService(subscription: Subscription, account: Account) {
  return subscription.isTrial || account.balance > 0;
}

if (canActivateService(subscription, account)) {
  // ...
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋ถ€์ • ์กฐ๊ฑด๋ฌธ์„ ํ”ผํ•˜์„ธ์š”

Bad:

function isEmailNotUsed(email: string): boolean {
  // ...
}

if (isEmailNotUsed(email)) {
  // ...
}

Good:

function isEmailUsed(email): boolean {
  // ...
}

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์กฐ๊ฑด๋ฌธ์„ ํ”ผํ•˜์„ธ์š”

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

Bad:

class Airplane {
  private type: string;
  // ...

  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();
      default:
        throw new Error('Unknown airplane type.');
    }
  }

  private getMaxAltitude(): number {
    // ...
  }
}

Good:

abstract class Airplane {
  protected getMaxAltitude(): number {
    // shared logic with subclasses ...
  }

  // ...
}

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();
  }
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํƒ€์ž… ์ฒดํ‚น์„ ํ”ผํ•˜์„ธ์š”

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

Bad:

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

Good:

type Vehicle = Bicycle | Car;

function travelToTexas(vehicle: Vehicle) {
  vehicle.move(currentLocation, new Location('texas'));
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•„์š” ์ด์ƒ์œผ๋กœ ์ตœ์ ํ™”ํ•˜์ง€ ๋งˆ์„ธ์š”

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

Bad:

// ์˜ˆ์ „ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์บ์‹œ๋˜์ง€ ์•Š์€ `list.length`๋ฅผ ์‚ฌ์šฉํ•œ ๊ฐ ์ˆœํšŒ๋Š” ๋น„์šฉ์ด ๋งŽ์ด ๋“ค ๊ฒƒ์ž…๋‹ˆ๋‹ค.
// `list.length`์˜ ์žฌ๊ณ„์‚ฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ํ˜„๋Œ€ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋Š” ์ด ๋ถ€๋ถ„์ด ์ตœ์ ํ™”๋ฉ๋‹ˆ๋‹ค.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

Good:

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•„์š”ํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ๋Š” ์ œ๊ฑฐํ•˜์„ธ์š”

์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ๋Š” ์ค‘๋ณต๋œ ์ฝ”๋“œ๋งŒํผ ๋‚˜์ฉ๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์ฝ”๋“œ์—์„œ ์ด๊ฒƒ์„ ์œ ์ง€ํ•  ์ด์œ ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํ˜ธ์ถœ๋˜์ง€ ์•Š์€ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ œ๊ฑฐํ•˜์„ธ์š”! ์ง€์šด ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ฒ„์ „ ๊ธฐ๋ก์—์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

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

function requestModule(url: string) {
  // ...
}

const req = requestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

Good:

function requestModule(url: string) {
  // ...
}

const req = requestModule;
inventoryTracker('apples', req, 'www.inventory-awesome.io');

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

iterator์™€ generator๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

์ŠคํŠธ๋ฆผ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ ์ฝœ๋ ‰์…˜์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” generator์™€ iterable์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋ช‡ ๊ฐ€์ง€์˜ ์ข‹์€ ์ด์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

  • ํ”ผํ˜ธ์ถœ์ž๊ฐ€ ์ ‘๊ทผํ•  ์•„์ดํ…œ ์ˆ˜๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค๋Š” ์˜๋ฏธ์—์„œ ํ”ผํ˜ธ์ถœ์ž๋ฅผ generator ๊ตฌํ˜„์œผ๋กœ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ง€์—ฐ ์‹คํ–‰, ์•„์ดํ…œ์€ ์š”๊ตฌ์— ์˜ํ•ด ์ŠคํŠธ๋ฆผ ์ฒ˜๋ฆฌ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • for-of ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•ด ์•„์ดํ…œ์„ ์ˆœํšŒํ•˜๋Š” ๋‚ด์žฅ ์ง€์›์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • iterable์€ ์ตœ์ ํ™”๋œ iterator ํŒจํ„ด์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

function fibonacci(n: number): number[] {
  if (n === 1) return [0];
  if (n === 2) return [0, 1];

  const items: number[] = [0, 1];
  while (items.length < n) {
    items.push(items[items.length - 2] + items[items.length - 1]);
  }

  return items;
}

function print(n: number) {
  fibonacci(n).forEach(fib => console.log(fib));
}

// ํ”ผ๋ณด๋‚˜์น˜ ์ˆซ์ž์˜ ์ฒซ ๋ฒˆ์งธ 10๊ฐœ ์ˆซ์ž๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
print(10);

Good:

// ํ”ผ๋ณด๋‚˜์น˜ ์ˆซ์ž์˜ ๋ฌดํ•œ ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
// `generator`๋Š” ๋ชจ๋“  ์ˆซ์ž์˜ ๋ฐฐ์—ด์„ ์œ ์ง€ํ•˜๊ณ  ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
function* fibonacci(): IterableIterator<number> {
  let [a, b] = [0, 1];

  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

function print(n: number) {
  let i = 0;
  for (const fib of fibonacci()) {
    if (i++ === n) break;  
    console.log(fib);
  }  
}

// ํ”ผ๋ณด๋‚˜์น˜ ์ˆซ์ž์˜ ์ฒซ ๋ฒˆ์งธ 10๊ฐœ ์ˆซ์ž๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
print(10);

map, slice, forEach ๋“ฑ๊ณผ ๊ฐ™์€ ๋ฉ”์†Œ๋“œ๋ฅผ ์—ฐ๊ฒฐํ•จ์œผ๋กœ์จ ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐฐ์—ด์„ ๋น„์Šทํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ iterable๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. iterable์˜ ๋ฐœ์ „๋œ ์กฐ์ž‘์˜ ์‚ฌ๋ก€๋ฅผ ์œ„ํ•ด itiriri๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”. (๋˜๋Š” ๋น„๋™๊ธฐ iterable์˜ ์กฐ์ž‘์„ ์œ„ํ•ด์„œ itiriri-async๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.)

import itiriri from 'itiriri';

function* fibonacci(): IterableIterator<number> {
  let [a, b] = [0, 1];
 
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

itiriri(fibonacci())
  .take(10)
  .forEach(fib => console.log(fib));

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๊ฐ์ฒด์™€ ์ž๋ฃŒ๊ตฌ์กฐ

getter์™€ setter๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” getter/setter ๊ตฌ๋ฌธ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ํ–‰๋™์„ ์บก์Šํ™”ํ•œ ๊ฐ์ฒด์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด getter์™€ setter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ๊ฐ์ฒด์—์„œ ์†์„ฑ์„ ๋‹จ์ˆœํžˆ ์ฐพ๋Š” ๊ฒƒ๋ณด๋‹ค ๋‚ซ์Šต๋‹ˆ๋‹ค. "์™œ ๊ทธ๋ ‡์Šต๋‹ˆ๊นŒ?" ๋ผ๊ณ  ๋ฌผ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

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

Bad:

type BankAccount = {
  balance: number;
  // ...
}

const value = 100;
const account: BankAccount = {
  balance: 0,
  // ...
};

if (value < 0) {
  throw new Error('Cannot set negative balance.');
}

account.balance = value;

Good:

class BankAccount {
  private accountBalance: number = 0;

  get balance(): number {
    return this.accountBalance;
  }

  set balance(value: number) {
    if (value < 0) {
      throw new Error('Cannot set negative balance.');
    }

    this.accountBalance = value;
  }

  // ...
}

// ์ด์ œ `BankAccount`๋Š” ๊ฒ€์ฆ ๋กœ์ง์„ ์บก์Šํ™”ํ•ฉ๋‹ˆ๋‹ค.
// ๋ช…์„ธ๊ฐ€ ๋ฐ”๋€๋‹ค๋ฉด, ์ถ”๊ฐ€์ ์ธ ๊ฒ€์ฆ ๊ทœ์น™์„ ์ถ”๊ฐ€ํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
// ๊ทธ ๋•Œ, `setter` ๊ตฌํ˜„๋ถ€๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
// ๊ด€๋ จ์žˆ๋Š” ๋‹ค๋ฅธ ์ฝ”๋“œ๋Š” ๋ณ€๊ฒฝํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
const account = new BankAccount();
account.balance = 100;

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

private/protected ๋ฉค๋ฒ„๋ฅผ ๊ฐ–๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ํด๋ž˜์Šค ๋ฉค๋ฒ„๋ฅผ ์œ„ํ•ด public ๊ธฐ๋ณธ, protected, private ์ ‘๊ทผ์ž๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

Bad:

class Circle {
  radius: number;
  
  constructor(radius: number) {
    this.radius = radius;
  }

  perimeter() {
    return 2 * Math.PI * this.radius;
  }

  surface() {
    return Math.PI * this.radius * this.radius;
  }
}

Good:

class Circle {
  constructor(private readonly radius: number) {
  }

  perimeter() {
    return 2 * Math.PI * this.radius;
  }

  surface() {
    return Math.PI * this.radius * this.radius;
  }
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋ถˆ๋ณ€์„ฑ์„ ์„ ํ˜ธํ•˜์„ธ์š”

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํƒ€์ž… ์‹œ์Šคํ…œ์€ interface/class์˜ ๊ฐœ๋ณ„ ์†์„ฑ์„ readonly๋กœ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๊ธฐ๋Šฅ์ ์ธ ๋ฐฉ์‹์œผ๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ์ƒํ•˜์ง€ ์•Š์€ ๋ณ€์กฐ๋Š” ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.)
๋”์šฑ ๋‚˜์€ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ํƒ€์ž… T๋ฅผ ๊ฐ–๊ณ  mapped types๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ๊ฐ ์†์„ฑ์„ ์ฝ๊ธฐ ์ „์šฉ์œผ๋กœ ํ‘œ์‹œํ•˜๋Š” Readonly ๋‚ด์žฅ ํƒ€์ž…์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. (mapped types๋ฅผ ํ™•์ธํ•˜์„ธ์š”.)

Bad:

interface Config {
  host: string;
  port: string;
  db: string;
}

Good:

interface Config {
  readonly host: string;
  readonly port: string;
  readonly db: string;
}

๋ฐฐ์—ด์˜ ๊ฒฝ์šฐ, ReadonlyArray<T>๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ฝ๊ธฐ ์ „์šฉ์˜ ๋ฐฐ์—ด์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ push()์™€ fill()๊ณผ ๊ฐ™์€ ๋ณ€๊ฒฝ์„ ๋ง‰์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ’ ์ž์ฒด๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๋Š” concat(), slice()๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ์€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Bad:

const array: number[] = [ 1, 3, 5 ];
array = []; // ์—๋Ÿฌ
array.push(100); // ๋ฐฐ์—ด์€ ๋ณ€๊ฒฝ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Good:

const array: ReadonlyArray<number> = [ 1, 3, 5 ];
array = []; // ์—๋Ÿฌ
array.push(100); // ์—๋Ÿฌ

TypeScript 3.4 is a bit easier์—์„œ ์ฝ๊ธฐ ์ „์šฉ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function hoge(args: readonly string[]) {
  args.push(1); // ์—๋Ÿฌ
}

๋ฆฌํ„ฐ๋Ÿด ๊ฐ’์„ ์œ„ํ•ด const assertions๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

Bad:

const config = {
  hello: 'world'
};
config.hello = 'world'; // ๊ฐ’์ด ๋ฐ”๋€๋‹ˆ๋‹ค

const array  = [ 1, 3, 5 ];
array[0] = 10; // ๊ฐ’์ด ๋ฐ”๋€๋‹ˆ๋‹ค

// ์“ธ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค
function readonlyData(value: number) {
  return { value };
}

const result = readonlyData(100);
result.value = 200; // ๊ฐ’์ด ๋ฐ”๋€๋‹ˆ๋‹ค

Good:

// ์ฝ๊ธฐ ์ „์šฉ ๊ฐ์ฒด
const config = {
  hello: 'world'
} as const;
config.hello = 'world'; // ์—๋Ÿฌ

// ์ฝ๊ธฐ ์ „์šฉ ๋ฐฐ์—ด
const array  = [ 1, 3, 5 ] as const;
array[0] = 10; // ์—๋Ÿฌ

// ์ฝ๊ธฐ ์ „์šฉ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
function readonlyData(value: number) {
  return { value } as const;
}

const result = readonlyData(100);
result.value = 200; // ์—๋Ÿฌ

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํƒ€์ž… vs ์ธํ„ฐํŽ˜์ด์Šค

ํ•ฉ์ง‘ํ•ฉ ๋˜๋Š” ๊ต์ง‘ํ•ฉ์ด ํ•„์š”ํ•  ๋•Œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์„ธ์š”. extends ๋˜๋Š” implements๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์—„๊ฒฉํ•œ ๊ทœ์น™์€ ์—†์ง€๋งŒ ๋‹น์‹ ์—๊ฒŒ ๋งž๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ type๊ณผ interface ์‚ฌ์ด์˜ ๋‹ค๋ฅธ ์ ์— ๋Œ€ํ•ด์„œ ๋” ์ƒ์„ธํ•œ ์„ค๋ช…์„ ์›ํ•œ๋‹ค๋ฉด ์ด ๋‹ต๋ณ€์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

Bad:

interface EmailConfig {
  // ...
}

interface DbConfig {
  // ...
}

interface Config {
  // ...
}

//...

type Shape = {
  // ...
}

Good:

type EmailConfig = {
  // ...
}

type DbConfig = {
  // ...
}

type Config  = EmailConfig | DbConfig;

// ...

interface Shape {
  // ...
}

class Circle implements Shape {
  // ...
}

class Square implements Shape {
  // ...
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํด๋ž˜์Šค

ํด๋ž˜์Šค๋Š” ์ž‘์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค

ํด๋ž˜์Šค์˜ ํฌ๊ธฐ๋Š” ์ฑ…์ž„์— ์˜ํ•ด ์ธก์ •๋ฉ๋‹ˆ๋‹ค. ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์— ๋”ฐ๋ฅด๋ฉด ํด๋ž˜์Šค๋Š” ์ž‘์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Bad:

class Dashboard {
  getLanguage(): string { /* ... */ }
  setLanguage(language: string): void { /* ... */ }
  showProgress(): void { /* ... */ }
  hideProgress(): void { /* ... */ }
  isDirty(): boolean { /* ... */ }
  disable(): void { /* ... */ }
  enable(): void { /* ... */ }
  addSubscription(subscription: Subscription): void { /* ... */ }
  removeSubscription(subscription: Subscription): void { /* ... */ }
  addUser(user: User): void { /* ... */ }
  removeUser(user: User): void { /* ... */ }
  goToHomePage(): void { /* ... */ }
  updateProfile(details: UserDetails): void { /* ... */ }
  getVersion(): string { /* ... */ }
  // ...
}

Good:

class Dashboard {
  disable(): void { /* ... */ }
  enable(): void { /* ... */ }
  getVersion(): string { /* ... */ }
}

// ๋‹ค๋ฅธ ํด๋ž˜์Šค์— ๋‚จ์€ ๋ฉ”์†Œ๋“œ๋ฅผ ์ด๋™์‹œํ‚ด์œผ๋กœ์จ ์ฑ…์ž„์„ ๋ถ„์‚ฐ์‹œํ‚ค์„ธ์š”
// ...

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋†’์€ ์‘์ง‘๋„์™€ ๋‚ฎ์€ ๊ฒฐํ•ฉ๋„

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

๊ฒฐํ•ฉ๋„๋Š” ๋‘ ํด๋ž˜์Šค๊ฐ€ ์–ผ๋งˆ๋‚˜ ์„œ๋กœ์—๊ฒŒ ๊ด€๋ จ๋˜์–ด์žˆ๊ฑฐ๋‚˜ ์ข…์†์ ์ธ ์ •๋„๋ฅผ ๋œปํ•ฉ๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ ํด๋ž˜์Šค์˜ ๋ณ€๊ฒฝ์ด ๋‹ค๋ฅธ ํด๋ž˜์Šค์—๊ฒŒ ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ทธ ํด๋ž˜์Šค๋“ค์˜ ๊ฒฐํ•ฉ๋„๋Š” ๋‚ฎ๋‹ค๊ณ  ๋งํ•ฉ๋‹ˆ๋‹ค.

์ข‹์€ ์†Œํ”„ํŠธ์›จ์–ด ์„ค๊ณ„๋Š” ๋†’์€ ์‘์ง‘๋„์™€ ๋‚ฎ์€ ๊ฒฐํ•ฉ๋„๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

Bad:

class UserManager {
  // Bad: ๊ฐ private ๋ณ€์ˆ˜๋Š” ๋ฉ”์†Œ๋“œ์˜ ํ•˜๋‚˜ ํ˜น์€ ๋˜ ๋‹ค๋ฅธ ๊ทธ๋ฃน์— ์˜ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  // ํด๋ž˜์Šค๊ฐ€ ๋‹จ์ผ ์ฑ…์ž„ ์ด์ƒ์˜ ์ฑ…์ž„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋Š” ๋ช…๋ฐฑํ•œ ์ฆ๊ฑฐ์ž…๋‹ˆ๋‹ค.
  // ์‚ฌ์šฉ์ž์˜ ํŠธ๋žœ์žญ์…˜์„ ์–ป๊ธฐ ์œ„ํ•ด ์„œ๋น„์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋Š” ๊ฒฝ์šฐ,
  // ์—ฌ์ „ํžˆ `emailSender` ์ธ์Šคํ„ด์Šค๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  constructor(
    private readonly db: Database,
    private readonly emailSender: EmailSender) {
  }

  async getUser(id: number): Promise<User> {
    return await db.users.findOne({ id });
  }

  async getTransactions(userId: number): Promise<Transaction[]> {
    return await db.transactions.find({ userId });
  }

  async sendGreeting(): Promise<void> {
    await emailSender.send('Welcome!');
  }

  async sendNotification(text: string): Promise<void> {
    await emailSender.send(text);
  }

  async sendNewsletter(): Promise<void> {
    // ...
  }
}

Good:

class UserService {
  constructor(private readonly db: Database) {
  }

  async getUser(id: number): Promise<User> {
    return await this.db.users.findOne({ id });
  }

  async getTransactions(userId: number): Promise<Transaction[]> {
    return await this.db.transactions.find({ userId });
  }
}

class UserNotifier {
  constructor(private readonly emailSender: EmailSender) {
  }

  async sendGreeting(): Promise<void> {
    await this.emailSender.send('Welcome!');
  }

  async sendNotification(text: string): Promise<void> {
    await this.emailSender.send(text);
  }

  async sendNewsletter(): Promise<void> {
    // ...
  }
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์ƒ์†(inheritance)๋ณด๋‹ค ์กฐํ•ฉ(composition)์„ ์‚ฌ์šฉํ•˜์„ธ์š”

Gang of Four์˜ ๋””์ž์ธ ํŒจํ„ด์— ๋‚˜์™€์žˆ๋“ฏ์ด ํ•  ์ˆ˜ ์žˆ๋Š” ๋Œ€๋กœ ์ƒ์†๋ณด๋‹ค ์กฐํ•ฉ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ƒ์†๊ณผ ์กฐํ•ฉ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ข‹์€ ์ด์œ ๋“ค์€ ๊ฐ๊ฐ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ด ๊ตํ›ˆ์—์„œ ์ค‘์š”ํ•œ ์ ์€ ๋‹น์‹ ์˜ ๋งˆ์Œ์ด ๋ณธ๋Šฅ์ ์œผ๋กœ ์ƒ์†์„ ์ถ”๊ตฌํ•œ๋‹ค๋ฉด, ์กฐํ•ฉ์ด ๋‹น์‹ ์˜ ๋ฌธ์ œ๋ฅผ ๋” ์ข‹๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผํ•ด๋ณด๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์–ด๋–ค ๊ฒฝ์šฐ์—๋Š” ๋” ์ข‹์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹น์‹ ์€ "์–ธ์ œ ์ƒ์†์„ ์‚ฌ์šฉํ•ด์•ผ ํ• ๊นŒ์š”?" ๋ผ๊ณ  ์˜๋ฌธ์ ์„ ๊ฐ€์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋‹น๋ฉดํ•œ ๋ฌธ์ œ์— ๋‹ฌ๋ ค ์žˆ์Šต๋‹ˆ๋‹ค. ์กฐํ•ฉ๋ณด๋‹ค ์ƒ์†์ด ๋” ์ข‹์€ ๊ฒฝ์šฐ๊ฐ€ ์•„๋ž˜์— ์žˆ์Šต๋‹ˆ๋‹ค:

  1. "has-a" ๊ด€๊ณ„๊ฐ€ ์•„๋‹Œ "is-a" ๊ด€๊ณ„์ผ ๋•Œ (์‚ฌ๋žŒ->๋™๋ฌผ vs ์‚ฌ์šฉ์ž->์‚ฌ์šฉ์ž ์ •๋ณด)

  2. ๊ธฐ๋ฐ˜์ด ๋˜๋Š” ํด๋ž˜์Šค๋กœ๋ถ€ํ„ฐ ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๋•Œ (์‚ฌ๋žŒ์€ ๋ชจ๋“  ๋™๋ฌผ์ฒ˜๋Ÿผ ์›€์ง์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.)

  3. ๊ธฐ๋ฐ˜์ด ๋˜๋Š” ํด๋ž˜์Šค๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ํŒŒ์ƒ๋œ ํด๋ž˜์Šค๋ฅผ ์ „์ฒด์ ์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋ ค๋Š” ๊ฒฝ์šฐ (๋ชจ๋“  ๋™๋ฌผ์€ ์›€์ง์ผ ๋•Œ ์นผ๋กœ๋ฆฌ๊ฐ€ ์†Œ๋น„๋ฉ๋‹ˆ๋‹ค.)

Bad:

class Employee {
  constructor(
    private readonly name: string,
    private readonly email: string) {
  }

  // ...
}

// `Employee`๊ฐ€ ์„ธ๊ธˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ธฐ ๋•Œ๋ฌธ์— ๋‚˜์œ ์—์ž…๋‹ˆ๋‹ค. `EmployeeTaxData`๋Š” `Employee`์˜ ํƒ€์ž…์ด ์•„๋‹™๋‹ˆ๋‹ค.
class EmployeeTaxData extends Employee {
  constructor(
    name: string,
    email: string,
    private readonly ssn: string,
    private readonly salary: number) {
    super(name, email);
  }

  // ...
}

Good:

class Employee {
  private taxData: EmployeeTaxData;

  constructor(
    private readonly name: string,
    private readonly email: string) {
  }

  setTaxData(ssn: string, salary: number): Employee {
    this.taxData = new EmployeeTaxData(ssn, salary);
    return this;
  }

  // ...
}

class EmployeeTaxData {
  constructor(
    public readonly ssn: string,
    public readonly salary: number) {
  }

  // ...
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

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

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

Bad:

class QueryBuilder {
  private collection: string;
  private pageNumber: number = 1;
  private itemsPerPage: number = 100;
  private orderByFields: string[] = [];

  from(collection: string): void {
    this.collection = collection;
  }

  page(number: number, itemsPerPage: number = 100): void {
    this.pageNumber = number;
    this.itemsPerPage = itemsPerPage;
  }

  orderBy(...fields: string[]): void {
    this.orderByFields = fields;
  }

  build(): Query {
    // ...
  }
}

// ...

const queryBuilder = new QueryBuilder();
queryBuilder.from('users');
queryBuilder.page(1, 100);
queryBuilder.orderBy('firstName', 'lastName');

const query = queryBuilder.build();

Good:

class QueryBuilder {
  private collection: string;
  private pageNumber: number = 1;
  private itemsPerPage: number = 100;
  private orderByFields: string[] = [];

  from(collection: string): this {
    this.collection = collection;
    return this;
  }

  page(number: number, itemsPerPage: number = 100): this {
    this.pageNumber = number;
    this.itemsPerPage = itemsPerPage;
    return this;
  }

  orderBy(...fields: string[]): this {
    this.orderByFields = fields;
    return this;
  }

  build(): Query {
    // ...
  }
}

// ...

const query = new QueryBuilder()
  .from('users')
  .page(1, 100)
  .orderBy('firstName', 'lastName')
  .build();

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

SOLID

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

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

Bad:

class UserSettings {
  constructor(private readonly user: User) {
  }

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

  verifyCredentials() {
    // ...
  }
}

Good:

class UserAuth {
  constructor(private readonly user: User) {
  }

  verifyCredentials() {
    // ...
  }
}


class UserSettings {
  private readonly auth: UserAuth;

  constructor(private readonly user: User) {
    this.auth = new UserAuth(user);
  }

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

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

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

Bad:

class AjaxAdapter extends Adapter {
  constructor() {
    super();
  }

  // ...
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
  }

  // ...
}

class HttpRequester {
  constructor(private readonly adapter: Adapter) {
  }

  async fetch<T>(url: string): Promise<T> {
    if (this.adapter instanceof AjaxAdapter) {
      const response = await makeAjaxCall<T>(url);
      // response ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    } else if (this.adapter instanceof NodeAdapter) {
      const response = await makeHttpCall<T>(url);
      // response ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    }
  }
}

function makeAjaxCall<T>(url: string): Promise<T> {
  // ์„œ๋ฒ„์— ์š”์ฒญํ•˜๊ณ  ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
}

function makeHttpCall<T>(url: string): Promise<T> {
  // ์„œ๋ฒ„์— ์š”์ฒญํ•˜๊ณ  ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
}

Good:

abstract class Adapter {
  abstract async request<T>(url: string): Promise<T>;

  // ํ•˜์œ„ ํด๋ž˜์Šค์™€ ๊ณต์œ ํ•˜๋Š” ์ฝ”๋“œ ...
}

class AjaxAdapter extends Adapter {
  constructor() {
    super();
  }

  async request<T>(url: string): Promise<T>{
    // ์„œ๋ฒ„์— ์š”์ฒญํ•˜๊ณ  ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  }

  // ...
}

class NodeAdapter extends Adapter {
  constructor() {
    super();
  }

  async request<T>(url: string): Promise<T>{
    // ์„œ๋ฒ„์— ์š”์ฒญํ•˜๊ณ  ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  }

  // ...
}

class HttpRequester {
  constructor(private readonly adapter: Adapter) {
  }

  async fetch<T>(url: string): Promise<T> {
    const response = await this.adapter.request<T>(url);
    // response ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  }
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

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

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

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

Bad:

class Rectangle {
  constructor(
    protected width: number = 0,
    protected height: number = 0) {

  }

  setColor(color: string): this {
    // ...
  }

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

  setWidth(width: number): this {
    this.width = width;
    return this;
  }

  setHeight(height: number): this {
    this.height = height;
    return this;
  }

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

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

  setHeight(height: number): this {
    this.width = height;
    this.height = height;
    return this;
  }
}

function renderLargeRectangles(rectangles: Rectangle[]) {
  rectangles.forEach((rectangle) => {
    const area = rectangle
      .setWidth(4)
      .setHeight(5)
      .getArea(); // BAD: `Square` ํด๋ž˜์Šค์—์„œ๋Š” 25๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. 20์ด ๋ฐ˜ํ™˜๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    rectangle.render(area);
  });
}

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

Good:

abstract class Shape {
  setColor(color: string): this {
    // ...
  }

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

  abstract getArea(): number;
}

class Rectangle extends Shape {
  constructor(
    private readonly width = 0,
    private readonly height = 0) {
    super();
  }

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

class Square extends Shape {
  constructor(private readonly length: number) {
    super();
  }

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

function renderLargeShapes(shapes: Shape[]) {
  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);

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

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

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

Bad:

interface SmartPrinter {
  print();
  fax();
  scan();
}

class AllInOnePrinter implements SmartPrinter {
  print() {
    // ...
  }  
  
  fax() {
    // ...
  }

  scan() {
    // ...
  }
}

class EconomicPrinter implements SmartPrinter {
  print() {
    // ...
  }  
  
  fax() {
    throw new Error('Fax not supported.');
  }

  scan() {
    throw new Error('Scan not supported.');
  }
}

Good:

interface Printer {
  print();
}

interface Fax {
  fax();
}

interface Scanner {
  scan();
}

class AllInOnePrinter implements Printer, Fax, Scanner {
  print() {
    // ...
  }  
  
  fax() {
    // ...
  }

  scan() {
    // ...
  }
}

class EconomicPrinter implements Printer {
  print() {
    // ...
  }
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

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

์ด ์›์น™์€ ๋‘ ๊ฐ€์ง€ ํ•„์ˆ˜์ ์ธ ์‚ฌํ•ญ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค:

  1. ์ƒ์œ„ ๋ ˆ๋ฒจ์˜ ๋ชจ๋“ˆ์€ ํ•˜์œ„ ๋ ˆ๋ฒจ์˜ ๋ชจ๋“ˆ์— ์˜์กดํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‘ ๋ชจ๋“ˆ์€ ๋ชจ๋‘ ์ถ”์ƒํ™”์— ์˜์กดํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

  2. ์ถ”์ƒํ™”๋Š” ์„ธ๋ถ€์‚ฌํ•ญ์— ์˜์กดํ•˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์„ธ๋ถ€์‚ฌํ•ญ์€ ์ถ”์ƒํ™”์— ์˜์กดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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

DIP๋Š” ์ฃผ๋กœ IoC ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ๋‹ฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ IoC ์ปจํ…Œ์ด๋„ˆ์˜ ์˜ˆ์ œ๋Š” InversifyJs์ž…๋‹ˆ๋‹ค.

Bad:

import { readFile as readFileCb } from 'fs';
import { promisify } from 'util';

const readFile = promisify(readFileCb);

type ReportData = {
  // ..
}

class XmlFormatter {
  parse<T>(content: string): T {
    // XML ๋ฌธ์ž์—ด์„ T ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
  }
}

class ReportReader {

  // BAD: ํŠน์ • ์š”์ฒญ์˜ ๊ตฌํ˜„์— ์˜์กดํ•˜๋Š” ๊ฒƒ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.
  // `parse` ๋ฉ”์†Œ๋“œ์— ์˜์กดํ•˜๋Š” `ReportReader`๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  private readonly formatter = new XmlFormatter();

  async read(path: string): Promise<ReportData> {
    const text = await readFile(path, 'UTF8');
    return this.formatter.parse<ReportData>(text);
  }
}

// ...
const reader = new ReportReader();
await report = await reader.read('report.xml');

Good:

import { readFile as readFileCb } from 'fs';
import { promisify } from 'util';

const readFile = promisify(readFileCb);

type ReportData = {
  // ..
}

interface Formatter {
  parse<T>(content: string): T;
}

class XmlFormatter implements Formatter {
  parse<T>(content: string): T {
    // XML ๋ฌธ์ž์—ด์„ T ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
  }
}


class JsonFormatter implements Formatter {
  parse<T>(content: string): T {
    // JSON ๋ฌธ์ž์—ด์„ T ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
  }
}

class ReportReader {
  constructor(private readonly formatter: Formatter) {
  }

  async read(path: string): Promise<ReportData> {
    const text = await readFile(path, 'UTF8');
    return this.formatter.parse<ReportData>(text);
  }
}

// ...
const reader = new ReportReader(new XmlFormatter());
await report = await reader.read('report.xml');

// ๋˜๋Š” json ๋ณด๊ณ ์„œ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ
const reader = new ReportReader(new JsonFormatter());
await report = await reader.read('report.json');

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ…Œ์ŠคํŠธ

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

ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š์„ ์ด์œ ๋Š” ์—†์Šต๋‹ˆ๋‹ค. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํƒ€์ž…์„ ์ง€์›ํ•˜๋Š” ๋งŽ์€ ์–‘์˜ ์ข‹์€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์žˆ์œผ๋ฏ€๋กœ ๋‹น์‹ ์˜ ํŒ€์ด ์„ ํ˜ธํ•˜๋Š” ๊ฒƒ์„ ์ฐพ์•„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋‹น์‹ ์˜ ํŒ€์— ์ ํ•ฉํ•œ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ฐพ์•˜๋‹ค๋ฉด, ๋‹น์‹ ์ด ๋งŒ๋“œ๋Š” ๋ชจ๋“  ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ/๋ชจ๋“ˆ์„ ์œ„ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ญ์ƒ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜์„ธ์š”. ํ…Œ์ŠคํŠธ ๊ธฐ๋ฐ˜ ๊ฐœ๋ฐœ(TDD)์ด ๋‹น์‹ ์ด ์„ ํ˜ธํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋ผ๋ฉด, ๋งค์šฐ ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ค‘์š”ํ•œ ๊ฑด ์–ด๋–ค ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๊ธฐ์กด์˜ ๊ฒƒ์„ ๋ฆฌํŒฉํ† ๋งํ•˜๊ธฐ ์ „์— ๋ชฉํ‘œํ•˜๋Š” ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ๋‹ฌ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

TDD์˜ ์„ธ ๊ฐ€์ง€ ๋ฒ•์น™

  1. ์‹คํŒจํ•˜๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์—๋Š” ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ๋งˆ์„ธ์š”.

  2. ์ปดํŒŒ์ผ์€ ์‹คํŒจํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์‹คํ–‰์ด ์‹คํŒจํ•˜๋Š” ์ •๋„๋กœ๋งŒ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.

  3. ์‹คํŒจํ•˜๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•  ์ •๋„๋กœ๋งŒ ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”.

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

F.I.R.S.T ๊ทœ์น™

๋ช…๋ฃŒํ•œ ํ…Œ์ŠคํŠธ๋Š” ๋‹ค์Œ ๊ทœ์น™์„ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค:

  • Fast ํ…Œ์ŠคํŠธ๋Š” ๋นˆ๋ฒˆํ•˜๊ฒŒ ์‹คํ–‰๋˜๋ฏ€๋กœ ๋นจ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • Independent ํ…Œ์ŠคํŠธ๋Š” ์„œ๋กœ ์ข…์†์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰ํ•˜๋“ ์ง€ ์ˆœ์„œ ์ƒ๊ด€์—†์ด ๋ชจ๋‘ ์‹คํ–‰ํ•˜๋“ ์ง€ ๋™์ผํ•œ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • Repeatable ํ…Œ์ŠคํŠธ๋Š” ์–ด๋–ค ํ™˜๊ฒฝ์—์„œ๋“  ๋ฐ˜๋ณต๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•˜๋Š”๋ฐ์— ์ด์œ ๊ฐ€ ์—†์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • Self-Validating ํ…Œ์ŠคํŠธ๋Š” ํ†ต๊ณผ ํ˜น์€ ์‹คํŒจ๋กœ ๋‹ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผ๋˜์—ˆ๋‹ค๋ฉด ๋กœ๊ทธ ํŒŒ์ผ์„ ๋ณด๋ฉฐ ๋น„๊ตํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค.

  • Timely ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์ „์— ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ํ›„์— ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋ฉด, ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋„ˆ๋ฌด ๊ณ ๋‹จํ•˜๊ฒŒ ๋Š๊ปด์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ…Œ์ŠคํŠธ ํ•˜๋‚˜์— ํ•˜๋‚˜์˜ ๊ฐœ๋…์„ ์ž‘์„ฑํ•˜์„ธ์š”

๋˜ํ•œ, ํ…Œ์ŠคํŠธ๋Š” ๋‹จ์ผ ์ฑ…์ž„ ์›์น™์„ ๋”ฐ๋ผ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ•˜๋‚˜๋‹น ํ•˜๋‚˜์˜ assert ๊ตฌ๋ฌธ์„ ์ž‘์„ฑํ•˜์„ธ์š”.

Bad:

import { assert } from 'chai';

describe('AwesomeDate', () => {
  it('handles date boundaries', () => {
    let date: AwesomeDate;

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

    date = new AwesomeDate('2/1/2016');
    assert.equal('2/29/2016', date.addDays(28));

    date = new AwesomeDate('2/1/2015');
    assert.equal('3/1/2015', date.addDays(28));
  });
});

Good:

import { assert } from 'chai';

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

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

  it('handles non-leap year', () => {
    const date = new AwesomeDate('2/1/2015');
    assert.equal('3/1/2015', date.addDays(28));
  });
});

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ…Œ์ŠคํŠธ์˜ ์ด๋ฆ„์€ ํ…Œ์ŠคํŠธ์˜ ์˜๋„๊ฐ€ ๋“œ๋Ÿฌ๋‚˜์•ผ ํ•ฉ๋‹ˆ๋‹ค

ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•  ๋•Œ, ํ…Œ์ŠคํŠธ์˜ ์ด๋ฆ„์€ ์–ด๋–ค ๊ฒƒ์ด ์ž˜๋ชป๋˜์—ˆ๋Š”์ง€ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ฒซ ๋ฒˆ์งธ ํ‘œ์‹œ์ž…๋‹ˆ๋‹ค.

Bad:

describe('Calendar', () => {
  it('2/29/2020', () => {
    // ...
  });

  it('throws', () => {
    // ...
  });
});

Good:

describe('Calendar', () => {
  it('should handle leap year', () => {
    // ...
  });

  it('should throw when format is invalid', () => {
    // ...
  });
});

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋™์‹œ์„ฑ

ํ”„๋กœ๋ฏธ์Šค vs ์ฝœ๋ฐฑ

์ฝœ๋ฐฑ์€ ๋ช…๋ฃŒํ•˜์ง€ ์•Š๊ณ , ์ง€๋‚˜์นœ ์–‘์˜ ์ค‘์ฒฉ๋œ ์ฝœ๋ฐฑ ์ง€์˜ฅ์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ฝœ๋ฐฑ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ธฐ์กด์˜ ํ•จ์ˆ˜๋ฅผ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋กœ ๋ณ€ํ™˜์‹œ์ผœ์ฃผ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. (Node.js๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด util.promisify๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”. ์ผ๋ฐ˜์ ์ธ ๋ชฉ์ ์ด๋ผ๋ฉด pify, es6-promisify๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.)

Bad:

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

function downloadPage(url: string, saveTo: string, callback: (error: Error, content?: string) => void) {
  get(url, (error, response) => {
    if (error) {
      callback(error);
    } else {
      writeFile(saveTo, response.body, (error) => {
        if (error) {
          callback(error);
        } else {
          callback(null, response.body);
        }
      });
    }
  });
}

downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html', (error, content) => {
  if (error) {
    console.error(error);
  } else {
    console.log(content);
  }
});

Good:

import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';

const write = promisify(writeFile);

function downloadPage(url: string, saveTo: string): Promise<string> {
  return get(url)
    .then(response => write(saveTo, response));
}

downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html')
  .then(content => console.log(content))
  .catch(error => console.error(error));  

ํ”„๋กœ๋ฏธ์Šค๋Š” ์ฝ”๋“œ๋ฅผ ๋”์šฑ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋ช‡๋ช‡์˜ ํ—ฌํผ ๋ฉ”์†Œ๋“œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค:

ํŒจํ„ด ์„ค๋ช…
Promise.resolve(value) ํ•ด๊ฒฐ(resolve)๋œ ํ”„๋กœ๋ฏธ์Šค๋กœ ๊ฐ’์„ ๋ณ€ํ™˜ํ•จ.
Promise.reject(error) ๊ฑฐ๋ถ€(reject)๋œ ํ”„๋กœ๋ฏธ์Šค๋กœ ์—๋Ÿฌ๋ฅผ ๋ณ€ํ™˜ํ•จ.
Promise.all(promises) ์ „๋‹ฌ๋œ ๋ชจ๋“  ํ”„๋กœ๋ฏธ์Šค๊ฐ€ ์ดํ–‰ํ•œ ๊ฐ’์˜ ๋ฐฐ์—ด์„ ์ดํ–‰ํ•˜๋Š” ์ƒˆ ํ”„๋กœ๋ฏธ์Šค ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ๊ฑฐ๋ถ€๋œ ์ฒซ ๋ฒˆ์งธ ํ”„๋กœ๋ฏธ์Šค์˜ ์ด์œ ๋กœ ๊ฑฐ๋ถ€ํ•จ.
Promise.race(promises) ์ „๋‹ฌ๋œ ํ”„๋กœ๋ฏธ์Šค์˜ ๋ฐฐ์—ด์—์„œ ๊ฐ€์žฅ ๋จผ์ € ์™„๋ฃŒ๋œ ๊ฒฐ๊ณผ/์—๋Ÿฌ๋กœ ์ดํ–‰/๊ฑฐ๋ถ€๋œ ์ƒˆ ํ”„๋กœ๋ฏธ์Šค ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•จ.

Promise.all๋Š” ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ํ•„์š”๊ฐ€ ์žˆ์„ ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. Promise.race๋Š” ํ”„๋กœ๋ฏธ์Šค๋ฅผ ์œ„ํ•œ ํƒ€์ž„์•„์›ƒ๊ณผ ๊ฐ™์€ ๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค.

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ”„๋กœ๋ฏธ์Šค๋ณด๋‹ค async/await๊ฐ€ ๋” ๋ช…๋ฃŒํ•ฉ๋‹ˆ๋‹ค

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

Bad:

import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';

const write = util.promisify(writeFile);

function downloadPage(url: string, saveTo: string): Promise<string> {
  return get(url).then(response => write(saveTo, response));
}

downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html')
  .then(content => console.log(content))
  .catch(error => console.error(error));  

Good:

import { get } from 'request';
import { writeFile } from 'fs';
import { promisify } from 'util';

const write = promisify(writeFile);

async function downloadPage(url: string, saveTo: string): Promise<string> {
  const response = await get(url);
  await write(saveTo, response);
  return response;
}

// somewhere in an async function
try {
  const content = await downloadPage('https://en.wikipedia.org/wiki/Robert_Cecil_Martin', 'article.html');
  console.log(content);
} catch (error) {
  console.error(error);
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์—๋Ÿฌ ์ฒ˜๋ฆฌ

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

throw ๋˜๋Š” reject ๊ตฌ๋ฌธ์—์„œ ํ•ญ์ƒ Error ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์„ธ์š”

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

Bad:

function calculateTotal(items: Item[]): number {
  throw 'Not implemented.';
}

function get(): Promise<Item[]> {
  return Promise.reject('Not implemented.');
}

Good:

function calculateTotal(items: Item[]): number {
  throw new Error('Not implemented.');
}

function get(): Promise<Item[]> {
  return Promise.reject(new Error('Not implemented.'));
}

// ๋˜๋Š” ์•„๋ž˜์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค:

async function get(): Promise<Item[]> {
  throw new Error('Not implemented.');
}

Error ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ์žฅ์ ์€ try/catch/finally ๊ตฌ๋ฌธ์— ์˜ํ•ด ์ง€์›๋˜๊ณ  ์•”์‹œ์ ์œผ๋กœ ๋ชจ๋“  ์—๋Ÿฌ๊ฐ€ ๋””๋ฒ„๊น…์— ๋งค์šฐ ๊ฐ•๋ ฅํ•œ stack ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.
๋˜ ํ•˜๋‚˜์˜ ๋Œ€์•ˆ์€ ์žˆ์Šต๋‹ˆ๋‹ค. throw ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๋Œ€์‹ , ํ•ญ์ƒ ์‚ฌ์šฉ์ž ์ •์˜ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋Š” ์ด๊ฒƒ์„ ํ›จ์”ฌ ๋” ์‰ฝ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ์˜ˆ์ œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”:

type Result<R> = { isError: false, value: R };
type Failure<E> = { isError: true, error: E };
type Failable<R, E> = Result<R> | Failure<E>;

function calculateTotal(items: Item[]): Failable<number, 'empty'> {
  if (items.length === 0) {
    return { isError: true, error: 'empty' };
  }

  // ...
  return { isError: false, value: 42 };
}

์ด ์•„์ด๋””์–ด์˜ ์ž์„ธํ•œ ์„ค๋ช…์€ ์›๋ฌธ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

catch ์ ˆ์—์„œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์„ ๋น„์›Œ๋‘์ง€ ๋งˆ์„ธ์š”

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

Bad:

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

// ์•„๋ž˜ ์˜ˆ์ œ๋Š” ํ›จ์”ฌ ๋‚˜์ฉ๋‹ˆ๋‹ค.

try {
  functionThatMightThrow();
} catch (error) {
  // ์—๋Ÿฌ๋ฅผ ๋ฌด์‹œ
}

Good:

import { logger } from './logging'

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์š”์ฒญ์ด ๊ฑฐ๋ถ€๋œ ํ”„๋กœ๋ฏธ์Šค ๊ฐ์ฒด๋ฅผ ๋ฌด์‹œํ•˜์ง€ ๋งˆ์„ธ์š”

์œ„์™€ ๊ฐ™์ด try/catch ์ ˆ์—์„œ ๋ฐ›์€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์„ ๋น„์›Œ๋‘๋ฉด ์•ˆ๋ฉ๋‹ˆ๋‹ค.

Bad:

getUser()
  .then((user: User) => {
    return sendEmail(user.email, 'Welcome!');
  })
  .catch((error) => {
    console.log(error);
  });

Good:

import { logger } from './logging'

getUser()
  .then((user: User) => {
    return sendEmail(user.email, 'Welcome!');
  })
  .catch((error) => {
    logger.log(error);
  });

// ๋˜๋Š” async/await ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

try {
  const user = await getUser();
  await sendEmail(user.email, 'Welcome!');
} catch (error) {
  logger.log(error);
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์„œ์‹

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

TSLint๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์€ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๊ทน์ ์œผ๋กœ ๊ฐœ์„ ์‹œํ‚ค๋„๋ก ๋„์™€์ฃผ๋Š” ์ •์  ๋ถ„์„ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ์— ์ฐธ๊ณ ํ•  ์ˆ˜ ์žˆ๋Š” TSLint ์„ค์ •์„ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค:

๋˜ํ•œ, ํ›Œ๋ฅญํ•œ ์ž๋ฃŒ์ธ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ์™€ ์ฝ”๋”ฉ ์ปจ๋ฒค์…˜์„ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”.

์—ญ์ž์ฃผ: TSLint๋Š” deprecated๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Roadmap: TSLint -> ESLint ์ด์Šˆ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.

์ผ๊ด€์ ์œผ๋กœ ๋Œ€์†Œ๋ฌธ์ž๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

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

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() {}

type animal = { /* ... */ }
type Container = { /* ... */ }

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() {}

type Animal = { /* ... */ }
type Container = { /* ... */ }

ํด๋ž˜์Šค, ์ธํ„ฐํŽ˜์ด์Šค, ํƒ€์ž… ๊ทธ๋ฆฌ๊ณ  ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ด๋ฆ„์—๋Š” PascalCase๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
๋ณ€์ˆ˜, ํ•จ์ˆ˜ ๊ทธ๋ฆฌ๊ณ  ํด๋ž˜์Šค ๋ฉค๋ฒ„ ์ด๋ฆ„์—๋Š” camelCase๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํ•จ์ˆ˜ ํ˜ธ์ถœ์ž์™€ ํ”ผํ˜ธ์ถœ์ž๋ฅผ ๊ฐ€๊น๊ฒŒ ์œ„์น˜์‹œํ‚ค์„ธ์š”

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

Bad:

class PerformanceReview {
  constructor(private readonly employee: Employee) {
  }

  private lookupPeers() {
    return db.lookup(this.employee.id, 'peers');
  }

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

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

  review() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();

    // ...
  }

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

  private getSelfReview() {
    // ...
  }
}

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

Good:

class PerformanceReview {
  constructor(private readonly employee: Employee) {
  }

  review() {
    this.getPeerReviews();
    this.getManagerReview();
    this.getSelfReview();

    // ...
  }

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

  private lookupPeers() {
    return db.lookup(this.employee.id, 'peers');
  }

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

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

  private getSelfReview() {
    // ...
  }
}

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

import ๊ตฌ๋ฌธ์„ ํŠน์ • ์ˆœ์„œ๋Œ€๋กœ ์ •๋ฆฌํ•˜์„ธ์š”

import ๊ตฌ๋ฌธ์„ ์ฝ๊ธฐ ์‰ฝ๊ณ  ๋ช…๋ฃŒํ•˜๊ฒŒ ํ•˜๋ฉด ๋‹น์‹ ์€ ํ˜„์žฌ ์ฝ”๋“œ์˜ ์˜์กด์„ฑ์„ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ import ๊ตฌ๋ฌธ ์ •๋ฆฌ๋ฅผ ์œ„ํ•œ ์ข‹์€ ๋ฐฉ๋ฒ•๋“ค์„ ์ ์šฉํ•ด๋ณด์„ธ์š”:

  • import ๊ตฌ๋ฌธ์€ ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ๋Œ€๋กœ ๋ฐฐ์—ดํ•˜๊ณ  ๊ทธ๋ฃนํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ import ๊ตฌ๋ฌธ์€ ์ œ๊ฑฐ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฆ„์ด ์žˆ๋Š” import ๊ตฌ๋ฌธ์€ ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ๋Œ€๋กœ ๋ฐฐ์—ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: import {A, B, C} from 'foo';)
  • import ํ•˜๋Š” ์†Œ์Šค์ฝ”๋“œ๋Š” ๊ทธ๋ฃน ๋‚ด์—์„œ ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ๋Œ€๋กœ ๋ฐฐ์—ดํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: import * as foo from 'a'; import * as bar from 'b';)
  • import ๊ตฌ๋ฌธ์˜ ๊ทธ๋ฃน์€ ๋นˆ ์ค„๋กœ ๊ตฌ๋ถ„๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ทธ๋ฃน์€ ๋‹ค์Œ ์ˆœ์„œ๋ฅผ ์ค€์ˆ˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:
    • ํด๋ฆฌํ•„ (์˜ˆ: import 'reflect-metadata';)
    • Node ๋‚ด์žฅ ๋ชจ๋“ˆ (์˜ˆ: import fs from 'fs';)
    • ์™ธ๋ถ€ ๋ชจ๋“ˆ (์˜ˆ: import { query } from 'itiriri';)
    • ๋‚ด๋ถ€ ๋ชจ๋“ˆ (์˜ˆ: import { UserService } from 'src/services/userService';)
    • ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ชจ๋“ˆ (์˜ˆ: import foo from '../foo'; import qux from '../../foo/qux';)
    • ๋™์ผํ•œ ๊ณ„์ธต์˜ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ชจ๋“ˆ (์˜ˆ: import bar from './bar'; import baz from './bar/baz';)

Bad:

import { TypeDefinition } from '../types/typeDefinition';
import { AttributeTypes } from '../model/attribute';
import { ApiCredentials, Adapters } from './common/api/authorization';
import fs from 'fs';
import { ConfigPlugin } from './plugins/config/configPlugin';
import { BindingScopeEnum, Container } from 'inversify';
import 'reflect-metadata';

Good:

import 'reflect-metadata';

import fs from 'fs';
import { BindingScopeEnum, Container } from 'inversify';

import { AttributeTypes } from '../model/attribute';
import { TypeDefinition } from '../types/typeDefinition';

import { ApiCredentials, Adapters } from './common/api/authorization';
import { ConfigPlugin } from './plugins/config/configPlugin';

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์•จ๋ฆฌ์–ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”

tsconfig.json์˜ compilerOptions ์„น์…˜ ์•ˆ์—์„œ paths์™€ baseUrl ์†์„ฑ์„ ์ •์˜ํ•ด ๋” ๋ณด๊ธฐ ์ข‹์€ import ๊ตฌ๋ฌธ์„ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”.

์ด ๋ฐฉ๋ฒ•์€ import ๊ตฌ๋ฌธ์„ ์‚ฌ์šฉํ•  ๋•Œ ๊ธด ์ƒ๋Œ€๊ฒฝ๋กœ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜๊ฒŒ ๋„์™€์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Bad:

import { UserService } from '../../../services/UserService';

Good:

import { UserService } from '@services/UserService';
// tsconfig.json
...
  "compilerOptions": {
    ...
    "baseUrl": "src",
    "paths": {
      "@services": ["services/*"]
    }
    ...
  }
...

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์ฃผ์„

์ฃผ์„์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ฃผ์„ ์—†์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์‹คํŒจํ–ˆ๋‹ค๋Š” ํ‘œ์‹œ์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ๋Š” ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(Single source of truth)์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‚˜์œ ์ฝ”๋“œ์— ์ฃผ์„๋“ค ๋‹ฌ์ง€ ๋งˆ๋ผ. ์ƒˆ๋กœ ์งœ๋ผ.
โ€” Brian W. Kernighan and P. J. Plaugher

์ฃผ์„ ๋Œ€์‹ ์— ์ž์ฒด์ ์œผ๋กœ ์„ค๋ช… ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”

์ฃผ์„์€ ๋ณ€๋ช…์ผ ๋ฟ, ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ข‹์€ ์ฝ”๋“œ๋Š” ๋Œ€๋ถ€๋ถ„ ๊ทธ ์กด์žฌ ์ž์ฒด๋กœ ๋ฌธ์„œํ™”๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

Bad:

// subscription์ด ํ™œ์„ฑํ™” ์ƒํƒœ์ธ์ง€ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค.
if (subscription.endDate > Date.now) {  }

Good:

const isSubscriptionActive = subscription.endDate > Date.now;
if (isSubscriptionActive) { /* ... */ }

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋‹น์‹ ์˜ ์ฝ”๋“œ๋ฅผ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜์ง€ ๋งˆ์„ธ์š”

๋ฒ„์ „ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์ด ์กด์žฌํ•˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ๋Š” ๊ธฐ๋ก์— ๋‚จ๊ธฐ์„ธ์š”.

Bad:

type User = {
  name: string;
  email: string;
  // age: number;
  // jobPosition: string;
}

Good:

type User = {
  name: string;
  email: string;
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์ผ๊ธฐ ๊ฐ™์€ ์ฃผ์„์„ ๋‹ฌ์ง€ ๋งˆ์„ธ์š”

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

Bad:

/**
 * 2016-12-20: ์ดํ•ดํ•˜์ง€ ๋ชปํ•ด์„œ ๋ชจ๋‚˜๋“œ๋ฅผ ์ œ๊ฑฐํ•จ (RM)
 * 2016-10-01: ํŠน๋ณ„ํ•œ ๋ชจ๋‚˜๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐœ์„ ํ•จ (JP)
 * 2016-02-03: ํƒ€์ž… ์ฒดํ‚น ์ถ”๊ฐ€ํ•จ (LI)
 * 2015-03-14: combine ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•จ (JR)
 */
function combine(a: number, b: number): number {
  return a + b;
}

Good:

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

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

์ฝ”๋“œ์˜ ์œ„์น˜๋ฅผ ์„ค๋ช…ํ•˜๋Š” ์ฃผ์„์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”

์ด๊ฑด ๋ณดํ†ต ์ฝ”๋“œ๋ฅผ ์–ด์ง€๋Ÿฝํžˆ๊ธฐ๋งŒ ํ•ฉ๋‹ˆ๋‹ค. ํ•จ์ˆ˜์™€ ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์ ์ ˆํ•œ ๋“ค์—ฌ์“ฐ๊ธฐ์™€ ์„œ์‹์œผ๋กœ ๋‹น์‹ ์˜ ์ฝ”๋“œ์— ์‹œ๊ฐ์ ์ธ ๊ตฌ์กฐ๊ฐ€ ๋ณด์ด๋„๋ก ํ•˜์„ธ์š”.
๋Œ€๋ถ€๋ถ„์˜ IDE(ํ†ตํ•ฉ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ)์—์„œ๋Š” ์ฝ”๋“œ ๋ธ”๋ก์„ ์ ‘๊ธฐ/ํŽผ์น˜๊ธฐ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. (Visual Studio Code์˜ folding regions๋ฅผ ํ™•์ธํ•ด๋ณด์„ธ์š”).

Bad:

////////////////////////////////////////////////////////////////////////////////
// Client ํด๋ž˜์Šค
////////////////////////////////////////////////////////////////////////////////
class Client {
  id: number;
  name: string;
  address: Address;
  contact: Contact;

  ////////////////////////////////////////////////////////////////////////////////
  // public ๋ฉ”์†Œ๋“œ
  ////////////////////////////////////////////////////////////////////////////////
  public describe(): string {
    // ...
  }

  ////////////////////////////////////////////////////////////////////////////////
  // private ๋ฉ”์†Œ๋“œ
  ////////////////////////////////////////////////////////////////////////////////
  private describeAddress(): string {
    // ...
  }

  private describeContact(): string {
    // ...
  }
};

Good:

class Client {
  id: number;
  name: string;
  address: Address;
  contact: Contact;

  public describe(): string {
    // ...
  }

  private describeAddress(): string {
    // ...
  }

  private describeContact(): string {
    // ...
  }
};

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

TODO ์ฃผ์„

์ถ”ํ›„์— ๊ฐœ์„ ์„ ์œ„ํ•ด ์ฝ”๋“œ์— ๋ฉ”๋ชจ๋ฅผ ๋‚จ๊ฒจ์•ผํ•  ๋•Œ, // TODO ์ฃผ์„์„ ์‚ฌ์šฉํ•˜์„ธ์š”. ๋Œ€๋ถ€๋ถ„์˜ IDE๋Š” ์ด๋Ÿฐ ์ข…๋ฅ˜์˜ ์ฃผ์„์„ ํŠน๋ณ„ํ•˜๊ฒŒ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด์•ผํ•  ์ผ ๋ชฉ๋ก์„ ๋น ๋ฅด๊ฒŒ ๊ฒ€ํ† ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ TODO ์ฃผ์„์ด ๋‚˜์œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ด์œ ๋Š” ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์„ ๋ช…์‹ฌํ•˜์„ธ์š”.

Bad:

function getActiveSubscriptions(): Promise<Subscription[]> {
  // ensure `dueDate` is indexed.
  return db.subscriptions.find({ dueDate: { $lte: new Date() } });
}

Good:

function getActiveSubscriptions(): Promise<Subscription[]> {
  // TODO: ensure `dueDate` is indexed.
  return db.subscriptions.find({ dueDate: { $lte: new Date() } });
}

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋ฒˆ์—ญ

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

๋ฒˆ์—ญ์ด ์™„๋ฃŒ๋˜๋ฉด ์ฐธ๊ณ ๋ฌธํ—Œ์— ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ๊ณผ ์ง„ํ–‰์ƒํ™ฉ์„ ๋ณด๊ณ ์‹ถ๋‹ค๋ฉด ์ด ๋…ผ์˜๋ฅผ ํ™•์ธํ•˜์„ธ์š”. ๋‹น์‹ ์€ ๋‹น์‹ ์˜ ์–ธ์–ด์— ์ด ๊ธ€์„ ๋ฒˆ์—ญํ•จ์œผ๋กœ์จ ํด๋ฆฐ ์ฝ”๋“œ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์ค‘์š”ํ•œ ๊ธฐ์—ฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โฌ† ๋งจ ์œ„๋กœ ์ด๋™

๋ฒˆ์—ญ์— ๋„์›€์„ ์ฃผ์‹  ๋ถ„๋“ค

๋ฆฌ๋ทฐ

์ฐธ๊ณ ์ž๋ฃŒ

clean-code-typescript's People

Contributors

738 avatar asimionese avatar dimadeveatii avatar fuzzknob avatar iblacki avatar jesuiterb avatar kajetansw avatar lucavictor220 avatar mouzourides avatar mrkiffie avatar msakamaki avatar nicoder avatar rvmoldova avatar userbit avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.