Skip to Content
영화 리뷰 미션복붙으로 늘어난 fetch 함수

복붙으로 늘어난 fetch 함수

카테고리: duplication · 이 패턴은 8기 PR 26건 중 12건에서 관찰됩니다.

popular/search 요청 함수를 통째로 복붙해 변경 지점을 늘린다.

getPopularMoviesgetSearchMovies가 URL 조립, fetch 옵션, ok 체크, 에러 throw까지 거의 동일한 코드를 반복합니다. 컨트롤러 단에서도 try-skeleton-fetch-render-catch 구조가 4~5곳에 그대로 복제되어, 헤더 한 줄만 바뀌어도 모든 사본을 찾아 고쳐야 합니다.

문제 코드

다음은 실제 8기 크루 PR에서 추출한 코드입니다. 작성자는 익명 처리하고 원본 PR 링크만 남깁니다.

사례 1

export async function getPopularMovies(pageNum) { const url = `${API_PATH.POPULAR_MOVIE}?page=${pageNum}&language=ko-KR`; const options = { method: "GET", headers: { Authorization: `Bearer ${API_KEY}` } }; const response = await fetch(url, options); if (!response.ok) throw new Error('...'); return response.json(); } export async function getSearchMovies(query, pageNum) { /* 동일 */ }

두 함수가 options/fetch/ok 체크를 통째로 복사해 두어 변경 지점이 두 배가 됩니다. 원본: PR #277  · src/api.ts

사례 2

(async () => { /* topRated */ })(); (async () => { renderSkeleton(); const movies = await errorTryCatch( async () => await getMoviePopular({ page }), (e) => { if (e.status_code == 22) { /* ... */ } alert("..."); } ); })();

alert 메시지와 status_code 22 분기가 4~5곳에 그대로 반복되어 에러 정책 변경이 일괄적이지 않습니다. 원본: PR #273  · src/main.ts

사례 3

export async function popularController() { try { movieListView.reset(); movieListView.skeletonRender(SKELETON_NUMBER); const popularMovies = await getMovies(page); // ... } catch (error) { /* ... */ } finally { movieListView.skeletonRemover(); } }

popular/search/morePopular/moreSearch 4개 컨트롤러가 동일한 try-catch-finally 구조를 복제합니다. 원본: PR #272  · src/controller/popularController.ts

스스로 진단해보기

해설을 펼치기 전에 다음 질문에 답한다.

  1. 두 함수의 차이점만 골라 매개변수로 추출했을 때 본문이 몇 줄이 남는지 센다.
  2. 공통 헬퍼로 묶었을 때 단점이 있다면 무엇인지 한 가지 적는다.
  3. 이 코드 변경 이력에서 같은 내용을 두 번 수정한 흔적을 찾는다.

해설

해설 보기

두 함수의 차이가 “URL 경로”와 “쿼리 파라미터” 두 가지뿐이라면, 나머지 90%는 한 곳에 있어야 합니다. 인증 헤더 한 줄을 바꾸려고 두 파일을 동시에 수정해야 한다면 이미 DRY 원칙 이 깨진 상태입니다.

추출의 실마리는 “달라지는 부분만 매개변수로 받는 헬퍼”입니다. fetch 옵션과 Response.ok 체크, JSON 파싱은 한 함수에 모으고, 경로와 쿼리만 호출자가 넘기게 합니다. URL 조립은 URLSearchParamsURL 생성자로 안전하게 만들 수 있습니다.

컨트롤러 단의 try-skeleton-fetch-render-catch 반복도 같은 결을 가집니다. “스켈레톤을 켜고, 페칭하고, 렌더하고, 에러 시 표시하고, finally에서 정리한다”는 흐름은 한 군데 헬퍼에 두고 페칭 함수와 렌더 함수만 인자로 받게 만들면 4~5곳의 사본이 한 줄 호출로 줄어듭니다. 사본이 줄어들면 정책 변경 한 번에 한 곳만 고치면 됩니다.

개선 방향

Before

export async function getPopularMovies(page) { const url = `${POPULAR}?page=${page}`; const res = await fetch(url, { headers: AUTH }); if (!res.ok) throw new Error("..."); return res.json(); } export async function getSearchMovies(query, page) { const url = `${SEARCH}?query=${query}&page=${page}`; const res = await fetch(url, { headers: AUTH }); if (!res.ok) throw new Error("..."); return res.json(); }

After

async function tmdbGet<T>(path: string, params: Record<string, string | number>): Promise<T> { const url = new URL(path, BASE); Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v))); const res = await fetch(url, { headers: AUTH }); if (!res.ok) throw new ApiError(res.status, await res.text()); return res.json(); } export const getPopularMovies = (page: number) => tmdbGet(POPULAR, { page, language: "ko-KR" }); export const getSearchMovies = (query: string, page: number) => tmdbGet(SEARCH, { query, page, language: "ko-KR" });

핵심 변화는 달라지는 부분만 매개변수로 추출하고 fetch·ok 체크·에러 변환을 한 헬퍼에 모아 변경 지점을 한 곳으로 줄였다는 점입니다.

더 알아볼 개념

Last updated on