TIC-TAC-TOE) 코드 리팩터링

pengooseDev·2023년 2월 1일
0
post-thumbnail

TIC-TAC-TOE?

현재 진행하고 있는 co-MusicPlayer 프로젝트이다.

리팩터링

Spring boot와의 협업이 처음이라 BE에서 열어둔 EndPoint로 요청을 보내고 응답을 정상적으로 받는지 확인하는 컴포넌트를 작성하였다.

//search.tsx
import axios from 'axios';

const GetData = () => {
  const [searchData, setSearchData] = useState('');

  const getSearch = await() => {
    const response = axios.get('/api/hello');
	setSearchData((prev) => response.data);    
  }

  return (
    <>
      <div>
        백엔드에서 가져온 데이터입니다 :
        {searchData ? searchData : '데이터 없음'}
      </div>
      <ToggleBtn onClick={getSearch}>search</ToggleBtn>
    </>
  );
};

export default GetData;

문제점 파악

코드를 복기하면 느낀 문제점은 아래와 같다.

  1. API 명세서에 따라 EndPoint로 계속해서 요청하는 코드를 작성해야 한다. 문제는 API를 요청하는 코드를 작성하는 코드마다, 위의 코드를 다시 작성해야 한다는 것이다. 즉, 재사용성이 없는 코드이다.
  2. 값이 하드코딩 되어있어 값이 변경될 경우, 취약하다.
  3. 위의 이유들 때문에 확장 및 유지보수가 어렵다. 기술부채 덩어리.

위의 문제점들을 고려하여 아래와 같이 리팩터링을 진행하였다.


리팩터링

1. 소프트 코딩

우선 하드코딩 되어있는 값들을 변경에 취약하지 않도록 소프트 코딩하였다.

// ./utils/contant/apiConstant.ts

/* Interfaces */
interface endPoint {
  [key: string]: string;
}

interface error {
  [key: string]: string;
}

interface api {
  [key: string]: endPoint | error;
}

/* CONSTANTS */
const END_POINT: endPoint = Object.freeze({
  HELLO: '/api/hello',
});

const ERROR: error = Object.freeze({
  ENONT: 'ENOENT',
  ENONT_MESSAGE: 'Unusable API EndPoint',
  PROTOCOL_MESSAGE: 'Protocol Error',
});

const API: api = Object.freeze({
  END_POINT: END_POINT,
  ERROR: ERROR,
});

export default API;
  1. TS의 interface를 이용한 Type 안정성 확보.

  2. 참조값인 Object type은 변화에 취약한 데이터 타입이다.
    따라서, const 키워드로 메모리의 변수 영역을 immutable 영역으로 고정시킨 뒤, Object.freeze를 이용해 Object의 데이터 영역. 즉, key와 value의 변수 영역을 참조하고 있는 메모리 공간(데이터 영역)을 immutable하게 고정한다.

    또한, Object.freeze의 depth는 1까지만 적용이 되기 때문에 값을 immutable화 하는 과정을 depth별로 진행하였다. 해당 부분은 nested Obejct의 구조의 depth가 깊어질수록 선언에 어려움이 있어 여기까지 글을 쓰고 해결책을 1시간 정도 고민하고 찾다가 다시 돌아왔다. (추후 포스팅 예정)


2. 재사용 가능한 모듈화

import axios from 'axios';
import API from '../constants/apiConstant';

const sendGetRequest = async (END_POINT: string) => {
  const { ERROR } = API;

  try {
    const searchResponse = await axios.get(END_POINT);
    const { data } = searchResponse;

    return data;
  } catch (error) {
    const ErrorType = error as protocolError;

    if (ErrorType.code === ERROR.ENONT) throw new Error(ERROR.ENONT_MESSAGE);
    throw new Error(ERROR.PROTOCOL_MESSAGE);
  }
};

export default sendGetRequest;

interface protocolError {
  code: string;
  message: string;
}

해당 함수는 BE와 소통하는 과정에서 반복적으로 사용될 함수라고 판단이 되었다. 이전 코드는 HTTP 통신을 진행하는 Endpoint가 함수 내부에 있기 때문에 의존성이 발생한다고 판단하였다. 따라서, 해당 부분을 함수 외부로 빼내어, 이를 매개변수로 받도록 함수를 작성하였다.

물론, 현재 위 함수의 한계는 명확하다.
오직 get 메서드만을 처리하고 있기 때문이다.

따라서, 이후 리팩터링 과정은 메서드를 받는 인자를 추가하여 이에 따른 올바른 요청을 진행하고, 결과를 return하는 함수를 작성하는 방향으로 진행할 것이다.


3. 결과

//search.tsx
import { useState } from 'react';
import sendGetRequest from './utils/req/sendGet';
import API from './utils/constants/apiConstant';

const Search = () => {
  const [searchData, setSearchData] = useState('');
  const {
    END_POINT: { HELLO },
  } = API;

  const getSearch = async () => {
    const response = await sendGetRequest(HELLO);
    setSearchData((prev) => response);
  };

  return (
    <>
      <div>
        백엔드에서 가져온 데이터입니다 :
        {searchData ? searchData : '데이터 없음'}
      </div>
      <ToggleBtn onClick={getSearch}>getSearch</ToggleBtn>
    </>
  );
};

search.tsx의 코드는 크게 변하지 않았지만, 코드의 안정성, 확장성, 재사용성 및 유지보수성은 올랐다는 것을 느낄 수 있었다.

이전까지 잘 지키지 않았던 자체적 리팩터링 및 코드리뷰가 점점 익숙해지는 과정인 것 같다.


리팩터링을 하며 생긴 궁금증 - 왜 Object.freeze()와 Spread Operator의 deep.copy depth는 1일까?

0개의 댓글