ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자바스크립트 클린 코드
    Note 2021. 8. 21. 17:56

    클린 코드로 가독성을 높이는 것이 개발할 때 중요하다고 한다. 보통 개발을 할 때 여러 명의 개발자와 함께 협업이 이루어지는데 다른 사람이 읽기 쉽고 가독성이 좋으면 시간도 절약될 것이고 유지 보수하는데 그만큼 유리해지게 된다.

     

    주석 없이도 코드 자체로 스스로 설명이 되고 쉽게 이해될 수 있는 코드는 과연 어떻게 써야 할까?

     

     

    변수(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` 전역 변수를 선언
    const MILLISECONDS_IN_A_DAY = 86400000;
    setTimeout(blastOff, MILLISECONDS_IN_A_DAY);

     

    자신만 알아볼 수 있는 작명을 피하자

    명시적인 것이 암시적인 것보다 좋다.

     

    Bad 😭

    const locations = ['서울', '인천', '수원'];
    locations.forEach(l => {
      doStuff();
      doSomeOtherStuff();
      // ...
      // ...
      // ...
      // 잠깐, `l`은 또 뭘까?
      dispatch(l);
    });

     

    Good 😍

    const locations = ['서울', '인천', '수원'];
    locations.forEach(location => {
      doStuff();
      doSomeOtherStuff();
      // ...
      // ...
      // ...
      dispatch(location);
    });

     

     

    함수(Functions)

     

    함수 인자는 2개 이하가 이상적

    매개변수의 개수를 제한하는 것은 함수 테스팅을 쉽게 만들어 주기 때문에 중요하다. 만약 매개변수가 3개 이상일 경우엔 테스트해야 하는 경우의 수가 많아지고 각기 다른 인수들로 여러 사례들을 테스트해야 한다.

     

    1개나 2개의 인자를 가지고 있는 것이 가장 이상적인 케이스이다. 그리고 3개의 인자는 가능한 피해야 한다. 그것보다 더 많다면 통합되어야 한다. 만약 당신이 2개 이상의 인자를 가진 함수를 사용한다면 그 함수에게 너무 많은 역할을 하게 만든 것이다.

     

    함수가 기대하는 속성을 좀 더 명확히 하기 위해서 es6의 비구조화(destructuring) 구문을 사용할 수 있고 이 구문에는 몇 가지 장점이 있다.

    • 어떤 사람이 그 함수의 시그니쳐(인자의 타입, 반환되는 값의 타입 등)를 볼 때 어떤 속성이 사용되는지 즉시 알 수 있음
    • 또한 비구조화는 함수에 전달된 인수 객체의 지정된 기본 타입 값을 복제하며 이는 사이드 이펙트가 일어나는 것을 방지한다. 참고로 인수 객체로부터 비구조화된 객체와 배열은 복제되지 않음
    • Linter를 사용하면 사용하지 않는 인자에 대해 경고해주거나 비구조화 없이 코드를 짤 수 없게 할 수 있음

     

    Bad 😭

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

     

    Good 😍

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

     

    함수는 하나의 행동만 해야 한다.

    이것은 소프트웨어 엔지니어링에서 가장 중요한 규칙이다. 함수가 1개 이상의 행동을 한다면 작성하는 것도, 테스트하는 것도, 이해하는 것도 어려워진다. 하나의 함수에 하나의 행동을 정의하는 것이 가능해진다면 함수는 좀 더 고치기 쉬워지고 코드들은 읽기 쉬워질 것이다.

     

    Bad 😭

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

     

    Good 😍

    function emailClients(clients) {
      clients
        .filter(isClientActive)
        .forEach(email);
    }
    
    function isClientActive(client) {
      const clientRecord = database.lookup(client);
      return clientRecord.isActive();
    }

     

    조건문을 캡슐화

     

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

     

    조건문 작성을 피하라

     

    조건문 작성을 피하라는 것은 매우 불가능한 일로 보인다. 이 얘기를 처음 듣는 사람들은 대부분 "If문 없이 어떻게 코드를 짜나요?"라고 말할 것이다. 하지만 다형성을 이용한다면 동일한 작업을 수행할 수 있다. 두 번째 질문은 보통 "네 좋네요 근데 내가 왜 그렇게 해야 하나요?" 그에 대한 대답은, 앞서 우리가 공부했던 clean code 콘셉트에 있다. 함수는 단 하나의 일만 수행하여야 한다. 함수나 클래스에 if문을 쓴다면 그것은 그 함수나 클래스가 한 가지 이상의 일을 수행하고 있다고 말하는 것과 같다. 기억하자, 하나의 함수는 딱 하나의 일만 해야 한다.

     

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

     

    과도한 최적화를 지양

     

    최신 브라우저들은 런타임에 많은 최적화 작업을 수행한다. 대부분 당신이 코드를 최적화 하는 것은 시간낭비일 가능성이 많다. 최적화가 부족한 곳이 어딘지를 알려주는 좋은 자료가 여기 있다. 이것을 참조하여 최신 브라우저들이 최적화 해주지 않는 부분만 최적화를 해주는 것이 좋다.

     

    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) {
      // ...
    }
    
    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');

     

     

    에러 처리(Error Handling)

     

    에러를 뱉는다는 것은 좋은 것이다! 즉, 프로그램에서 무언가가 잘못되었을 때 런타임에서 성공적으로 확인되면 현재 스택에서 함수 실행을 중단하고 (노드에서) 프로세스를 종료하고 스택 추적으로 콘솔에서 사용자에게 그 이유를 알려준다.

     

    단순히 에러를 확인만 하지말자

     

    단순히 에러를 확인하는 것만으로 그 에러가 해결되거나 대응할 수 있게 되는 것은 아니다. console.log를 통해 콘솔에 로그를 기록하는 것은 에러 로그를 잃어버리기 쉽기 때문에 좋은 방법이 아니다. 만약에 try/catch로 어떤 코드를 감쌌다면 그건 당신이 그 코드에 어떤 에러가 날지도 모르기 때문에 감싼 것이므로 그에 대한 계획이 있거나 어떠한 장치를 해야 한다.

     

    Bad 😭

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

     

    Good 😍

    try {
      functionThatMightThrow();
    } catch (error) {
      // 첫번째 방법은 console.error를 이용하는 것. 이건 console.log보다 조금 더 알아채기 쉬움
      console.error(error);
      // 다른 방법은 유저에게 알리는 방법
      notifyUserOfError(error);
      // 또 다른 방법은 서비스 자체에 에러를 기록하는 방법
      reportErrorToService(error);
      // 혹은 그 어떤 방법이 될 수 있음
    }

     

    Promise가 실패된 것을 무시하지 말자

    Bad 😭

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

     

    Good 😍

    getdata()
    .then(data => {
      functionThatMightThrow(data);
    })
    .catch(error => {
      // 첫번째 방법은 console.error를 이용하는 것. 이건 console.log보다 조금 더 알아채기 쉬움
      console.error(error);
      // 다른 방법은 유저에게 알리는 방법
      notifyUserOfError(error);
      // 또 다른 방법은 서비스 자체에 에러를 기록하는 방법
      reportErrorToService(error);
      // 혹은 그 어떤 방법이 될 수 있음
    });

     

     

    주석(Comments)

     

    비즈니스 로직이 복잡한 경우에만 주석을 다는 것이 좋다.

    주석을 다는 것은 사과해야 할 일이며 필수적인 것이 아니다. 좋은 코드는 코드 자체로 말한다.

     

    Bad 😭

    function hashIt(data) {
      // 이건 해쉬
      let hash = 0;
    
      // lengh는 data의 길이.
      const length = data.length;
    
      // 데이터의 문자열 개수만큼 반복문을 실행
      for (let i = 0; i < length; i++) {
        // 문자열 코드를 얻음
        const char = data.charCodeAt(i);
        // 해쉬를 만듦
        hash = ((hash << 5) - hash) + char;
        // 32-bit 정수로 바꿈
        hash &= hash;
      }
    }

     

    Good 😍

    function hashIt(data) {
      let hash = 0;
      const length = data.length;
    
      for (let i = 0; i < length; i++) {
        const char = data.charCodeAt(i);
        hash = ((hash << 5) - hash) + char;
    
        // 32-bit 정수로 바꿈
        hash &= hash;
      }
    }

     

    주석으로 된 코드를 남기지 말자

     

    버전 관리 도구가 존재하기 때문에 코드를 주석으로 남길 이유가 없다.

     

    Bad 😭

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

     

    Good 😍

    doStuff();

     

     

     

     

     

     

    참고 자료

    'Note' 카테고리의 다른 글

    Portals  (0) 2021.08.30
    옵셔널 체이닝  (0) 2021.08.24
    이벤트 루프  (0) 2021.08.13
    브라우저 작동 원리  (0) 2021.08.10
    반응형 테이블(Responsive Table)  (0) 2021.08.06

    댓글