Skip to content

Latest commit

Β 

History

History
1608 lines (1182 loc) Β· 33.7 KB

clean-code.md

File metadata and controls

1608 lines (1182 loc) Β· 33.7 KB

Clean Code

ryanmcdermott의 clean-code-javascriptλ₯Ό λ²ˆμ—­ν•œ ν¬μŠ€νŠΈμž…λ‹ˆλ‹€.

클린 μ½”λ“œ(clean code)λ₯Ό μ‚¬μš©ν•˜λ©΄ μ½”λ“œμ˜ 가독성과 μž¬μ‚¬μš©μ„±μ„ λ†’μΌμˆ˜ 있으며 λ¦¬νŒ©ν† λ§ν•˜κΈ° μ‰¬μš΄ ν”„λ‘œκ·Έλž¨μ„ κ°œλ°œν•  수 μžˆλ‹€.


Table of Contents


Variables

의미λ₯Ό λ‚΄ν¬ν•˜κ³  있으며 λ°œμŒν•˜κΈ° μ‰¬μš΄ λ³€μˆ˜ 이름 μ‚¬μš©

  • Bad

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

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

λ™μΌν•œ μœ ν˜•μ˜ λ³€μˆ˜μ— λ™μΌν•œ μ–΄νœ˜ μ‚¬μš©

  • Bad

    getUserInfo();
    getClientData();
    getCustomerRecord();
  • Good

    getUser();

검색 κ°€λŠ₯ν•œ 이름 μ‚¬μš©

μž‘μ„±λœ μ½”λ“œμ˜ λ¦¬λ·°λ‚˜ 검색을 μœ„ν•΄μ„œ ν”„λ‘œκ·Έλž¨μ„ μ‰½κ²Œ 이해할 수 μžˆλŠ” 의미λ₯Ό λ‹΄κ³  μžˆλŠ” λ³€μˆ˜λͺ…을 μ‚¬μš©ν•΄μ•Όν•œλ‹€.

  • Bad

    // 86400000κ°€ λ„λŒ€μ²΄ λ­μž„?!
    setTimeout(blastOff, 86400000);
  • Good

    // λŒ€λ¬Έμžλ‘œ μ„ μ–Έλœ μƒμˆ˜ 이름을 톡해 λ³€μˆ˜κ°€ 무엇을 μ˜λ―Έν•˜λŠ”μ§€ μ•Œ 수 μžˆλ‹€.
    const MILLISECONDS_IN_A_DAY = 60 * 60 * 24 * 1000; //86400000;
    
    setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

μ„€λͺ…이 μžˆλŠ” λ³€μˆ˜λͺ… μ‚¬μš©

  • 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);

λ§€ν•‘μ‹œ λͺ…μ‹œμ μΈ λ³€μˆ˜λͺ… μ‚¬μš©

λͺ…μ‹œμ μΈ(explicit)것이 μ•”μ‹œμ μΈ(implicit)것보닀 λ‚«λ‹€.

  • Bad

    const locations = ["Austin", "New York", "San Francisco"];
    locations.forEach(l => {
      doStuff();
      doSomeOtherStuff();
      // ...
      // ...
      // ...
      // 1이 λ„λŒ€μ²΄ λ­μž„?!
      dispatch(l);
    });
  • Good

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

λΆˆν•„μš”ν•œ μ»¨ν…μŠ€νŠΈμ˜ 반볡 μ§€μ–‘

ν΄λž˜μŠ€λ‚˜ 객체의 이름이 무언가λ₯Ό μ˜λ―Έν•˜κ³  μžˆλ‹€λ©΄, ν”„λ‘œνΌν‹°μ— 클래슀/객체의 이름을 λ°˜λ³΅ν•  ν•„μš” μ—†λ‹€.

  • Bad

    const Car = {
      carMake: "Honda",
      carModel: "Accord",
      carColor: "Blue"
    };
    
    function paintCar(car) {
      car.carColor = "Red";
    }
  • Good

    const Car = {
      make: "Honda",
      model: "Accord",
      color: "Blue"
    };
    
    function paintCar(car) {
      car.color = "Red";
    }

ν•¨μˆ˜ μΈμžμ— default κ°’ μ‚¬μš©

  • Bad

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

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

Functions

ν•˜λ‚˜ λ˜λŠ” 두 개의 ν•¨μˆ˜ 인자 μ‚¬μš©

두 개의 μΈμžκ°€ κ°€μž₯ 이상적이며 ν•¨μˆ˜μ˜ 인자의 개수λ₯Ό μ΅œμ†Œν™”ν•˜λ©΄ ν•¨μˆ˜λ₯Ό 더 μ‰½κ²Œ ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€. μΈμžκ°€ λ§Žμ„κ²½μš° ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€κ°€ λ§Žμ•„μ Έ λΉ„νš¨μœ¨μ μ΄λ‹€.

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

ν•¨μˆ˜λŠ” ν•œ κ°€μ§€λ§Œ μˆ˜ν–‰

두 κ°€μ§€ μ΄μƒμ˜ 일을 μˆ˜ν–‰ν•˜λŠ” ν•¨μˆ˜λ“€μ€ λΉ„κ΅ν•˜κ±°λ‚˜ ν…ŒμŠ€νŠΈν• λ•Œμ˜ 어렀움이 μžˆλ‹€. ν•¨μˆ˜λ₯Ό ν•˜λ‚˜μ˜ μž‘μ—…μœΌλ‘œ 뢄리할 수 있으면 μ‰½κ²Œ λ¦¬νŒ©ν† λ§ν•  수 있으며 가독성을 높일 수 μžˆλ‹€.

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

ν•¨μˆ˜μ˜ κΈ°λŠ₯을 μ„€λͺ…ν•˜λŠ” ν•¨μˆ˜λͺ…

  • Bad

    function addToDate(date, month) {
      // ...
    }
    
    const date = new Date();
    
    // μ—°, μ›”, 일 쀑 무엇을 λ”ν•˜λŠ” ν•¨μˆ˜μΈκ°€ μ•ŒκΈ° 어렀움
    addToDate(date, 1);
  • Good

    function addMonthToDate(month, date) {
      // ...
    }
    
    const date = new Date();
    addMonthToDate(1, date);

ν•¨μˆ˜λŠ” 였직 ν•œ λ‹¨κ³„μ˜ 좔상화여야 ν•œλ‹€.

두 단계 μ΄μƒμ˜ 좔상화가 적용된 경우 λ§Žμ€ μž‘μ—…μ„ μˆ˜ν–‰ν•¨μœΌλ‘œ λΉ„νš¨μœ¨μ μ΄λ‹€.

  • 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;
    }

μ€‘λ³΅λœ μ½”λ“œ 제거

μ€‘λ³΅λœ μ½”λ“œλ“€μ€ 일뢀 μ½”λ“œμ˜ λ‘œμ§μ„ λ³€κ²½ν• λ•Œ λ‹€λ₯Έ μ½”λ“œλ“€μ˜ λ‘œμ§λ„ 변경될 κ°€λŠ₯성이 μžˆμœΌλ―€λ‘œ 쀑볡을 μ΅œμ†Œν™” ν•˜λŠ” 것이 μ’‹λ‹€.

μ€‘λ³΅λœ μ½”λ“œλ₯Ό μ œκ±°ν•œλ‹€λŠ” 것은 λ‹€μ–‘ν•œ μš”μ†Œλ“€μ„ 처리 ν•  수 μžˆλŠ” ν•¨μˆ˜/λͺ¨λ“ˆ/클래슀 λ“±μ˜ 좔상화λ₯Ό λ§Œλ“œλŠ” 것을 μ˜λ―Έν•œλ‹€. 좔상화λ₯Ό ν• λ•ŒλŠ” 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);
      });
    }

Object.assign을 μ‚¬μš©ν•œ κΈ°λ³Έ 객체 μ„€μ •

  • 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);

ν”Œλž˜κ·Έλ₯Ό ν•¨μˆ˜ λ§€κ°œλ³€μˆ˜λ‘œ μ‚¬μš©ν•˜μ§€λ§κ²ƒ

ν”Œλž˜κ·Έ λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•΄ 쑰건 λΆ„κΈ°λ₯Ό 톡해 ν•¨μˆ˜κ°€ λ‹€λ₯Έ λ™μž‘μ„ μˆ˜ν–‰ν•˜λŠ” 경우 ν•¨μˆ˜λ₯Ό λΆ„λ¦¬μ‹œμΌœμ•Ό ν•œλ‹€.

  • 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}`);
    }

Side Effect λ°©μ§€(part 1)

ν•¨μˆ˜λŠ” 값을 가져와 λ‹€λ₯Έ κ°’μœΌλ‘œ λ°˜ν™˜ν•˜λŠ” 것 μ΄μ™Έμ˜ μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ©΄ Side effectκ°€ λ°œμƒν•œλ‹€. 이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄μ„œ ꡬ쑰 없이 객체의 μƒνƒœλ₯Ό 곡유, μ–΄λ–€κ²ƒμœΌλ‘œλ“  기둝될 수 μžˆλŠ” 가변적인 데이터 νƒ€μž…λ“€μ˜ μ‚¬μš©, Side effectκ°€ λ°œμƒν•˜λŠ” 곳의 비집쀑화 등을 ν”Όν•΄μ•Όν•œλ‹€.

  • Bad

    // λ§Œμ•½ 이 name을 μ‚¬μš©ν•˜λŠ” λ‹€λ₯Έ ν•¨μˆ˜κ°€ μžˆμ–΄ 이 λ³€μˆ˜κ°€ 곡유되면, μ˜λ„μΉ˜μ•Šκ²Œ λ³€μˆ˜μ˜ 값이 λ³€ν•  수 μžˆλ‹€.
    
    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'];

Side Effect λ°©μ§€(part 2)

μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ 일뢀 값듀은 변경이 λΆˆκ°€ν•˜λ©°(immutable) λ‹€λ₯Έ 일뢀 값듀은 변경이 κ°€λŠ₯ν•˜λ‹€(mutable). 객체와 λ°°μ—΄ 두 μ’…λ₯˜λŠ” λ³€κ²½ κ°€λŠ₯ν•œ κ°’μ΄λ―€λ‘œ ν•¨μˆ˜μ— λ§€κ°œλ³€μˆ˜λ‘œ μ „λ‹¬ν• λ•Œ μ£Όμ˜ν•΄μ•Όν•œλ‹€. μžλ°”μŠ€ν¬λ¦½νŠΈ ν•¨μˆ˜λŠ” 객체의 ν”„λ‘œνΌν‹°μ™€ λ°°μ—΄μ˜ μš”μ†Œμ˜ 값을 λ°”κΏ€ 수 있으며 이것이 λ²„κ·Έμ˜ 원인이 λœλ‹€.

  • Bad

    const addItemToCart = (cart, item) => {
      cart.push({ item, date: Date.now() });
    };
    // μ‡Όν•‘μΉ΄νŠΈμ— μΆ”κ°€λ˜μ–΄μžˆλ˜ item에 λŒ€ν•œ λ°°μ—΄(cart)은 λ°˜ν™˜λ˜μ§€ μ•ŠλŠ”λ‹€.
  • Good

    const addItemToCart = (cart, item) => {
      return [...cart, { item, date: Date.now() }];
    };
    // addItemToCartκ°€ λ°˜ν™˜ν• λ•Œ cartλ₯Ό 볡제 및 μƒˆλ‘œμš΄ item을 μΆ”κ°€ν•œ ν›„ λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ— 이전 μ‡Όν•‘μΉ΄νŠΈμ— 좔가해놓은 μ•„μ΄ν…œμ— 영ν–₯을 μ£Όμ§€ μ•ŠλŠ”λ‹€.

μ „μ—­ ν•¨μˆ˜μ— 쓰지말 것

μ „μ—­ λ²”μœ„λ₯Ό μž‘μ„±ν•˜λŠ” 것은(polluting globals)은 λ‚˜μœ μŠ΅κ΄€μ΄λ‹€. λ‹€λ₯Έ λΌμ΄λΈŒλŸ¬λ¦¬μ™€ μΆ©λŒν•  κ°€λŠ₯성이 있고 API μ‚¬μš©μžκ°€ ν”„λ‘œλ•μ…˜μ‹œ μ˜ˆμ™Έκ°€ λ°œμƒν•  λ•ŒκΉŒμ§€ λͺ¨λ₯Όμˆ˜λ„ μžˆλ‹€. 예λ₯Ό λ“€μ–΄ Array λ©”μ†Œλ“œλ₯Ό ν™•μž₯ν•΄ 두 λ°°μ—΄κ°„μ˜ 차이λ₯Ό λ‚˜νƒ€λ‚Όμˆ˜ μžˆλŠ” diff λ©”μ†Œλ“œλ₯Ό λ§Œλ“€λ €κ³  ν• λ•Œ, Array.prototype을 μ‚¬μš©ν•  경우 λ‹€λ₯Έ λΌμ΄λΈŒλŸ¬λ¦¬μ™€ μΆ©λŒν•  κ°€λŠ₯성이 μžˆλ‹€. 이 경우 Array.prototype이 μ•„λ‹Œ 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));
      }
    }

λͺ…λ Ήν˜• ν”„λ‘œκ·Έλž˜λ°λ³΄λ‹€ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ„ μ§€ν–₯

  • 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
    );

쑰건문 μΊ‘μŠν™”

  • Bad

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

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

λΆ€μ •μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•œ 쑰건문 μ§€μ–‘

  • Bad

    function isDOMNodeNotPresent(node) {
      // ...
    }
    if (!isDOMNodeNotPresent(node)) {
      // ...
    }
  • Good

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

λ‹€μˆ˜μ˜ 쑰건문 μ‚¬μš© μ§€μ–‘

λ‹€μˆ˜μ˜ 쑰건문이 ν•„μš”ν•œ 경우 λ‹€ν˜•μ„± 즉, class 상속(extend)을 μ΄μš©ν•˜λŠ”κ²ƒμ΄ μ’‹λ‹€.

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

νƒ€μž… 검사 ν”Όν•˜κΈ°(part 1)

μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” μΈμžμ— μ–΄λ– ν•œ νƒ€μž…λ„ μ‚¬μš©ν•  수 μžˆλŠ” untyped 언어이닀. μ΄λ‘œμΈν•΄ νƒ€μž… 검사λ₯Ό ν•΄μ•Όν•˜λŠ” κ²½μš°κ°€ μƒκΈΈμˆ˜λ„ μžˆλ‹€. 이λ₯Ό ν”Όν•˜κΈ° μœ„ν•œ λ§Žμ€ 방법 쀑 κ°€μž₯ λ¨Όμ € κ³ λ €ν•΄μ•Όν•  방법은 μΌκ΄€λœ 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"));
    }

νƒ€μž… 검사 ν”Όν•˜κΈ°(part 2)

λ¬Έμžμ—΄μ΄λ‚˜ μ •μˆ˜ν˜• 같은 κΈ°λ³Έ μ›μ‹œμ μΈ νƒ€μž…μ˜ κ°’λ“€λ‘œ μž‘μ—…μ„ν•œλ‹€λ©΄ λ‹€ν˜•μ„±μ„ μ‚¬μš©ν•  수 μ—†λ‹€. 이 경우 νƒ€μž… 검사가 ν•„μš”ν•˜λ‹€λ©΄ νƒ€μž…μŠ€ν¬λ¦½νŠΈλ₯Ό μ‚¬μš©ν•˜λŠ” 것을 κ³ λ €ν•΄μ•Όν•œλ‹€. νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ ꡬ문에 정적 타이핑을 μ œκ³΅ν•˜λŠ” ν›Œλ₯­ν•œ λŒ€μ•ˆμ΄λ‹€.

  • 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;
    }

μ§€λ‚˜μΉœ μ΅œμ ν™” κΈˆμ§€

λͺ¨λ˜ λΈŒλΌμš°μ €λ“€μ€ λŸ°νƒ€μž„μ— 보이진 μ•Šμ§€λ§Œ λ‚΄λΆ€μ μœΌλ‘œ(under-the-hood) λ§Žμ€ μ΅œμ ν™”λ₯Ό μˆ˜ν–‰ν•œλ‹€. λ”°λΌμ„œ ν•„μš”μ΄μƒμ˜ μ§€λ‚˜μΉœ μ΅œμ ν™”λŠ” μ‹œκ°„ 낭비이닀.

  • Bad

    // On old browsers, each iteration with uncached `list.length` would be costly
    // because of `list.length` recomputation. In modern browsers, this is optimized.
    for (let i = 0, len = list.length; i < len; i++) {
      // ...
    }
  • Good

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

ν•„μš”μ—†λŠ” μ½”λ“œ μ‚­μ œ

더이상 μ‚¬μš©ν•˜μ§€μ•ŠλŠ” μ½”λ“œ(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");

Objects and Data Structures

getter와 setterλ₯Ό μ‚¬μš©ν•΄λΌ

getter와 setterλ₯Ό μ‚¬μš©ν•΄ 객체λ₯Ό μ•‘μ„ΈμŠ€ν•˜λŠ” 것이 객체의 ν”„λ‘œνΌν‹°λ₯Ό μ°ΎλŠ”κ²ƒλ³΄λ‹€ 더 λ‚«λ‹€. κ·Έ μ΄μœ λŠ” μ•„λž˜μ™€ κ°™λ‹€.

  1. 객체의 속성을 μ–»λŠ” 것 μ΄μƒμ˜ μž‘μ—…μ΄ ν•„μš”ν• λ•Œ λͺ¨λ“  μ ‘κ·Όμžλ“€μ„ λ³€κ²½ν•  ν•„μš”κ°€ μ—†λ‹€.
  2. set을 μ‚¬μš©ν•΄ μ‰½κ²Œ μœ νš¨μ„± 검증 절차λ₯Ό λ§Œλ“€ 수 μžˆλ‹€.
  3. λ‚΄λΆ€μ μœΌλ‘œ μΊ‘μŠν™”κ°€ κ°€λŠ₯ν•˜λ‹€.
  4. μ½”λ“œ λ‘œκΉ…κ³Ό μ—λŸ¬ μ²˜λ¦¬κ°€ κ°„νŽΈν•˜λ‹€.
  5. μ„œλ²„μ—μ„œλΆ€ν„° 객체의 ν”„λ‘œνΌν‹°λ₯Ό κ°€μ Έμ˜¬λ•Œ lazy loadκ°€ κ°€λŠ₯ν•˜λ‹€.
  • Bad
function makeBankAccount() {
  // ...

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

const account = makeBankAccount();
account.balance = 100;
  • Good
function makeBankAccount() {
  // balanceλŠ” privateν•œ 멀버 λ³€μˆ˜μ΄λ‹€.
  let balance = 0;

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

  // "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);

객체에 private 멀버λ₯Ό λ§Œλ“€μ–΄λΌ

ν΄λ‘œμ €(ES5 μ΄ν•˜)λ₯Ό μ‚¬μš©ν•΄ privateν•œ 멀버 λ³€μˆ˜λ₯Ό λ§Œλ“€ 수 μžˆλ‹€.

  • 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

Classes

ES5의 ν•¨μˆ˜λ³΄λ‹€ ES6의 클래슀λ₯Ό μ‚¬μš©ν•΄λΌ

κΈ°μ‘΄ ES5 ν΄λž˜μŠ€μ—μ„œλŠ” 클래슀 상속, μƒμ„±μž λ©”μ†Œλ“œλ₯Ό κ΅¬ν˜„ν•˜λŠ” 것이 μ–΄λ ΅λ‹€. λ§Œμ•½ 상속이 ν•„μš”ν•œ 경우 ES6의 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹λ‹€. κ·ΈλŸ¬λ‚˜ 더 크고 λ³΅μž‘ν•œ 객체가 ν•„μš”ν•˜λ‹€λ©΄ ν΄λž˜μŠ€λ³΄λ‹€ μž‘μ€ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 더 λ‚«λ‹€.

  • 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() {
    /* ... */
  }
}

λ©”μ†Œλ“œ 체이닝을 μ‚¬μš©ν•΄λΌ

λ©”μ†Œλ“œ 체이닝은 μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ 맀우 μœ μš©ν•œ νŒ¨ν„΄μ΄λ©° μ œμ΄μΏΌλ¦¬λ‚˜ lodash 같은 λ§Žμ€ λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ μ‚¬μš©λœλ‹€. λ©”μ†Œλ“œ 체이닝을 μ‚¬μš©ν•˜λ©΄ μ½”λ“œλ₯Ό 잘 ν‘œν˜„ν•  수 있으며 κ°„κ²°ν•˜κ²Œ λ§Œλ“€μ–΄μ€€λ‹€. 클래슀 ν•¨μˆ˜μ—μ„œ κ°„λ‹¨νžˆ λͺ¨λ“  ν•¨μˆ˜μ— thisλ₯Ό λ°˜ν™˜ν•˜λŠ”κ²ƒλ§ŒμœΌλ‘œ 클래슀 λ©”μ†Œλ“œλ₯Ό μ—°κ²°ν•  수 μžˆλ‹€.

  • 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;
    // 체이닝을 μœ„ν•œ this λ°˜ν™˜
    return this;
  }

  setModel(model) {
    this.model = model;
    // 체이닝을 μœ„ν•œ this λ°˜ν™˜
    return this;
  }

  setColor(color) {
    this.color = color;
    // 체이닝을 μœ„ν•œ this λ°˜ν™˜
    return this;
  }

  save() {
    console.log(this.make, this.model, this.color);
    // 체이닝을 μœ„ν•œ this λ°˜ν™˜
    return this;
  }
}

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

μƒμ†λ³΄λ‹€λŠ” μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν•΄λΌ

상속을 μ‚¬μš©ν–ˆμ„λ•Œλ³΄λ‹€ μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν• λ•Œμ˜ 이득이 많기 λ•Œλ¬Έμ— κ°€λŠ₯ν•˜λ‹€λ©΄ μƒμ†λ³΄λ‹€λŠ” μ»΄ν¬μ§€μ…˜μ„ μ‚¬μš©ν•΄μ•Ό ν•œλ‹€. (Design pattern by Gang of Four)

κ·ΈλŸ¬λ‚˜ μƒμ†μ˜ 관계가 "has-a"(μ‚¬μš©μž -> μ‚¬μš©μžμ •λ³΄)κ°€ μ•„λ‹Œ "is-a" (μ‚¬λžŒ -> 동물)μΌλ•Œ, 기반이 λ˜λŠ” 베이슀 클래슀λ₯Ό μž¬μ‚¬μš©ν•  수 있으며 베이슀 클래슀λ₯Ό μˆ˜μ •ν•΄ νŒŒμƒλœ ν΄λž˜μŠ€λ“€μ„ μˆ˜μ •ν•˜κ³  μ‹Άμ„λ•ŒλŠ” 상속을 μ‚¬μš©ν•˜λŠ”κ²ƒμ΄ 더 λ‚«λ‹€.

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

  // ...
}

// Employeesκ°€ tax dataλ₯Ό κ°€μ§€κ³  있기 λ•Œλ¬Έμ— 이 ν΄λž˜μŠ€λŠ” μ’‹μ§€μ•Šλ‹€.
// EmployeeTaxDataλŠ” 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);
  }
  // ...
}

객체지ν–₯ 5λŒ€ 원칙 SOLID

단일 μ±…μž„ 원칙(SRP, Single Responsibility Principle)

클린 μ½”λ“œμ—μ„œλŠ” 클래슀λ₯Ό μˆ˜μ •ν•˜λŠ” μ΄μœ κ°€ ν•œ κ°€μ§€λ₯Ό λ„˜μœΌλ©΄ μ•ˆλœλ‹€κ³  λ§ν•œλ‹€. 이것은 ν•˜λ‚˜μ˜ 클래슀의 λ„ˆλ¬΄ λ§Žμ€ κΈ°λŠ₯을 가득 μ±„μš°λŠ” 것(jam-pack)κ³Ό κ°™λ‹€. 이 λ¬Έμ œλŠ” ν΄λž˜μŠ€κ°€ κ°œλ…μ μœΌλ‘œ κ΅¬μ„±λ˜μ–΄μžˆμ§€ μ•ŠμœΌλ©° 클래슀λ₯Ό λ°”κΏ”μ•Όν•  λ§Žμ€ μ΄μœ κ°€ λœλ‹€. ν•˜λ‚˜μ˜ ν΄λž˜μŠ€μ— λ„ˆλ¬΄ λ§Žμ€ κΈ°λŠ₯듀이 있고 μ΄λŸ¬ν•œ μž‘μ€ κΈ°λŠ₯듀을 μˆ˜μ •μ΄ λ‹€λ₯Έ λͺ¨λ“ˆλ“€μ— μ–΄λ–€ 영ν–₯을 λΌμΉ˜μ§€λŠ”μ§€ μ•ŒκΈ° μ–΄λ ΅κΈ° λ•Œλ¬Έμ— 클래슀의 μˆ˜μ • μ‹œκ°„μ„ μ΅œμ†Œν™” ν•˜λŠ”κ²ƒμ΄ μ€‘μš”ν•˜λ‹€.

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

개방/폐쇄 원칙(OCP, Open/Closed Principle)

Bertrand Meyer에 μ˜ν•˜λ©΄ μ†Œν”„νŠΈμ›¨μ–΄ μ—”ν„°ν‹°λ“€(클래슀, λͺ¨λ“ˆ, ν•¨μˆ˜ λ“±)은 ν™•μž₯을 μœ„ν•΄μ„œ κ°œλ°©μ μ΄μ–΄μ•Όν•˜λ©° μˆ˜μ •μ‹œμ—λŠ” 폐쇄적이어야 ν•œλ‹€. 이 원칙은 기본적으둜 μ‘΄μž¬ν•˜λŠ” μ½”λ“œμ˜ μˆ˜μ • 없이 μ‚¬μš©μžκ°€ μƒˆλ‘œμš΄ κΈ°λŠ₯듀을 μΆ”κ°€ν•  수 μžˆλ„λ‘ ν—ˆμš©ν•΄μ•Όν•œλ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€.

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

λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙(LSP, Liskov Substitution Principle)

λ¦¬μŠ€μ½”ν”„ μΉ˜ν™˜ 원칙은 맀우 κ°„λ‹¨ν•˜μ§€λ§Œ κ°•λ ₯ν•œ κ°œλ…μ΄λ‹€. 이 μ›μΉ™μ˜ μ •μ˜λŠ” "λ§Œμ•½ Sκ°€ T의 ν•˜μœ„ νƒ€μž…(sub type)이면, ν”„λ‘œκ·Έλž¨ μ†μ„±μ˜ λ³€κ²½ 없이 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);

μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙(ISP, Interface Segregation Principle)

μžλ°”μŠ€ν¬λ¦½νŠΈμ—λŠ” μΈν„°νŽ˜μ΄μŠ€κ°€ μ—†κΈ° λ•Œλ¬Έμ— λ‹€λ₯Έ 원칙듀 만큼 μ—„κ²©ν•˜κ²Œ μ μš©λ˜μ§„ μ•Šμ§€λ§Œ νƒ€μž… μ‹œμŠ€ν…œμ΄ 없더라도 μ€‘μš”ν•˜κ³  κ΄€λ ¨μžˆλŠ” 원칙이닀.

μΈν„°νŽ˜μ΄μŠ€ 뢄리 원칙은 "ν΄λΌμ΄μ–ΈνŠΈκ°€ μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” μΈν„°νŽ˜μ΄μŠ€μ— μ˜μ‘΄ν•˜λ„λ‘ κ°•μš”ν•΄μ„œλŠ” μ•ˆλœλ‹€." κ·œμ •ν•˜κ³  있으며 μΈν„°νŽ˜μ΄μŠ€λ“€μ€ 덕타이핑 λ•Œλ¬Έμ— μ•”μ‹œμ μΈ contract이닀.

μΈν„°νŽ˜μ΄μŠ€ 뢄리 μ›μΉ™μ˜ 쒋은 μ˜ˆλŠ” λ§Žμ€ μ–‘μ˜ μ„€μ • 객체(large settings objects)κ°€ ν•„μš”ν•œ ν΄λž˜μŠ€μ΄λ‹€. λŒ€λΆ€λΆ„ λͺ¨λ“  섀정듀을 μ„ΈνŒ…μ„ μš”κ΅¬ν•˜λŠ”κ²ƒμ΄ μ•„λ‹ˆκΈ°λ•Œλ¬Έμ— ν΄λΌμ΄μ–ΈνŠΈκ°€ λ§Žμ€ μ–‘μ˜ μ˜΅μ…˜μ„ 선택할 ν•„μš”κ°€ μ—†λ‹€. 선택적인 섀정을 톡해 fat μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“œλŠ” 것을 λ°©μ§€ν•  수 μžˆλ‹€.

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

μ˜μ‘΄κ΄€κ³„Β μ—­μ „ 원칙(DIP, Dependency Inversion Principle)

이 원칙은 λ‘κ°€μ§€μ˜ ν•„μˆ˜ μš”μ†Œλ₯Ό κ°€μ§€κ³  μžˆλ‹€.

  1. μƒμœ„ 레벨의 λͺ¨λ“ˆμ€ ν•˜μœ„ 레벨의 λͺ¨λ“ˆμ— μ˜μ‘΄ν•΄μ„œλŠ” μ•ˆλœλ‹€. 두 λͺ¨λ“ˆ λ‹€ 좔상화에 μ˜μ‘΄ν•΄μ•Ό ν•œλ‹€.
  2. μΆ”μƒν™”λŠ” 세뢀사항에 μ˜μ‘΄ν•΄μ„œλŠ” μ•ˆλœλ‹€. 세뢀사항은 좔상화에 μ˜ν•΄ λ‹¬λΌμ Έμ•Όν•œλ‹€.

μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” μΈν„°νŽ˜μ΄μŠ€κ°€ μ—†κΈ°λ•Œλ¬Έμ— 좔상화에 μ˜μ‘΄ν•˜λŠ”κ²ƒμ€ μ•”μ‹œμ μΈ contractλ‹€. 즉, λ‹€λ₯Έ 객체와 ν΄λž˜μŠ€μ— λ…ΈμΆœλ˜λŠ” λ©”μ†Œλ“œμ™€ ν”„λ‘œνΌν‹°λ“€μ΄ μ•”μ‹œμ μΈ contractκ°€ λœλ‹€. μ•„λž˜μ˜ 예제 μ½”λ“œμ—μ„œμ˜ μ•”μ‹œμ μΈ contractλŠ” 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();


.. Testing, Concurrency, Error Handling, Formatting, Commentsm, Translation

Reference