[WEB/영화 검색] 코드 길이 60% 날려버리는 모듈화

y1nlog·2025년 1월 17일
0
post-thumbnail

이번에 할 일은

필수과제였던 메인 기능들을 완성하고서 거의 마지막 단계로 모듈화를 수행하려고 했다.

이번 단계에 할 일은

  1. 모듈화 및 코드 분리
    • API 요청과 관련된 코드와 DOM 조작 코드를 별도의 모듈로 분리하여, 코드의 재사용성과 유지보수성을 높이세요.
    • 예를 들어, API 요청은 api.js에서, UI 업데이트는 ui.js에서 처리하는 식으로 각각 분리합니다.
    • 중요 포인트: 함수로 모듈을 분리할 때 매개변수와 반환값을 명확히 정의하여 재사용성을 높이는 데 중점을 두세요.

그동안main.js에 차곡차곡 쌓아뒀던... 나의 213줄짜리 코드를 나눠줄 차례다.

워낙 길다 보니까, 이게 서로 종속적인 상태로 묶여있었는데.. 시작하려니 참 감이 안 왔다.

접근은 그냥 로직이 복잡하지 않은 기능 순서대로, 차근차근 옮겨보려 했다. 헤메이다가 깨달은 결론 먼저 스포하자면,

코드분리 작업은 작은 단위로 쪼개어 옮겨 테스트 해보는 게 중요하다!

트러블슈팅 로그

1. 배경

  • 메인 기능 개발 완료 후 모듈화 작업 중
    • card.js, modal.js 분리 및 디버깅 완료
      (UI상 기능 정상 확인!)
    • main.js 코드길이 213줄 -> 158줄...

목표한 최종 Structure

/PROJECT
└── src
	├── main.js # 메인 로직 구현 (아직 158줄)
	├── api.js # fetch 기능
    ├── ui.js # UI 조작 관련 기능
	├── card.js # 영화 카드 생성 기능 (CLR)
	├── modal.js # 모달 관련 기능 (CLR)
	└── bookmark.js # 북마크 주요 기능 (추후 개발 예정)

현재 주어진 달성 사항은 API 요청과 관련된 코드와 DOM 조작 코드를 별도의 모듈로 분리해야 한다.

나는 조금 더 욕심을 부려서.. 현재 목표한 프로젝트의 주요 기능들 (영화 카드 보여주기 / 영화 상세 모달화 / 북마크 추가 및 보여주기 기능)을 따로 모듈화하여 관리하고자 했다.

아니, 근데 문제는 API였다!!! 하라고 한 게 안되는 상황 ㅋㅋ 아니 모달이랑 카드는 잘 옮겨졌는데 말이지...

  • 문제 코드:
    1. [모듈화 전] main화면 내 trending movie - Card List 표출
let dataset = []; // 데이터셋을 담을 변수

fetch("https://api.themoviedb.org/3/trending/movie/day?language=ko", {
  method: "GET",
  headers: {
    accept: "application/json",
    Authorization:"API_KEY",
  },
})
  .then((res) => res.json()) // JSON으로 받아오기
  .then((res) => {
    let rows = res["results"]; // Result만 불러오기
    dataset = rows; // 굳이 한 코드

    // 평가한 사람이 많은 순으로 정렬 (내림차순)
    rows.sort((a, b) => b.vote_count - a.vote_count);

    // 각 영화에 대해 카드 생성
    rows.forEach((movie) => {
      const card = createMovieCard(movie);
      showCardsContainer(card);

      // 카드 클릭 시 openModal
      card.addEventListener("click", () => {
        openModal(movie.id, dataset);
      });
    });
  })
  .catch((err) => console.error(err));

2. [모듈화 전] 검색 기능 - Search/Movie

searchBtn.addEventListener("click", async function () {
  const searchQuery = searchTitle.value.trim().toLowerCase();

  if (!searchQuery) {
    alert(`[오류] 키워드를 입력해주세요!\n검색어가 입력되지 않았습니다.`);
    return;
  }

  try {
    const response = await fetch(
      `https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=ko`,
      {
        method: "GET",
        headers: {
          accept: "application/json",
          Authorization:
            "API_KEY",
        },
      }
    );
    if (!response.ok) {
      throw new Error("API 요청 실패");
    }
    const data = await response.json();
    const sortedSearchResult = data.results.sort(
      (a, b) => b.vote_count - a.vote_count
    );
    dataset = sortedSearchResult;

    movieList.innerHTML = ""; // 기존 목록 초기화
    if (sortedSearchResult.length > 0) {
      sortedSearchResult.forEach((movie) => {
        const card = createMovieCard(movie);
        showCardsContainer(card);
        card.addEventListener("click", () => {
          openModal(movie.id, dataset);
        });
      });
    }
    // 검색결과가 없을 때
    else {
      const noResultContainer = document.createElement("div");
      noResultContainer.classList.add("no-result-container");

      const noResultMessage = document.createElement("p");
      noResultMessage.textContent = "검색 결과가 없습니다.";

      noResultContainer.appendChild(noResultMessage);
      movieList.appendChild(noResultContainer);
    }
  } catch (error) {
    console.error(error); // 오류 발생 시 콘솔에 출력
  }
  backToMain();
});

코드 안에 이벤트 리스너부터 DOM 조작 명령어 등등이 혼재되어 있어서 API를 건들기가 어려웠던 것도 사실...
리팩토링도 너무 필요한 상황이라, 일단 main화면 내 trending movie 데이터를 불러와 Card List 표출하는 것부터 손을 댔다.

  • 리팩토링

api.js 파일을 생성해주고, 그 안에 fetch 구문을 길게 만들었던 이 녀석을 변수에 담아주었다.

const options = {
  method: "GET",
  headers: {
    accept: "application/json",
    Authorization:
      "API_KEY",
  },
};

async function getTrendingMovies(url) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      throw new Error(`error! status : ${response.status}`);
    }

    const data = await response.json();
    return data; // 데이터를 반환
  } catch (err) {
    console.error(err);
    throw new Error(`error! status : ${err.message}`);
  }
}

export { getTrendingMovies };

그래도 위의 코드보다는 조금 줄어든 상황이라, 여기까진 좋았다.


2. 문제 발생

  • 상황 :
    "main.js에서는 results 값만 받아오고, 동시에 정렬도 수행하고 오면 좋겠다!"는 생각을 했던 것..
    그래서 아래 코드에
async function getTrendingMovies(url) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      throw new Error(`error! status : ${response.status}`);
    }

    const data = await response.json();
    return data; // 데이터를 반환
  } catch (err) {
    console.error(err);
    throw new Error(`status : ${err.message}`);
  }
}

내가 붙이고 싶었던

const data = await response.json();
    const sortedSearchResult = data.results.sort(
      (a, b) => b.vote_count - a.vote_count
    );

이런 코드를 조합했다. 그러니까 아무리 main.js에서 getTrendingMovies를 불러와도 안되는 거다.. API 응답에 에러 발생.

3. 원인 추론

  1. API 응답 데이터 구조에 대한 불확실성
  • data.results.sort()를 수행하기 전에, data["results"]를 먼저 콘솔에 찍어보자.
  1. 문법 오류
  • 받아온 데이터가 배열이 맞는지 확인하기. sort() 메소드는 배열에만 작동한다.
  1. 비동기 처리
  • 비동기로 불러오는 데이터 처리가 잘 되었는지 확인하기. await가 제대로 동작하지 않으면, 데이터가 아직 로드되지 않았는데 .sort()를 호출하려고 할 수도 있어서..(?)

4. 해결 방안

[api.js]

import { API_KEY } from "./api-key.js";

const options = {
  method: "GET",
  headers: {
    accept: "application/json",
    Authorization: API_KEY,
  },
};

// [main] trending movie
async function getMovieData(url) {
  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      throw new Error(`status : ${response.status}`);
    }

    const data = await response.json();
    return data;
  } catch (err) {
    throw new Error(`status : ${err.message}`);
  }
}
  • API 모듈에서 욕심을 좀 버렸다.
  • 리팩토링 상태 그대로 유지한 채로, async/await으로 불러온 데이터를 다시 비동기로 JSON화 하여 바로 return하는 구조.

[main.js]

let dataset = [];
async function main() {
  try {
    dataset = await getMovieData(urlTrending);
    return dataset["results"].sort((a, b) => b.vote_count - a.vote_count);
  } catch (err) {
    throw new Error(`error! status : ${err}`);
  }
}
dataset = await main();
createCardNEvent(dataset);
  • 메인 화면 기능을 구현하는 파트.
  • 비동기로 불러온 trending 영화 데이터를 dataset 변수에 담아서, 성공적으로 fetch가 되면, dataset 중 영화 값들이 들어있는 results에 key를 기반으로 접근 후, sort 메소드를 적용한다.

그리고 main()을 또다시 비동기로 실행해준다는 점. 왜냐면 main.js 내에서도 main이라는 비동기 함수 안에 구성을 해 주어서..!

그래도 웹페이지 로드하는 데에는 큰 문제점이 없는 상황.

0. 노트

  • 문제 코드랑 다른 점이 많이 없지만, 실패코드 커밋을 제대로 안 해두어서 복기가 어렵다. 그때그때 기록하는 습관을 길러야겠다고 프로젝트 회고성으로 트러블슈팅을 작성하며 반성해 본다. (완벽하지 않은데 완벽해야 한다고 생각하니까 그런 것 같음) 그래서 오늘은 거의 코드리뷰 겸 프로젝트 회고로 작성된 듯 싶다.
  • 리팩토링이랑 모듈화의 개념을 이번에 처음 접하고, 직접 해 보면서.. 재사용을 위해서 얼마나 중요한지를 깨달았다. 처음 코드 분리하려고 213줄의 코드를 직면한 순간, 정말 막막했었음.
  • 뇌 정지 상태에서 극복한 방법은 찔끔찔끔 코드 옮기면서, console.log()찍어보고 테스트 하는 거. 그래도 스스로 이것저것 시도해보고 생소한 개념에 한 걸음 가까워진 경험이었다고 생각함.
profile
FrontEnd Developer

0개의 댓글