Seoyoung Portfolio LogoSeoyoung.

플랫폼 코드 품질·안정성 개선 — TS 전환 · 권한 서버 전환 · 테스트 체계

#frontend#TypeScript#Vitest#Zustand#lint-staged#GitLab CI
Seoyoung Kim · TS 전환 설계·주도 · 권한 전환 FE 담당 · 테스트 체계 도입 @ (주)썬더소프트코리아 → (주)사파이어스트림테크놀로지 / System SW
2026-05-20 ~ 진행 중

진행 중 (2026.05 ~). 권한 전환은 진행 중이고, 테스트 체계는 2026년 10월 도입. 보안 정책상 코드 발췌와 사내 용어는 싣지 않는다.

지표결과
TS 비율 — 도입 후 신규 파일80% (2026.05.20 도입 이후 신규 파일 440개 중 351개)
TS 비율 — shared 레이어72% (전체는 27%. 주로 신규 파일로 올랐고 기존 JS 변환은 40건)
권한 판정화면 곳곳의 규칙 → 기능 단위 · 대상 단위 두 함수
머지 검증MR마다 저장소 전체 lint · tsc · 테스트, 통과해야 머지

배경

통합 플랫폼이 29개 페이지까지 커지면서 세 가지 문제가 쌓였다.

  1. 타입 — JS + JSDoc으로 타입을 관리했는데, 기능이 바뀔 때 JSDoc 갱신이 자주 누락돼 타입 정보가 코드와 어긋났다.
  2. 권한 — 권한 규칙이 FE 코드에만 있었다. 화면에서 버튼을 숨길 뿐이라 화면을 거치지 않는 API 호출은 막을 수 없었고, 권한을 바꿀 때마다 FE 배포가 필요했다.
  3. 검증 — 테스트 러너가 없고 타입 검사는 각자 로컬에서만 돌렸다. 타입 에러가 main에 들어가는 걸 막는 장치가 없었다.

세 작업 모두 한 번에 갈아엎지 않고, 기능 개발과 병행할 수 있게 단계로 나눴다.


1. TypeScript 점진 전환

  • 도입 설계 문서와 팀 TS 가이드(any 금지 · unknown으로 좁히기, enum 대신 union, export 함수 반환 타입 명시 등 15개 항목)를 작성해 팀에 공유
  • allowJs: true, checkJs: false — 기존 JS는 그대로 두고 TS와 공존. 기준은 strict: true
  • 순서 — 다른 코드가 가장 많이 import하는 shared/ui · shared/hooks를 먼저 TS로. 이 레이어가 JS로 남아 있으면 새 TS 파일에도 타입 없는 코드가 늘어나기 때문
  • 규칙 — 새 파일은 TS로만 작성(빌드 설정 등 예외만 JS). TS에서 기존 JS를 쓰다 타입 문제가 생기면 먼저 JSDoc을 보강하고, 같은 모듈에서 두 번 이상 걸리면 그 모듈을 TS로 변환. 기존 파일 변환은 마이그레이션 전용 PR에서만

지금 상태 — 신규 파일 80%, shared 72%가 TS. widgets(2%)가 다음 차례다. 규칙은 컨벤션 문서로 관리하고, lint로 강제하지는 않는다.


2. 권한 서버 전환 (FE 담당)

변경 전변경 후
권한 정의FE 코드 여러 곳 (규칙 파일 · 사용자 목록 · 화면 인라인)BE DB → 권한 조회 API 응답
판정 기준역할 · 소속 · 사용자 ID 조합 하드코딩리소스 · 액션별 granted + relations
관리자FE에서 무조건 통과FE 우회 없음, 서버가 내려준 대로
규칙이 없을 때허용/거부를 화면마다 선택항상 거부 (fail closed)
작성자 · 소속 조건화면마다 직접 비교서버가 관계 조건을 내려주고 FE가 대상 데이터로 판정
권한 변경FE 배포 필요DB 수정만으로 반영
  • 판정 함수 두 개로 통일 — 기능 단위 함수(resource, action)는 기능이 있는지(라우트 · 메뉴 · 새 글 버튼), 대상 단위 함수(resource, action, target)는 특정 대상에 대해(내 글 수정, 담당 소속만 수정). 관계 조건이 여러 개면 하나라도 맞으면 통과
  • 공용 권한 레이어를 TS로 새로 작성 — API → 응답 정규화(방어적 adapter) → 순수 판정 함수 → store → 반응형 훅 / 비훅 함수. 렌더 안에서는 훅을, 컬럼 빌더 · 이벤트 핸들러에서는 store를 직접 읽는 함수를 쓴다
  • 깜빡임 방지 — 권한을 받기 전에는 라우트 가드가 로딩을, 버튼 가드가 빈 화면을 그려 "안 보임 → 보임"으로 바뀌지 않게 했다
  • 계정 전환 안전장치 — 저장해 둔 권한에 소유자 ID를 함께 저장해, 다른 계정으로 로그인하면 이전 계정의 권한으로 판정하지 않는다
  • 7단계 교체 — 인프라 → 페이지 접근 → 단순 액션 → 소속 조건 → 작성자 조건 → 사용자 · 기타 → 정리. 교체가 끝난 기존 규칙은 같은 단계에서 삭제해 두 체계가 오래 공존하지 않게 했다
  • 권한 DB · API는 BE가 구현했다. 권한 DB에 아직 리소스가 없는 레거시 규칙 일부는 기존 방식을 유지한다

3. 테스트 · 머지 검증

  • 대상을 좁혔다 — 전체 커버리지가 목표가 아니다. 틀리면 권한이 열리거나 닫히는 판정 로직처럼 보안 · 데이터에 영향이 가는 로직, 입력 케이스가 5개 이상 갈리는 순수 함수, 결과를 보장해야 하는 리팩터링만 테스트한다. 컴포넌트 · 훅은 수동 확인이 더 싸다고 판단했다
  • Vitest를 고른 이유 — Vite 설정과 경로 alias를 그대로 공유해 별도 변환 설정이 없고, Jest와 같은 API. 37건 기준 1.5초
  • 테스트하기 좋게 분리 — store나 훅에 묶인 로직은 의존성을 인자로 받는 순수 함수로 먼저 분리했다. 호출처는 이름과 시그니처가 같은 함수를 그대로 쓴다
  • 두 단계 검증
커밋 시 (pre-commit)MR 파이프라인
범위커밋한 파일과 관련 테스트만저장소 전체
실행eslint --fix · prettier · 관련 테스트eslint · tsc --noEmit · 전체 테스트
우회가능 (--no-verify 등)불가, 통과해야 머지

로컬은 빠르게 고쳐 주는 층, MR은 아무도 우회하지 못하는 확인 층이다. MR 파이프라인의 타입 검사가 타입 에러가 main에 들어가는 걸 막는 첫 장치가 됐다.


배운 점과 남은 과제

  • 큰 전환은 "한 번에"보다 "멈추지 않고"가 중요했다. 세 작업 모두 기능 개발과 병행할 수 있게 순서와 예외 규칙부터 정했다
  • 결정은 문서로 남겼다 — TS 도입 설계 · 가이드, 테스트 도입 문서, 권한 전환 문서를 작성해 팀에 공유
  • 남은 과제 — 레이어 의존 방향을 lint로 강제하기, widgets 레이어 TS 전환, 막 도입한 테스트 체계의 운영 경험 쌓기