리액트 프로젝트를 리팩토링해보자 - 2편

imloopy·2022년 9월 14일
0

Today I Learned

목록 보기
48/56
post-custom-banner

이전

리액트 프로젝트를 리팩토링해보자 - 1편

프로젝트에서 사용할 새로운 환경 세팅을 완료했다.

이 과정에서 많은 리뷰를 거쳤고, 모든 팀원들의 합의 하에 새로운 환경이 만들어졌다.

큰 변경점들은 다음과 같다.

  • eslint rules
  • eslint로 정의할 수 없는 여러 팀 컨벤션
  • webpack 환경설정 정리
    • webpack.config.js를 타입스크립트로 재작성
    • 사용하지 않는 loader 정리
  • 커밋 및 PR Rules

어디서부터 리팩토링을 시작해야 할까?

프로젝트 환경 재설정 후, 바로 리팩토링 작업에 들어가고자 내가 작성했던 코드를 살펴보았다. 스파게티도 이런 스파게티가 없다.

처음 리팩토링을 시작할 부분으로 인터페이스 영역과 Presentational 영역에 대해서 고민했다. 어느 쪽을 선택하든 간에 모두 장단점이 있었다.

장점단점
API 영역Layer부터 정리 후에 작업을 시작할 수 있기 때문에, 좀 더 깔끔한 구조로 시작할 수 있다.- 후에 API를 변경할 예정이므로 다시 바꿔야한다.- API가 어디서 어떻게 사용되는 지 알 수 없으므로, 어디까지 수정해야 하는지 알 수 없다.
Presentational 영역구조를 바꾸더라도 영향을 미치는 범위가 적다. 나중에 다른 쪽에서 바뀌더라도 영향력이 작아서 수정에 부담이 없다.현재 구조로는 API단에 변화가 발생하면 Presentational 영역에도 수정이 필요하다.

표로 나타내보니까 더 문제점이 확실해진다. 레이어가 없어서 각 영역이 다른 영역에 의존하는 부분이 크다.

인터페이스부터 재정의 하려고 하니, 해당 수정이 미치는 영향 범위가 어디까지일지 감이 전혀 잡히지 않았다. 해당 API 영역을 내가 담당하는 부분에서만 사용한다면 부담 없이 수정을 할 수 있겠지만, 해당 API는 내가 작업하는 영역 뿐 아니라 다른 팀원들도 인터페이스를 변형하여 사용하고 있었다.

해당 부분은 바꾸기에는 이르다고 생각하여, view 영역부터 바꾸기로 결심했다. 현재 view 영역 또한 모듈화가 이루어지지 않은 상태이기 때문에, api 영역에서 변경이 일어난다면, 어디까지 수정해야 할 지 경계가 명확하지 않다는 문제점이 있다. 물론 view 영역을 수정하더라도 api 영역이 수정된다면 다시 바꿔야 하는 것은 동일하지만, view가 모듈화 되어있다면 수정 범위가 한정되고, 수정에도 부담이 없을 것이라 생각했다.

따라서 현재 view 영역의 리팩토링이 조금 더 시급하다고 생각하여 해당 부분을 먼저 리팩토링하였다.

컴포넌트로부터 로직을 분리하기

가장 먼저, 컴포넌트에서 로직 분리를 시도했다. 현재 QuizSolvePage와 QuizResultPage에서 공통적으로 사용되는 quiz 데이터들을 하나의 hook으로 묶었다. 그 이유는 데이터 관점으로 컴포넌트를 추상화 했을 때, 데이터와, 해당 데이터를 다루는 메서드들이 남기 때문에, 해당 데이터와 관련된 메서드들을 모두 useQuiz라는 hooks을 만들어 이동시켰다.

// 이전 코드
// QuizSolvePage.tsxconst QuizSolvePage = () => {

const QuizSolvePage = () => {
  const [quizzes, setQuizzes] = useQuiz()
  
  useEffect(() => {
    (async () => {
      getQuizzes().then((data) => {
        setQuizzes(data)
        // other logics
      })
    })()
  }, [])
}
// 현재 코드
// useQuiz.ts
const useQuiz = () => {
  const [quizzes, setQuizzes] = useState([])
  
  const getQuizzes = useCallback(async (count: number) => {
    try {
      const data = await getQuizzes(count);
      setQuizzes(data);
    } catch (error) {
      console.error(error);
    }
  }, []);
  
  // ...more
  return { quizzes, getQuizzes }
}

// QuizSolvePage.tsx
const QuizSolvePage = () => {
  useEffect(() => {
  
    const fetchData = async () => {
      try {
        if (randomQuizCount && randomQuizCount > 0) {
          await getRandomQuizzes(randomQuizCount);
        } else if (channelId) {
          await getQuizzesFromQuizset(channelId);
        }
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    };
  
    fetchData();
  }, [])

  return (
    <div></div>
  )
}

현재는 그냥 함수를 옮겨놓은 것이기 때문에, 큰 차이가 없어 보일수도 있다. 그러나 이것은 큰 변화의 시작이다.

  • useQuiz hook에 quiz와 quiz를 다루는 메서드들이 함께 존재하므로 응집도가 증가했다.
  • useQuiz hook의 의존성 변화가 useQuiz hook을 사용하는 컴포넌트에 영향을 미치지 않으므로, 영향 범위가 제한된다.

즉, useQuiz hook이 존재함으로써 컴포넌트가 앞으로 다가올 큰 변화에 좀 더 유연하게 대처할 수 있게 되었다.

앞으로 계획

  • useQuiz hook을 바탕으로 QuizSolvePage에서 사용할 데이터들을 묶어서 hook으로 분리하기
  • quiz service 레이어를 만들거나, helper.ts 로 이동시켜 api 영역과 유스케이스 영역을 분리하기

여러 고민들

현재 하고 있는 고민은, QuizSolvePage와 QuizResultPage의 도메인을 분리할지 말 지 여부이다. 현재 두 페이지는 모두 Quiz라는 데이터를 공유하고, 문제는 없다.

그러나 현재 서버에서 내려져 오는 데이터는 PostAPI이고, 이를 바탕으로 Quiz 인터페이스를 정의하고 있다. 그러나 이 QuizInterface는 기본적으로 PostAPI interface를 상속하므로 Quiz와 관련이 없는 데이터들 역시 존재한다. 이렇게 하나의 데이터를 공유하게 될 경우 다음과 같은 문제점이 있다.

  • QuizSolvePage에서 사용하지 않는 데이터들까지 존재하여, 불필요한 메모리 낭비
  • Quiz와 관계없는 데이터의 존재로 관심 밖의 데이터들이 존재
  • 추후 API 변경 시 Post와 Quiz 정보가 분리될 예정이다. 의존성 분리가 필요하다.

데이터를 불러오기 위해 사용하는 API 역시 다르기 때문에 도메인을 분리하는 것이 좋다고 생각한다. 수정하기 전에 다른 팀원들의 의견 역시 한번 들어봐야겠다.

느낀 점

리팩토링을 정말 좋아하지만, 이렇게 엉켜있을 경우 어디서부터 손 대야할 지 모르겠다. 다른 프로젝트를 진행하고, 또 관련 지식들을 쌓고 돌아와보니 그 때는 정말 최선이라고 느꼈던 코드들이 스파게티처럼 꼬여 보였다. 다른 프로젝트를 진행하면서 개발자로서 성장했나…?

책이나 인터넷 블로그에서 칼럼으로만 보던 레이어를 나누는 것의 중요성을 직접 경험했다. 현재 코드가 한 쪽이 변경됨에 따라 다른 쪽이 무더기로 변경될 가능성이 있음을 깨닫고, 이전에 변경에 너무나도 취약하게 코드를 작성했음을 깨달았다. 이번 리팩토링 작업을 하면서 레이어를 좀 더 확실하게 나눠 변경에 유연하고, 변경이 두렵지 않은 로직 및 컴포넌트를 만드는 것이 목표다.

post-custom-banner

0개의 댓글