프론트엔드

🧨 우당탕탕 Axios 인터셉터 만들기

-- 토큰 만료와의 혈투 기록 -- 프론트엔드를 하다보면, 그 어떤 api보다도 나를 가장 괴롭히는게 등장한다 바로 토큰 만료(401) 이번 글은 “처음에 대충 회사 코드 ctrl c + ctrl v 했다가 혼나서 결국 프로젝트 갈아엎고 다시 처음부터 axios 인터셉터 설계하게 된 이야기” 시작은 항상 \복붙이었다. 솔직히 말하면 인터셉터 만들 여유보다는 화면 구성하고싶은 맘이 조급했다. 백오피스를 마이그레이션 하라는 지시를 받았고, 인터셉터 그까이거 그냥 사용하던거 쓰면 되는거 아님? 당연히 이렇게 시작했다. 나는 그냥 옛날 코드 가져와 붙였고, 아무 생각 없이 진행하고 있었다. 근데 코드리뷰에서 멘토님이 한 말씀 기존거 가져다쓰지 마시고 처음부터 머리 박아가면서 만들어보시죠. 네..? 다시요..? 그렇게 나는 다시 401을 맞았다. 그래서 진짜로 인터셉터가 어떤 구조로 작동해야 하는지, 토큰 로직은 어떻게 구성해야 하는지 기본부터 다시 뜯어보기로 했다. 사실 이번 인턴 면접에서 토큰 기반 인증에 관한 질문이 있었고, (현 멘토님이 내 면접관이셨다) 한 페이지에서 여러 요청이 동시에 401 에러가 나오면 어떻게 해결하는지에 대해 여쭤보셨다. 나는 그것까지는 생각을 해본 적이 없어서 잘 모르겠다고 답 했었고?? 두고두고 맘에 남았다. 그래서 기존 프로젝트 axios 인터셉터 보고 “오 이렇게 만드는구나. 설계가 잘 되어있구나” 하고 그대로 갖다 썼던 마음이 큰 것 같다. 첫 삽질: 401 해결하려다 403 맞기 나는 빠르게 at 만료 감지를 만들고, refresh api도 붙였다. 그리고 좀 기다려서 만료되었을 때 나의 시나리오 오래된 at로 api 호출 서버가 401 던짐 인터셉터가 refresh 호출 새 토큰으로 요청 재시도 … 되어야 하는데 만료된 at로 api 호출 -> 401 err 자동적으로 refresh api 호출 -> 403 err 아니 왜 재발급 한번도 못했는데 왜 403 나옴? 이 악몽같은 현상으로 하루종일 삽질만 했다. 재발급 시마다 alert도 띄워보고, Authorization 헤더도 다시 붙여보고, 인터셉터 바깥에서 refresh 호출 성공하는것도 확인했는데,, 원인이 걍 퐝당했음. at와 rt의 유효기간이 같았던 것임!!! 아!뿔!싸! 기존 인터셉터도 이거 처리가 안되어있더라! 너무 모르겠어서 jwt.io에서 token 까보고 알았다. at가 만료되었을 때 rt도 만료가되어 refresh를 할 수 있는 시간이 없었던 것이다. accessToken 만료 → refresh 요청 시도 근데 refreshToken도 이미 만료 → 403 그래서 당연히 403이 나는 구조였다. 근데 백엔드를 바꿔달라고 할 수가 없는 상황 이걸 계기로 오랜만에 조금 머리를 굴려보았다. 자동 재발급? 그건 좀 아닌것같아요. 처음에는 이런 식의 자동 로직을 생각했다. 사용자가 뭘 하든 at 만료 5분전에 자동으로 refresh 하자 근데 이 생각을 말씀드렸더니 멘토님이 말씀하셨다. 사용자가 아무 행동도 안하는데 알아서 재발급 하는건 아닌 것 같아요! 맞말임. 아무래도 백오피스다보니까 보안적인 부분도 고려를 했어야했다. 그래서 방향을 바꿨다. 사용자가 실제 api 요청 시 만료 시간이 5분 이하라면 그때 refresh 하자 즉 활성 사용자만 재발급 → 비활성 사용자 세션은 종료 동시 요청 문제 → Pending Queue 도입 다음 문제는 이거였다. 이거는 내가 면접 때 질문을 받고 대답을 못했던 그 문제이기도 한데 A API 요청 시 → 토큰 만료 → refresh 시작 그 사이에 B API 요청 → 또 토큰 만료 → refresh 또 호출 이러면 refresh api가 동시에 여러번 호출되며 경합이 발생한다. 그래서 필수적으로 refresh 중엔 다른 요청을 멈춰서 줄세우는 로직이 필요하였다. 그것이 바로 pending queue ~~내가 자료구조 수업에서 듣고 코테 풀때만 쓰던 큐를 진짜 써보는 날이 오다니~~ 요약하자면 refresh 중이면 → 요청을 queue 에 쌓아둠 refresh 끝나면 → queue에 있던 요청들을 새 토큰으로 재요청 이렇게 하면 동시 재발급 요청 문제는 해결된다. 세션 만료 경고가 여러 번 뜨는 문제 아 근데 산넘어 산이라더니 에베레스트가 나옴 refresh 실패(401/403) → 로그아웃 처리할 때 다음과 같은 코드가 있었다. 근데 api 여러개가 동시에 터지면서 alert이 여러개가 떴던것임! alert 확인 버튼 연타함 그래서 로그아웃이 한 번만 실행되게 하기 위해서 zustand에 isLoggingOut state를 추가하여 해결하였다. 근데 왜 isLoggingOut 초기화가 안돼요? 문제 발생 두번째 세션 만료 로그아웃 때, 401 떴는데 로그아웃이 안되더라 그건 내가 isLoggingOut 상태값을 zustand persist 미들웨어에 넣어놨기 때문이었다. 그래서 persist partialize 설정해서 해결하였다. ㅎㅎ 바보 최종 Axios 인터셉터 코드 삽질 끝에 만들어진 인터셉터 구조는 다음과 같다. 토큰 만료 5분 이하 → refresh refresh 동안 다른 요청은 pending queue refresh 실패 → 로그아웃 alert 중복 방지 → isLoggingOut accessToken, refreshToken 상태는 zustand에서 관리 Authorization 헤더 자동 주입 정리하자면.. 이번에 인터셉터를 처음부터 설계하면서 느낀 점: 토큰 구조를 이해해야 한다 세션 만료 UX까지 고려해야 한다 동시 요청 문제까지 잡아야 한다 상태관리까지 연계해야 한다 인터셉터는 그냥 훅 두 개가 아니라 프로젝트의 전체 인증 흐름을 담당하는 중요한 부분임을 깨달았다. 그리고 무엇보다: 절대… 생각 안하고 코드 복붙부터 시작하지 말자.

2025년 11월 19일 15:33

프론트엔드

최고의 프로젝트 구조를 찾아서....

프론트엔드 디자인 패턴: Atomic Design + FSD 적용기 프롤로그: 협업을 시작하며 처음으로 현업에서 종사하는 여러 프론트엔드 개발자들과 협업을 진행하면서 다양한 프론트엔드 디자인 패턴에 대해 깊이 고민할 기회가 생겼다. 그중에서도 가장 큰 고민은 어떻게 폴더 구조를 정리하고, 컴포넌트를 재사용할 것인가? 였다. 이 과정에서 접하게 된 대표적인 디자인 패턴이 바로 Atomic Design Pattern과 Feature-Sliced Design(FSD) 이다. 이번 글에서는 Atomic Design의 장점과 한계, 그리고 이를 보완하기 위해 FSD와 결합한 방식을 소개하려 한다. Atomic Design Pattern 적용기 Atomic Design Pattern이란? Brad Frost가 제안한 UI 설계 방식으로, 컴포넌트를 작은 단위에서 점점 확장하는 방식이다. UI를 다음 5가지 단계로 구성한다. 1. Atoms (원자) – 더 이상 쪼갤 수 없는 최소한의 UI 요소 (ex. Button, Input, Typography) 2. Molecules (분자) – Atom이 조합된 단위 (ex. SearchBar, Dropdown) 3. Organisms (유기체) – 여러 Molecule이 조합된 UI 블록 (ex. Header, Footer) 4. Templates (템플릿) – Organism을 배치한 UI 레이아웃 5. Pages (페이지) – 최종 데이터가 반영된 화면 Atomic Design Pattern 적용 시 폴더 구조 이런 구조를 사용하면 컴포넌트를 조합하는 방식이 명확해지고, 재사용성이 높아지는 장점이 있다. 하지만 실제 프로젝트에 적용하면서 몇 가지 한계를 경험했다. Atomic Design의 한계와 고민 Feature-Sliced Design(FSD)이란? FSD(Feature-Sliced Design) 은 기능(Feature) 단위로 폴더를 구성하는 방식이다. • 컴포넌트는 기능 단위로 묶는다. • 비즈니스 로직, 서비스 로직, UI 컴포넌트를 명확하게 구분한다. • 페이지별로 독립적인 폴더 구조를 유지하여 유지보수가 쉽다. Atomic Design + FSD 결합 후 디렉토리 구조 • Atomic Design은 components 폴더에서 UI 요소를 관리하는 데 적용 • FSD는 features 폴더에서 기능별로 폴더를 나누어 관리 • 공통적으로 사용하는 로직(hooks, api 등)은 shared 폴더에 배치 적용 후 개선된 점 폴더 구조가 기능 중심으로 정리되어 유지보수가 쉬워졌다. UI 컴포넌트와 비즈니스 로직을 분리할 수 있었다. 페이지 단위로 기능을 나누면서 개발 속도가 빨라졌다. 고민했던 점 Atomic Design을 어디까지 적용할 것인가? • Atom/Molecule/Organism으로 나누는 것은 좋은데, 모든 컴포넌트가 Atomic한 것은 아니다. • 어떤 기준으로 구분할지 논의가 필요했다. FSD의 Feature 컴포넌트는 어디에 넣어야 할까? • features/{feature}/components 안에 넣어야 할까? • 아니면 components/organisms로 가야 할까? • 결국 독립적인 기능일 경우 features 안에 넣고, 재사용할 UI는 components에 넣기로 결정 3. FSD 적용 후 폴더 depth를 어떻게 제한할 것인가? • 너무 깊은 구조를 만들지 않기 위해 최대 3 depth 이하로 제한 • 예외적으로 스터디 만들기 폼처럼 많은 컴포넌트가 필요한 경우 한 depth 추가 최종 정리 & 회고 Atomic Design과 FSD를 결합한 이유? • Atomic Design은 UI 컴포넌트의 재사용성을 높이는 데 효과적 • FSD는 비즈니스 로직을 기능 단위로 구분하여 유지보수를 쉽게 함 • 두 가지를 조합하면 UI와 기능을 분리하면서도 재사용성을 높일 수 있음 적용하면서 느낀 점 • Atomic만 적용하면 폴더 구조가 너무 깊어지고 관리가 어려워진다. • FSD만 적용하면 재사용성이 떨어질 수 있다. • 적절한 기준을 정하는 것이 중요하며, 프로젝트의 규모와 특성에 맞춰 유연하게 적용하는 것이 중요하다.

2025년 03월 13일 02:45

프론트엔드

React Query로 데이터 처리의 효율성 높이기

React Query를 왜 사용해야 하는가? 사실, React Query 사용 전에는 서버 담당자가 자꾸 React Query를 쓰세요 라고 권장하는 이유를 잘 이해하지 못했습니다. 난 함수 호출 잘 하고있는데? 굳이 배워야해? 라는 마인드였죠 하지만 블로그 코드를 리팩토링하고 뜯어보면서, account 정보를 불러오는 함수를 너무 많이 호출한다는 사실을 깨달았습니다. (이 블로그 왼쪽에 위치한 그 유저 정보 맞습니다.) 그러면서 그냥 유저 정보를 Recoil로 담아서 전역으로 관리할까? 라는 생각도 들었습니다. 하지만 유저 정보가 바뀌었을 때, 또 다른 곳에서 이 함수를 호출해야 한다는 문제가 생깁니다. 그리고 또 고민이 생겼습니다. 지금 계속 호출하는 게 서버에 괜찮나? 현재는 나 혼자만 사용하지만, 더 많은 사용자가 들어오게 된다면 서버에 큰 부담이 될 것이라는 생각이 들었습니다. 이렇게 고민하던 중에, React Query가 제공하는 자동 캐싱과 최신 데이터 요청 기능이 문제를 해결해줄 수 있다는 것을 알게 되었습니다. React Query란? React Query는 데이터 페칭과 상태 관리를 매우 효율적으로 처리할 수 있는 라이브러리입니다. React Query는 서버에서 데이터를 가져오는 작업을 간편하게 만들어주며, 비동기 작업을 처리하고 그 결과를 캐싱하여 성능을 최적화 합니다. 주요 특징은 다음과 같습니다. 자동 캐싱 및 재사용: 동일한 데이터 요청이 있을 경우, 이전에 받은 데이터를 재사용하여 네트워크 요청을 최소화합니다. 자동 갱신: 데이터가 변경되면, 자동으로 최신 데이터를 가져옵니다. 로딩, 오류 및 데이터 상태 관리: 로딩 상태, 오류 상태 등을 관리할 수 있는 기능을 제공하여 상태 관리가 간편해집니다. 쿼리 무효화 및 재요청: 데이터를 수정한 후, 쿼리를 무효화하여 최신 데이터를 다시 요청할 수 있습니다. 여태까지 내가 계속 해왔던 방법 - 함수 계속 호출하기 리액트 프로젝트에서 데이터를 처리할 때, useEffect 안에서 함수를 계속 호출하는 방식을 사용했습니다. 이 방식은 간단하지만, 몇 가지 단점이 존재합니다: 반복되는 코드: 데이터 요청이 여러 곳에서 반복되어 코드 중복이 발생합니다. 로딩 상태와 에러 처리: loading, error 상태를 일일이 관리해야 하므로 코드가 복잡해집니다. 성능 문제: 데이터 요청이 자주 발생하면 불필요한 네트워크 요청이 많아져 성능에 영향을 줄 수 있습니다. React Query로 바꿔보자 위 코드는 React Query의 useQuery 훅을 사용하여 유저 정보를 서버에서 가져오는 함수입니다. 이 코드는 자동 캐싱과 데이터 갱신 기능을 통해 데이터를 처리합니다. 쿼리 키와 데이터 요청 useQuery는 queryKey를 통해 요청을 구분하고, queryFn을 통해 데이터를 요청합니다. queryKey:해당 데이터 요청을 고유하게 식별 queryFn: 실제로 데이터를 요청하는 함수 캐시 관리 staleTime: 데이터가 오래된 것으로 간주되기 전에 데이터를 얼마나 유효하게 유지할지를 결정 cacheTime: 데이터가 캐시에서 삭제되기까지의 시간을 설정 에러 처리 및 로딩 상태 onError: 데이터 요청 중 오류가 발생했을 때 호출 isLoading: 요청 중 로딩 상태 error: 요청 실패 시의 에러 정보 refetch refetch: 데이터를 수동으로 다시 요청할 수 있는 함수 그래서 React Query가 뭐가 다른데? 이 질문이 나온 이유는, React Query를 사용해도 새로고침을 하면 다시 fetch가 된다는 점에서 의문이 발생했기 때문입니다. 새로고침만 해도 쿼리가 다시 실행되는데 그러면 내가 모든 컴포넌트에 account 함수 호출하는거랑 뭐가 달라? 제가 의문이 생겨서 chatGPT에게 실제로 물어봤던 내용입니다. 기존 방식에서는 페이지를 새로고침할 때마다 데이터를 새로 요청하게 되며, 이를 계속 함수 호출이라고 표현할 수 있습니다. 이 방식은 사용자가 새로고침을 할 때마다 매번 API 요청을 보내고, 그에 따른 로딩 상태가 표시되기 때문에 불편할 수 있습니다. 또, 같은 데이터를 반복적으로 요청하게 되어 성능에도 영향을 미칩니다. 반면, React Query는 자동 캐싱 기능을 제공하여, 한 번 데이터를 요청하고 나면 캐시에서 데이터를 가져옵니다. 이후에는 서버에 다시 요청을 보내지 않고, 이미 받은 데이터를 사용할 수 있기 때문에 불필요한 요청을 줄일 수 있습니다. 그리고 데이터가 수정되거나 갱신될 때는 invalidateQueries나 refetchQueries를 통해 필요한 시점에만 다시 요청을 보내어 최신 상태로 동기화할 수 있습니다. 차이점 1: 데이터 요청 및 캐시 관리 • 기존 방식: 페이지 새로 고침 시마다 데이터를 새로 요청. • React Query: 데이터가 캐시되어, 동일한 데이터 요청 시 네트워크 요청을 최소화하고, 자동으로 최신 상태를 유지. 차이점 2: 로딩 상태 관리 • 기존 방식: 페이지 새로 고침 시마다 로딩 상태가 반복적으로 표시. • React Query: 이미 캐시된 데이터가 있으면 로딩 없이 바로 데이터를 표시하고, 필요 시 백그라운드에서 갱신. React Query의 캐싱과 탭 전환 여기서 중요한 점은 다른 탭으로 넘어갔다가 다시 돌아올 때도 React Query는 이미 캐시된 데이터를 사용한다는 점입니다. 기존 방식에서는 페이지를 새로고침할 때마다 데이터를 요청해야 했지만, React Query는 데이터가 캐시되기 때문에 같은 데이터를 다시 요청하지 않고, 캐시된 데이터를 사용합니다. 예를 들어, useQuery로 한 번 데이터를 요청하고 나면, 해당 데이터는 캐시에 저장됩니다. 다른 페이지나 탭으로 이동하더라도, 같은 데이터를 다시 요청하지 않고 캐시된 데이터를 즉시 사용할 수 있습니다. 만약 데이터가 변경되었다면, React Query는 invalidateQueries나 refetchQueries를 통해 데이터를 새로 요청하여 자동으로 최신 데이터를 반영합니다. 이렇게 React Query는 데이터 요청을 효율적으로 관리하며, 불필요한 요청을 줄이고, 사용자 경험을 개선할 수 있습니다. 🚨문제: 데이터 변경 요청 후 캐시된 데이터 유지 문제는, userData 수정 요청을 보내고 데이터를 변경한 후에도 캐시된 데이터가 계속 유지된다는 점입니다. 예를 들어, 사용자가 자신의 정보를 수정하는 API 요청을 보낸 후, 데이터베이스에는 수정된 내용이 반영되었지만, React Query의 캐시에는 여전히 이전 데이터가 남아 있을 수 있습니다. 이 경우, 수정된 데이터가 즉시 반영되지 않고, 여전히 오래된 캐시 데이터를 사용하게 됩니다. 문제 발생 이유 • 캐시와 서버 데이터 간 불일치: useQuery로 데이터를 요청한 후 React Query는 캐시된 데이터를 사용하고, 수정된 데이터는 서버에서 갱신되었지만, 캐시에는 여전히 이전 데이터가 저장되어 있을 수 있습니다. • 자동 갱신 미흡: invalidateQueries나 refetchQueries를 사용하지 않으면, 수정된 데이터를 즉시 다시 요청하지 않기 때문에 캐시된 데이터가 계속 사용됩니다. refetch( ) 사용으로 해결하기 이 문제를 해결하기 위해서는 useQuery로 데이터를 요청하고, refetch( )를 사용하여 수정된 데이터를 강제로 다시 가져오는 방법이 필요합니다. refetch( )는 useQuery로 가져온 데이터를 다시 요청하는 함수로, 데이터 수정 후 필요한 시점에만 다시 요청을 보내어 최신 상태로 동기화할 수 있습니다. 예를 들어, userData를 수정한 후 refetch( )를 사용하여 해당 데이터를 다시 가져올 수 있었습니다: 결론 React Query는 서버 부하를 줄이고, 데이터 요청을 효율적으로 관리하는 데 중요한 도구입니다. 자동 캐싱과 데이터 갱신 기능을 통해 불필요한 네트워크 요청을 줄이고, 서버에 가는 요청을 최소화할 수 있습니다. 특히 refetch( )를 사용하여 데이터 수정 후 즉시 최신 데이터를 요청할 수 있어, 서버와 클라이언트 간의 데이터 동기화를 원활하게 할 수 있습니다. 기존 방식에서 발생할 수 있는 중복된 요청과 성능 문제를 해결하면서, React Query는 데이터 관리의 효율성을 높이고 서버 성능 최적화에도 기여할 수 있습니다. 따라서, React Query는 단순한 데이터 요청 라이브러리를 넘어서, 서버 자원을 효율적으로 관리하고 성능을 최적화하는 데 필요한 필수 도구라고 할 수 있습니다.

2025년 01월 20일 09:40

프론트엔드

스파게티 코드 갱생시키기 프로젝트

🔴 개관 저는 프로그래밍을 할 때 단순히 돌아가기만 하면 된다는 접근보다, 가독성과 코드의 효율성을 더욱 중요하게 여깁니다. 처음 React Native로 프로젝트를 진행했을 때, React와 달리 데이터를 저장하고 사용하기 위해 복잡한 코드를 작성해야 했던 경험이 있습니다. 당시에는 효율성보다는 기능 구현에 초점을 맞췄기 때문에 코드가 비효율적이었습니다. 초보자라면 누구나 겪을 문제라고 생각합니다. 그러나 이번에 React Native로 두 번째 프로젝트를 진행하며, 비효율적인 AsyncStorage의 저장 및 조회 코드를 모듈화하여 재사용성과 유지보수성을 높이겠다고 계획했습니다. 🔴 첫 번째 React Native 프로젝트: 완전한 스파게티 코드 오랜만에 캡스톤에서 진행했던 프로젝트의 코드를 꺼내보았습니다. React Native를 처음 사용해보는거라서 아주 스파게티 코드입니다. 지금 생각하면 아주 끔찍한 코드입니다. 서버에서 오는 응답에 따른 두번의 Alert 코드와 그 Alert을 감싸고있는 AsyncStorage 함수의 모습입니다.... 초반에는 기능 구현에만 집중했기 때문에 비효율적인 코드가 발생하였습니다. 저는 여기서 Alert나 AsyncStorage에 대한 모듈화가 필요하다고 생각하였지만 기간이 빡빡하기도 하였고, 저의 실력 문제;;로 진행하지는 못하였습니다. 🔴 두번째 React Native 프로젝트: 코드 모듈화 사실 첫번째 어플리케이션을 리팩토링하자! 하는 마음이 지금도 남아있지만.... AI 서버와 백엔드 서버가 다 내려갔기 때문에 리팩토링, 유지보수 한다고 해도 돌아간다는 보장이 없습니다. 모듈화의 기회는 두번째 프로젝트에서 달성할 수 있었습니다. 저는 제일 문제된다고 생각한 것이 AsyncStorage 함수와 Alert 함수라고 생각했습니다. 그래서 프로젝트에 Utils 폴더를 만들어 전역적으로 이 함수들을 관리하기로 결심했습니다. Alert 컴포넌트 코드를 먼저 보여드리겠습니다. 확인 버튼이 있는 알림창과 확인, 취소 버튼이 있는 알림창 컴포넌트를 만들었습니다. 두 파일로 나눠서 만들까 하다가 Utils/component 폴더 내부에 또 Alert 폴더를 만들기 싫어서 한 파일만 임포트하면 두 Alert 컴포넌트 접근 가능해서 위의 이유로 한 파일에 합쳐 작성했습니다. 아래 코드는 위 컴포넌트 사용 예시입니다. 치매 진단지를 서버로 제출하는 함수인데, 서버에서 오는 응답 코드에 따라 다른 Alert 컴포넌트를 띄우거나 탭을 이동하는 코드입니다. 물론! 저 함수에 title, message, onPress 세 개의 파라미터가 들어가기 때문에 모듈화를 시켜 사용한다고 해도 여전히 코드가 어지럽고 길긴 합니다. ~~하지만 제 실력으로는 최선이었습니다.~~ 하지만 모듈화를 안하고 사용했다면? 진짜 아찔합니다. 다음으로는 절 힘들게했던 AsyncStorage 함수입니다. React와는 다르게 ReactNative에서는 async가 필요해서 코드가 굉장히 복잡해졌습니다. 여기서 흠은 console을 띄웠다는 점인데요.. 다음은 에러가 나온다고 하면 Alert창을 띄우거나.. 다시 시도하거나 하는 방식을 해봐야겠습니다. Storage 싱글톤 객체를 만들어서 내부에 네가지 메서드를 넣었습니다. 한창 정보처리기사 공부할때 디자인패턴이 나오길래 신나했던 기억이 납니다.. 주로 사용했던 메서드는 setItem과 getItem 입니다. 위에는 아까 봤던 Alert 컴포넌트와 Storage 의 setItem 메서드가 결합된 코드입니다. 엄청 깨끗해지고 가독성도 좋아졌습니다. (맞나..?) 🔴 결론 저는 캡스톤에서 처음으로 React Native를 사용해봤습니다. 기간이 부족했기 떄문에 몸통박치기 수준이 아니고 몸통 갈아넣기 정도로 일단 써보자 마인드였는데요 만약에 프론트가 저 혼자가 아니고 여러명이서 진행했다면 프론트끼리 머리채잡고 싸웠을수도 있습니다. 이런 스파게티 코드는 물론 개발 진행에 있어서 굉장히 불가피하다고 생각합니다. 하지만 계속해서 코드를 리팩토링하고 유지보수한다면 전보다는 실력이 10%정도.. 아님 1%라도 성장하는 것 같습니다. 요즘 저는 리팩토링을 진행했다고는 하지만 지금보면 발싸개같은 코드를 다시 보면서.. React 디자인패턴 공부에 박차를 가해야겠다는 생각이 듭니다.... 저의 시행착오가 여러분들에게 도움이 되셨으면 좋겠습니다.

2024년 10월 22일 23:15

CS

Database

데이터베이스(Database) 데이터베이스란 무엇인가? 데이터베이스(Database)는 여러 사용자가 공동으로 사용하는 데이터의 집합이다. 단순히 데이터를 저장하는 것이 목적이 아니라, 데이터의 중복을 줄이고 데이터 간 일관성을 유지하며 여러 사용자가 동시에 안전하게 접근할 수 있도록 관리하는 것이 핵심이다. 이러한 관리를 담당하는 소프트웨어를 DBMS(Database Management System) 라고 한다. 데이터베이스 설계가 필요한 이유 현실 세계의 데이터를 그대로 저장하면 다음과 같은 문제가 발생한다. 동일한 데이터가 여러 곳에 저장됨 → 중복 발생 한 곳만 수정되어 데이터 불일치 발생 일부 데이터를 삭제했는데, 다른 데이터까지 사라짐 이러한 문제를 방지하기 위해 체계적인 설계 과정을 통해 데이터 구조를 정의해야 한다. 데이터베이스 설계 단계 데이터베이스 설계는 다음과 같은 순서로 진행된다. 요구사항 분석 → 개념 설계 → 논리 설계 → 물리 설계 3.1 요구사항 분석 사용자가 어떤 데이터를 필요로 하고, 어떤 처리가 이루어져야 하는지를 분석하는 단계이다. 어떤 정보가 필요한가? 데이터 간 관계는 무엇인가? 3.2 개념 설계 (Conceptual Design) 현실 세계를 개념적으로 모델링하는 단계이다. 엔터티(Entity) 속성(Attribute) 관계(Relationship) 를 정의하며, 주로 ER 다이어그램(ERD) 을 사용한다. 이 단계에서는 DBMS 종류나 구현 방식은 고려하지 않는다. 3.3 논리 설계 (Logical Design) 개념 설계를 바탕으로 DBMS에서 사용할 논리적 구조를 설계하는 단계이다. 테이블 정의 속성 및 키 정의 정규화 수행 즉, 정규화는 논리 설계 단계에서 수행된다. 3.4 물리 설계 (Physical Design) 논리 설계 결과를 실제 저장 장치에 맞게 구현하는 단계이다. 저장 구조 인덱스 접근 경로 성능(처리량, 응답 시간) 을 고려한다. 👉 물리 설계의 핵심은 성능 최적화이다. 키(Key)의 개념 데이터베이스에서 키는 튜플을 유일하게 식별하기 위한 속성 또는 속성의 집합이다. 주요 키 종류 슈퍼키(Super Key): 유일성은 만족하지만 최소성은 만족하지 않음 후보키(Candidate Key): 유일성과 최소성을 모두 만족 기본키(Primary Key): 후보키 중 선택된 하나 대체키(Alternate Key): 기본키로 선택되지 않은 후보키 외래키(Foreign Key): 다른 테이블의 기본키를 참조 정규화(Normalization) 정규화는 데이터의 중복을 제거하고 이상 현상을 방지하기 위한 과정이다. 정규화를 통해 다음과 같은 이상 현상을 방지할 수 있다. 삽입 이상 삭제 이상 갱신 이상 5.1 제1정규형 (1NF) 모든 속성 값은 원자값(Atomic Value) 을 가져야 한다. 즉, 하나의 속성에 여러 값이 들어가면 안 된다. 5.2 제2정규형 (2NF) 제1정규형을 만족하면서 부분 함수 종속을 제거한 상태이다. 복합 기본키의 일부에만 의존하는 속성이 존재하면 이를 분리해야 한다. 5.3 제3정규형 (3NF) 제2정규형을 만족하면서 이행 함수 종속을 제거한 상태이다. 기본키가 아닌 속성이 다른 속성을 결정하면 안 된다. 5.4 BCNF 제3정규형보다 더 엄격한 정규형으로, 모든 결정자가 후보키여야 한다. 함수 종속(Function Dependency) 속성 X의 값이 결정되면 속성 Y의 값이 항상 하나로 결정될 때, 이를 Y는 X에 함수적으로 종속된다고 하며 다음과 같이 표기한다. X → Y 관계대수와 관계해석 관계대수(Relational Algebra) 절차적 질의 언어 연산의 순서를 명시 관계 연산의 집합 관계해석(Relational Calculus) 비절차적 질의 언어 원하는 결과만 기술

2026년 02월 02일 06:31

CS

결합도, 응집도

결합도, 응집도 결합도(Coupling)란? 모듈과 모듈 사이의 의존 정도 한 모듈의 변경이 다른 모듈에 얼마나 영향을 주는지를 나타냄 낮을수록 좋다 결합도 종류 (내·공·외·제·스·자) 내용 결합도 (Content) 다른 모듈의 내부 데이터나 로직을 직접 참조/수정 공통 결합도 (Common) 여러 모듈이 공통 데이터 영역(전역 변수) 사용 외부 결합도 (External) 외부 파일, 외부 인터페이스, 외부 포맷 공유 제어 결합도 (Control) 제어 신호(flag, mode)를 전달해 동작 제어 스탬프 결합도 (Stamp) 배열, 구조체, 객체 등 자료 구조 전체 전달 자료 결합도 (Data) 필요한 데이터 값만 전달 (가장 이상적) 응집도(Cohesion)란? 모듈 내부 요소들 간의 관련성 하나의 모듈이 얼마나 하나의 목적에 집중되어 있는지 높을수록 좋다 응집도 종류 (기·순·통·절·시·논·우) 기능적 응집도 (Functional) 하나의 명확한 기능만 수행 (최고) 순차적 응집도 (Sequential) 한 기능의 출력이 다음 기능의 입력 통신적 응집도 (Communicational) 동일한 데이터를 사용하는 기능들 묶음 절차적 응집도 (Procedural) 실행 순서 때문에 묶인 기능들 시간적 응집도 (Temporal) 같은 시간대에 실행되는 기능들 (초기화, 종료) 논리적 응집도 (Logical) 논리적으로 유사한 기능들 (입출력 처리 등) 우연적 응집도 (Coincidental) 관련 없는 기능들이 묶임 (최악)

2026년 02월 02일 05:23

CS

소프트웨어 테스트

Software Test 소프트웨어 테스트란? 소프트웨어가 요구사항에 맞게 동작하는지 확인하는 과정 오류(Error) 및 결함(Bug)을 발견하기 위한 활동이며 결함이 없음을 증명하는 활동이 아님 살충제 패러독스: 동일한 테스트 케이스를 반복해서 수행하면 새로운 결함을 더 이상 발견하지 못하는 현상 파레토의 법칙: 소프트웨어 테스트에서는 전체 결함의 대부분이 소수의 모듈에 집중되어 발생 테스트의 목적 오류 및 결함 발견 소프트웨어 품질 향상 신뢰성 확보 생명주기 단계별 테스트 (V-모델 기준) | 개발 단계 | 테스트 단계 | | ------------- | ------------- | | 요구사항 분석 | 인수 테스트 | | 시스템 설계 | 시스템 테스트 | | 아키텍처 설계 | 통합 테스트 | | 모듈 설계 | 단위 테스트 | 테스트 단계별 정리 단위 테스트 (Unit Test) 대상: 함수, 클래스, 단일 모듈 수행자: 개발자 목적: 모듈 내부 로직의 정확성 검증 특징 - 내부 구조를 고려한 테스트 - 구현 직후 수행 - 화이트박스 테스트 중심 통합 테스트 (Integration Test) 대상: 여러 모듈의 결합 수행자: 개발자 또는 테스트 담당자 목적: 모듈 간 인터페이스 오류 검출 특징 - 상위·하위 모듈 연동 테스트 - 스텁(Stub)과 드라이버(Driver) 사용 시스템 테스트 (System Test) 대상: 전체 시스템 수행자: 테스트 담당자 목적: 시스템 요구사항 충족 여부 검증 특징 - 실제 환경과 유사한 조건 - 기능 테스트 + 비기능 테스트 포함 - 블랙박스 테스트 중심 인수 테스트 (Acceptance Test) 대상: 완성된 시스템 수행자: 사용자 목적: 요구사항 만족 여부 최종 확인 특징 - 사용자 관점 테스트 - 계약 기준에 따라 수행 - 블랙박스 테스트 - 알파, 베타 테스트 테스트 관점에 따른 분류 블랙박스 테스트 (Black Box Test) 특징 - 내부 구조를 고려하지 않음 - 입력과 출력만을 기준으로 테스트 - 사용자 관점 테스트 적용 단계 - 시스템 테스트 - 인수 테스트 대표 기법 - 동치 분해: 입력 값을 유효한 그룹과 무효한 그룹으로 나누어 각 그룹에서 대표 값을 선택해 테스트 - 경계값 분석: 입력 값의 최소값·최대값 등 경계 부근에서 오류가 발생하기 쉬운 점을 집중 테스트 - 원인–결과 그래프: 입력 조건(원인)과 출력 결과(결과)의 논리적 관계를 그래프로 표현하여 테스트 케이스 도출 화이트박스 테스트 (White Box Test) 특징 - 내부 구조를 고려하여 테스트 - 코드 흐름과 분기 구조 확인 - 개발자 관점 테스트 적용 단계 - 단위 테스트 대표 기법 - 기초 경로 검사: 프로그램의 제어 흐름 그래프를 기반으로 독립적인 모든 실행 경로를 최소 한 번 이상 테스트 - 제어 구조 테스트: 프로그램의 순차, 선택, 반복 구조가 올바르게 동작하는지 검사 스텁과 드라이버 스텁 (Stub) 하위 모듈을 대신하는 가짜 모듈 호출 당하는 모듈 하향식 통합 테스트에서 사용 드라이버 (Driver) 상위 모듈을 대신하는 가짜 모듈 호출 하는 모듈 상향식 통합 테스트에서 사용 테스트 커버리지 (Test Coverage) 테스트가 프로그램을 얼마나 실행했는지를 측정하는 기준 테스트의 충분성을 판단하는 지표 주요 커버리지 종류 구문 커버리지: 모든 문장이 한 번 이상 실행되었는지 분기 커버리지: 모든 조건의 참/거짓이 실행되었는지 조건 커버리지: 조건식 내 각 조건이 실행되었는지 테스트 오라클 (Test Oracle) 테스트 결과가 올바른지 판단하기 위한 기준 테스트 실행 결과를 기대 결과(Expected Result) 와 비교하여 성공 또는 실패를 판정 목적: 테스트 결과의 옳고 그름 판단 특징 - 자동 또는 수동으로 판정 가능 - 모든 테스트에는 오라클이 존재해야 테스트 수행 가능 - 오라클이 없으면 테스트 결과의 정확성 판단 불가 회귀 테스트 (Regression Test) 프로그램 수정 또는 변경 이후 기존 기능이 정상적으로 동작하는지 확인하는 테스트 목적: 수정으로 인해 발생할 수 있는 부작용(Side Effect) 검출 특징 - 기존 테스트 케이스를 재사용 - 변경된 부분 및 영향 범위 중심으로 수행 - 유지보수 단계에서 주로 수행 정적 테스트 / 동적 테스트 정적 테스트 (Static Test) 프로그램을 실행하지 않고 검토 코드 또는 문서 기반 테스트 종류 - 워크스루(Walkthrough): 개발자가 주도하며 설명하면서 검토 - 인스펙션(Inspection): 전문가가 참여하는 공식적인 검토 동적 테스트 (Dynamic Test) 프로그램을 실제로 실행하여 테스트 실행 결과를 기반으로 오류 검출 알파 테스트 / 베타 테스트 알파 테스트 개발자 환경에서 수행 사용자 참여 가능 내부 검증 목적 베타 테스트 실제 사용자 환경에서 수행 배포 전 최종 검증

2026년 01월 30일 07:22

CS

소프트웨어 생명주기

소프트웨어 개발 전반 요약 (V-모델 기준) V-model 개발 단계(왼쪽)와 테스트 단계(오른쪽)를 1:1로 대응시킨 모델 각 단계의 산출물을 대응되는 테스트 단계에서 검증 요구사항 분석 ↔ 인수 테스트 요구사항 분석 시스템이 무엇을 해야 하는지 정의 기능 요구사항 정의 비기능 요구사항 정의 (성능, 보안, 신뢰성 등) 산출물 요구사항 명세서 유스케이스 다이어그램(선택) 인수 테스트 사용자 관점에서 수행 요구사항 충족 여부 확인 시스템 설계 ↔ 시스템 테스트 시스템 설계 전체 시스템 구조 설계 서브시스템 구성 외부 인터페이스 정의 산출물 시스템 설계서 유스케이스 다이어그램 컴포넌트 다이어그램 배치 다이어그램 시스템 테스트 전체 시스템 기능 테스트 성능, 보안 등 비기능 테스트 포함 아키텍처 설계 ↔ 통합 테스트 아키텍처 설계 주요 컴포넌트 구조 설계 컴포넌트 간 상호작용 정의 데이터 흐름 정의 산출물 아키텍처 설계서 컴포넌트 다이어그램 상위 수준 시퀀스 다이어그램 통합 테스트 모듈 및 컴포넌트 간 연동 테스트 인터페이스 오류 검출 스텁과 드라이버 사용 모듈 설계 ↔ 단위 테스트 모듈 설계 클래스 내부 구조 설계 메서드와 속성 정의 알고리즘 정의 산출물 상세 클래스 다이어그램 상세 시퀀스 다이어그램 모듈 설계서 단위 테스트 함수 또는 클래스 단위 테스트 내부 구조를 고려한 테스트 개발자가 직접 수행 구현 구현 설계 내용을 기반으로 소스 코드 작성 산출물 프로그램 코드 테스트 단계 요약 | 개발 단계 | 대응 테스트 | | ------------- | ------------- | | 요구사항 분석 | 인수 테스트 | | 시스템 설계 | 시스템 테스트 | | 아키텍처 설계 | 통합 테스트 | | 모듈 설계 | 단위 테스트 |

2026년 01월 29일 06:41

CS

Diagram & UML

Diagram & UML Diagram이란? 설계 시 모두가 같은 의미로 이해하기 위해 사용하는 그림 시스템을 시각적으로 표현하는 의사소통 도구 UML이란? Diagram을 그리기 위한 객체지향 모델링 언어 Unified Modeling Language 그림 기호와 의미를 표준화한 약속 UML의 구성요소 (사관다) UML은 다음 3가지로 구성된다. ① 사물 (Things) 관계의 대상이 되는 요소 구·행·그·주 - 구조: 시스템의 개념적, 물리적 요소 표현 - 행동: 시간과 공간에 따른 요소들의 행위 표현 - 그룹: 요소들을 그룹으로 묶어서 표현 - 주해: 부가적인 설명이나 제약조건 표현 ② 관계 (Relationships) 사물과 사물 사이의 연결 방식 연·집·포·일·의·실 - 연관(—): 2개 이상의 사물이 서로 관련되어 있음 - 집합(◇—): 하나의 사물이 다른 사물에 포함되어 있는 관계 - 포함(◆—): 포함하는 사물의 변화가 포함되는 사물에게 영향 미침 - 일반화(—▷): 하나의 사물이 다른 사물에 비해 더 일반적인지 구체적인지 표현 - 의존(--→): 사물 사이에 연관이 있으나 필요에 의해 서로에게 영향을 주는 짧은 시간 동안만 연관 유지 - 실체화(--▷) : 사물이 할 수 있거나 해야 하는 기능 (행위, 인터페이스) 로 서로를 그룹화 할 수 있는 관계 ③ 다이어그램 (Diagram) 사물 + 관계를 그림으로 표현한 결과물 정적 다이어그램, 동적 다이어그램으로 구성 정적 다이어그램: 구조 표현 클·객·컴·배·복·패 - 클래스 (Class): 클래스 사이 관계 표현 - 객체 (Object): 인스턴스를 특정 시점의 객체와 객체사이의 관계로 표현 - 컴포넌트 (Component): 컴포넌트 간 관계 표현 - 배치 (Deployment): 물리적 요소들의 위치 - 복합체 구조 (Composite Structure): 복합 구조 가지는 경우 그 내부 구조 표현 - 패키지 (Package): 모델 요소들을 그룹화한 패키지들의 관계 동적 다이어그램: 동작/순서 표현 유·시·커·상·활·호·타 - 유스케이스 (Use Case): 사용자의 요구 분석, 액터, 유스케이스로 구성 - 시퀀스 (Sequence): 시스템, 객체들이 주고받는 메시지 - 커뮤니케이션 (Communication): 메시지 뿐만이 아니라 객체들 관의 연관까지 표현 - 상태 (State): 객체의 상태가 어떻게 변하는지 표현 - 활동 (Activity): 시스템이 어떤 기능을 수행하는지 - 상호작용 개요 (Interaction Overview): 상호작용 다이어그램 간의 제어흐름 표현 - 타이밍 (Timing): 객체 상태 변화, 시간 제약을 명시적으로 표현 ※ Use Case 다이어그램 전용 관계 연관 (Association): 액터 ↔ 유스케이스 연결 포함 (Include): 항상 수행되는 공통 기능 확장 (Extend): 조건부로 수행되는 기능 일반화 (Generalization): 액터 또는 유스케이스 간 상속 관계

2026년 01월 29일 06:08