Clean Code Javascript (2) - 함수편

December 31, 2019

목적

  • 함수편은 코드를 재사용 할 수 있는 요소인 함수에 중점을 둔 기본 팁을 모았습니다.

단락 또는 조건부 변수를 사용하는 대신 기본 인수를 사용합니다.

  • 대부분의 프로그래밍 언어에서는 함수에 기본 인수를 정의할 수 있습니다.
  • 이를 통해 코드 본문에서 조건부 또는 단락에 대한 로직을 처리할 필요가 없습니다.
  function setName(name) {
    const newName = name || ‘Juan Palomo’;
  }
  
  // function내 기본 인수 사용
  function setName(name = ‘Juan Palomo’) {
    // …
  }

두개 또는 그 이상의 함수를 인수로 사용하는 경우

  • 이 개념은 코드 품질을 향상시키는데 필수적인 항목입니다.
  • 함수인수에 대한 수를 줄여야 합니다.
  • 적절한 숫자는 2개 이하 이지만 특정 프로그래밍 언어에 따라 달라질 수 있으므로 숫자에 집착하지 않으셔도 됩니다.
  • 보통 Function은 하나의 객체를 구성하여 그룹화 되기 때문에, 추상화 수준이 높은 객체를 사용하는 것이 좋습니다.
  function newBurger(name, price, ingredients, vegan) {
    // …
  }
  
  function newBurger(burger) {
    // …
  }
  
  function newBurger({ name, price, ingredients, vegan }) {
    // …
  }
    
  const burger = {
    name: ‘Chicken’,
    price: 1.25,
    ingredients: [‘chicken’],
    vegan: false,
  };
  newBurger(burger);
  • 첫번째 function에서는 햄버거의 독창적인 기능을 위해서 네가지 매개 변수를 받습니다.

    • 이때의 매개변수는 고정되어 있으며, 순서에 따라 많은 제한이 따릅니다.
    • 이로써 Function내에 엄격한 기능으로 들어갑니다.
  • 같은 내용을 사용하여 두번째 function에서 새로운 햄버거를 만드는 것이 상당히 개선되었습니다.

    • 이와 같은 방식으로 속성을 단일 객체로 그룹화 할 수 잇습니다.
  • 세번째 Function에서 우리는 전송된 객체에 대해 구조화를 사용 가능하며, 함수 본문의 속성에 엑세스가 가능해집니다.

    • 단일 매개변수를 사용하기 때문에 유연성이 향상될 수 있습니다.

사이드 이펙트 방지하기 - 전역 변수 사용

  • 사이드이펙트는 미래에 문제를 야기할 수 있는 원인이 됩니다.
  let fruits = ‘Banana Apple’;
  function splitFruits() {
    fruits = fruits.split(‘ ‘);
  }
  splitFruits();
  console.log(fruits); // [‘Banana’, ‘Apple’];
  function splitFruits(fruits) {
    return fruits.split(‘ ‘);
  }
    
  const fruits = ‘Banana Apple’;
  const newFruits = splitFruits(fruits);
    
  console.log(fruits); // ‘Banana Apple’;
  console.log(newFruits); // [‘Banana’, ‘Apple’];
  • 함수가 범위를 벗어난 변수 또는 객체를 수정하는 전형적인 사이드 이펙트 관련 예제입니다.
  • 이 함수는 테스트할 인수가 없으므로 테스트가 불가능합니다.
  • 실제로, 수정하는 변수의 상태는 함수에 의해 제어되거나 관리가 되지 않는 형태가 됩니다.
  • 함수의 범위 내에 있는 변수 (시간이 지남에 따라 기억해야 하는 경우에는 분명하지 않은 변수)를 인수로 전달시키는 것입니다.

사이드 이펙트 방지하기 - Object 변이 (Mutable)

  • 코드의 다른 부분을 동일한 객체를 사용하여 데이터를 수정할 때 더 많은 사이드 이펙트가 발생할 수 있습니다.
  • Javascript에서 배열 데이터 구조와 함께 작동하는 메소드는 오브젝트를 변경하는 메소드와 그렇지 않은 메소드로 구분됩니다.
  const addItemToCart = (cart, item) => {
    cart.push({ item, date: Date.now() });
  }; 
  const addItemToCart = (cart, item) => {
    return […cart, {
      item, 
      date: Date.now(),
    }];
  };

함수는 한가지만 수행할 수 있어야 합니다.

  • 프로그래밍의 기본적인 원칙 중 하나입니다.
  • 각 기능은 하나의 개념적인 작업만 수행할 수 있어야 합니다.
  • 일련의 작은 작업이 더 큰 작업을 수행하나 작업이 혼합되는 경우를 커플링이라고 합니다.
  • 함수는 한가지 기능만 수행할 수 있어야 합니다.
  function emailCustomers(customers) {
    customers.forEach((customer) => {
      const customerRecord = database.find(customer);
      if (customerRecord.isActive()) {
        email(client);
      }
      });
  }
  • 위의 예제는 클라이언트 목록을 수신해서 고객에게 이메일을 보내는 기능을 모델링 했습니다.
  • 개념상 이는 단순한 비즈니스 규칙이지만, 이는 구현하는 과정에서 명확하게 차별화된 두 가지 작업을 지니고 있습니다.
  • 우리는 활성 상태인 사용자를 필터링 해야하며, 이전 사용자와 독립적인 기능으로 작동됩니다.
  • if 의 남용은 지양했으면 합니다. 활성화된 클라이언트를 필터링 한 후 각 클라이언트에게 메일을 전송하는 다른 기능이 필요합니다.
  function emailActiveCustomers(customers) {
    customers
      .filter(isActiveCustomer)
      .forEach(email);
    }
    
  function isActiveCustomer(customer) {
    const customerRecord = database.find(customer);
    return customerRecord.isActive();
  }

함수는 추상화의 한 수준 이어야 합니다.

  • 함수를 디자인 할 때 충족해야 할 또 다른 요구 사항은 각 함수에 단일 추상화 레벨만 있어야 한다는 것입니다.
  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…
    });
  }
  • 상기 예제를 해결하는 내용은 간단합니다.
  • 다양한 수준의 추상화를 식별하고, 전체에 설명된 요구사항을 충족하는 함수를 작성하면 됩니다.
  • 리팩토링을 적용한 후의 기능은 다음과 같습니다.
  const REGEXES = [ // …];
  function tokenize(code) { 
    const statements = code.split(‘ ‘);
    const tokens = [];
    REGEXES.forEach((REGEX) => {
      statements.forEach((statement) => {
        tokens.push( /* … */ );
      });
    });
    return tokens;
  }
  
  function lexer(tokens) {
    const ast = [];
    tokens.forEach((token) => ast.push( /* */ ));
    return ast;
  }
  
  function parseBetterJSAlternative(code) {
    const tokens = tokenize(code);
    const ast = lexer(tokens);
    ast.forEach((node) => // parse…);
  }

명령형 프로그래밍 보다 기능적 프로그래밍을 선호합니다.

  • 패러다임의 경우에는 다양할 수 있으므로, 기능적 패러다임에 대한 간략한 설명만 짚고 넘어가겠습니다.
  • Alvin Alexander의 블로그를 읽어보는 것이 좋습니다.
  • 함수형 프로그램을 사용하는 주요 이점을 요약했습니다.

    • 순수한 기능은 추론하기 더 쉽습니다.
    • 테스트가 더 쉬우며 속성 기반 테스트와 같은 기술에 적합합니다.
    • 디버깅이 더 쉽습니다.
    • 프로그램이 더 높은 수준으로 작성되므로 이해하기 쉽습니다.
    • 병렬 / 동시 프로그램이 더 쉽습니다.
  • 함수형 프로그래밍과 명령형 프로그래밍의 또 다른 특징은 코드가 더 가독성이 좋다는 것입니다.
  • 간단한 카운터 함수를 보겠습니다.

    • 몇 가지 변수를 명심하세요: total, items, i, length, price
    • 기능 구현에서는 총계, 가격 및 품목만 있습니다.
  const items = [{
      name: ‘Coffe’,
      price: 500
    }, {
      name: ‘Ham’,
      price: 1500
    }, {
      name: ‘Bread’,
      price: 150
    }, {
      name: ‘Donuts’,
      price: 1000
    }
  ];
  
  let total = 0;
  for (let i = 0; i < items.length; i++) {
    total += items[i].price;
  }
  
  // Good practice
  const total = items
    .map(({ price }) => price)
    .reduce((total, price) => total + price);

메소드 체인을 사용합니다.

  • 객체 또는 데이터 흐름에서 작동하는 기능을 설계할 때 일반적으로 단일 작업을 수행하는 기능입니다.
  • 이러한 function들은 단일 추상화 수준을 가지며 부작용이 발생하지 않습니다.
  • 복잡한 작업을 수행하기 위해서 여러 작업을 조합하게 됩니다.
  • 더 많은 코드를 읽을 수 있는 체인 방식이 개발됩니다.
  class Car {
    constructor({ make, model, color } = car) {
      /* */
    }
    
    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(WV,’Jetta’,’gray’);
  car.setColor(‘red’);
  car.save();
  
  // method chain 사용
  class Car {
    constructor({ make, model, color } = car){}
    
    setMake(make) {
      this.make = make;
      return this;
    }
    setModel(model) {
      this.model = model;
      return this;
    }
    setColor(color) {
      this.color = color;
      return this;
    }
    save() {
      console.log(this.make, this.model, this.color);
      return this;
    }
  }
  const car = new Car(WV,’Jetta’,’gray’)
  .setColor(‘red’)
  .save();
...