공부기록/바닐라 자바스크립트

자바스크립트 코드 리팩토링

_우지 2022. 7. 19. 20:01

 

 

 

주석을 표시해두었습니다 a,b를 찾아보세요.

현재 각각의 생성자 함수에서 a,b 와 같은 방법으로 new 키워드를 사용했는지 아닌지를 판단하고 있습니다.

이러한 코드를 함수로 밖으로 빼면 더 간결하게 코드를 짤 수 있는데요. 

    <script>
      // 이곳에서 코딩을 시작하세요!
      function Validator() {
        if (!(this instanceof Validator)) {   // a
          console.log(this instanceof Validator);
        }
        ...
      }

      function TodoList(data, $app) {
        if (!(this instanceof TodoList)) { // b
          console.log(this instanceof TodoList);
          throw new Error(ErrorMessage.EXCEPT_NEW);
        }
     	...
      }
      const $target = document.querySelector('.App');
      const validData = new Validator();
      try {
        const todoList = new TodoList(validData.validate(data), $target);
        const todoList2 = new TodoList(validData.validate(data2), $target);
        const todoList3 = new TodoList(validData.validate(data3), $target);
        setTimeout(() => {
          todoList.setState(data4, validData);
        }, 2000);
      } catch (e) {
        console.log(e.message);
        alert('뭔가 문제가 있습니다. 개발팀을 불러주세요.');
      }
    </script>

 

주석은 회색배경 글자로 편의상 쓰겠습니다.

isInstanceof 함수, a , b 에 주목해주세요.

isInstanceof 함수를 만들어줌으로써 중복되는 코드를 함수를 사용하여 리팩토링한 모습입니다.

    <script>
      // 이곳에서 코딩을 시작하세요!
      const isInstanceof = (context, constructor) => { // isInstanceof 함수
        if (!(context instanceof constructor)) {
          throw new Error(ErrorMessage.EXCEPT_NEW);
        }
      };
      function Validator() {
        isInstanceof(this, Validator);  // a
        ...
      }

      function TodoList(data, $app) {
        isInstanceof(this, TodoList); // b
        ...
      }
      const $target = document.querySelector('.App');
      try {
        const validData = new Validator();
        const todoList = new TodoList(validData.validate(data), $target);
        const todoList2 = new TodoList(validData.validate(data2), $target);
        const todoList3 = new TodoList(validData.validate(data3), $target);
        setTimeout(() => {
          todoList.setState(data4, validData);
        }, 2000);
      } catch (e) {
        console.log(e.message);
        alert('뭔가 문제가 있습니다. 개발팀을 불러주세요.');
      }
    </script>

 

너무 길어져서 접은글로 리팩토링 전 , 리팩토링 후 코드를 나타내겠습니다.

리팩토링 전

더보기

처음에 내가 짠 로직은 Validator 라는 함수를 만들었다. 

그후 Validator 안의 this.validate 라는 메소드를 만들었다. 

우선 이 로직으로 data를 validate 하기 위해서는 Validator라는 생성자함수를 호출을 해야했다.

이게 로직을 짤 때는 몰랐지만 필요없는 코드였다.

 <script>
      // 이곳에서 코딩을 시작하세요!
      function Validator() {
        if (!(this instanceof Validator)) {
          console.log(this instanceof Validator);
          throw new Error(ErrorMessage.EXCEPT_NEW);
        }
        this.isFalsy = (todoList) => {
          if (!todoList) {
            throw new Error(ErrorMessage.FALSY_VALUE);
          }
        };
        this.isNotArray = (todoList) => {
          if (!Array.isArray(todoList) || !todoList.length) {
            throw new Error(ErrorMessage.ISNOT_ARRAY);
          }
        };

        this.isNotObject = (todoList) => {
          if (typeof todoList !== 'object') {
            throw new Error(ErrorMessage.ISNOT_OBJECT);
          }
        };

        this.isNotValidData = (todoList) => {
          if (
            todoList.some(
              (item) =>
                !item ||
                !item.hasOwnProperty('text') ||
                !item.hasOwnProperty('isCompleted')
            )
          ) {
            throw new Error(ErrorMessage.VALIDDATA);
          }
        };
        this.validate = (todoList) => {
          this.isFalsy(todoList);
          this.isNotArray(todoList);
          this.isNotObject(todoList);
          this.isNotValidData(todoList);
          return todoList;
        };
      }

      function TodoList(data, $app) {
        if (!(this instanceof TodoList)) {
          console.log(this instanceof TodoList);
          throw new Error(ErrorMessage.EXCEPT_NEW);
        }

        this.data = data;
        this.$target = document.createElement('ul');
        $app.appendChild(this.$target);
        this.render = () => {
          this.$target.innerHTML = `<ul>${this.data
            .map(({ text, isCompleted }) =>
              isCompleted ? `<li><s>${text}</s></li>` : `<li>${text}</li>`
            )
            .join('')}</ul>`;
        };
        this.setState = (nextData, validData) => {
          validData.validate(nextData);
          this.data = nextData;
          this.render();
        };
        this.render();
      }
      const $target = document.querySelector('.App');
      const validData = new Validator();
      try {
        const todoList = new TodoList(validData.validate(data), $target);
        const todoList2 = new TodoList(validData.validate(data2), $target);
        const todoList3 = new TodoList(validData.validate(data3), $target);
        setTimeout(() => {
          todoList.setState(data4, validData);
        }, 2000);
      } catch (e) {
        console.log(e.message);
        alert('뭔가 문제가 있습니다. 개발팀을 불러주세요.');
      }
    </script>

리팩토링 후 

더보기

우선 Validator 함수에 있던 메소드를 모두 밖으로 빼준 모습이다.

isInstanceof, isFalsy, isNotArray, isNotArray, isNotObject 가 그 예시에 해당된다.

이제 이를 사용하기위해서 TodoList 생성자 함수 내부에 this.validate 메소드를 만들어주는 것이다.

그러면 나의 리팩토링 이전 코드처럼 Validator를 따로 호출할 필요도 없을 뿐더러

new TodoList( ) 를 호출할때 파라미터의 데이터를 검증하게 되므로 더욱 간단한 코드가 된다.

 

하나 헷갈렸던 부분이 있다. TodoList 생성자 함수 내부에 this.validate 메소드를 만들때 파라미터로 this.state를 넘겨주는 것을 볼 수 있다. 근데 이 로직을 이해할 당시 `어? this.state가 할당되기 전인데?` 라는 생각을 했다. 

그러나 문제가 되지않은점은 this.validate()를 호출하는 시점이 this.state가 할당이 되고 난 다음이기 때문이다.

 

또한 타입스크립트를 사용하면 이러한 validate과정이 필요가 없어진다.

 <script>
      // 이곳에서 코딩을 시작하세요!
      const isInstanceof = (context, constructor) => {
        if (!(context instanceof constructor)) {
          throw new Error(ErrorMessage.EXCEPT_NEW);
        }
      };
      const isFalsy = (todoList) => {
        if (!todoList) {
          throw new Error(ErrorMessage.FALSY_VALUE);
        }
      };
      const isNotArray = (todoList) => {
        if (!Array.isArray(todoList) || !todoList.length) {
          throw new Error(ErrorMessage.ISNOT_ARRAY);
        }
      };

      const isNotObject = (todoList) => {
        if (typeof todoList !== 'object') {
          throw new Error(ErrorMessage.ISNOT_OBJECT);
        }
      };

      const isNotValidData = (todoList) => {
        if (
          todoList.some(
            (item) =>
              !item ||
              !item.hasOwnProperty('text') ||
              !item.hasOwnProperty('isCompleted')
          )
        ) {
          throw new Error(ErrorMessage.VALIDDATA);
        }
      };

      function TodoList(data, $app) {
        this.validate = () => {
          isInstanceof(this, TodoList);
          isFalsy(this.data);
          isNotArray(this.data);
          isNotObject(this.data);
          isNotValidData(this.data);
        };

        this.data = data;

        this.validate();

        this.$target = document.createElement('ul');
        $app.appendChild(this.$target);
        this.render = () => {
          this.$target.innerHTML = `<ul>${this.data
            .map(({ text, isCompleted }) =>
              isCompleted ? `<li><s>${text}</s></li>` : `<li>${text}</li>`
            )
            .join('')}</ul>`;
        };
        this.setState = (nextData, validData) => {
          this.data = nextData;
          this.validate();
          this.render();
        };
        this.render();
      }
      const $target = document.querySelector('.App');
      try {
        const todoList = new TodoList(data, $target);
        const todoList2 = new TodoList(data2, $target);
        const todoList3 = new TodoList(data3, $target);
        setTimeout(() => {
          todoList.setState(data4, validData);
        }, 2000);
      } catch (e) {
        console.log(e.message);
        alert('뭔가 문제가 있습니다. 개발팀을 불러주세요.');
      }
    </script>