SPACE LOGBOOK

OCEAN LOGBOOK

리팩토링 작업 그리고 Git에 대한 시각..

2026.09.02

리팩토링 작업 그리고 Git에 대한 시각..

2026.09.02
REAL-WORLD REFACTORING · SAESOL CRM

운영 중인 웹앱을 크게 뜯어고쳤다
그리고 Git을 조금 다르게 보게 됐다

오늘은 새솔CRM의 구조를 꽤 큰 폭으로 정리했다. 프런트 UI, 관리자 화면, 서버 라우트, 문서 생성 공통 로직까지 여러 영역을 나눠 리팩토링했고, 그 과정에서 기존에 숨어 있던 DB 초기화 문제와 오래된 테스트 한 건도 찾아냈다. 기능을 새로 만드는 작업은 아니었지만, 끝나고 보니 오히려 앞으로의 개발을 훨씬 안전하게 만드는 작업이었다.

처음에는 단순히 “파일이 너무 커졌으니 좀 나누자” 정도로 생각했다. 그런데 실제 운영 중인 웹앱에서 리팩토링을 해보니 핵심은 파일을 잘게 쪼개는 데 있지 않았다. 어떤 변경을 어디까지 격리하고, 어떤 근거로 안전하다고 판단하며, 문제가 생겼을 때 어디까지 되돌릴 수 있게 만들 것인가가 훨씬 중요했다.

1. 리팩토링을 시작하게 된 이유

새솔CRM은 실제로 사용하고 있는 업무용 웹앱이다. 처음 만들 때는 기능을 빠르게 추가하는 것이 중요했다. 그러다 보니 기능이 늘어날수록 한 파일이 담당하는 역할도 같이 늘어났다.

예를 들어 프런트의 한 API 파일이 인증, 설정, 업체, 할 일, 이메일, 문서, 원천세, 세금계산 관련 요청을 거의 모두 담당하고 있었다. 업체 목록 파일 역시 화면 렌더링, 이벤트, 상태 관리, API 호출 등 여러 역할이 한곳에 섞여 있었다. 그리고 사실 이번이 첫번째 리팩토링은 아니고 그간 서너번의 크고 작은 리팩토링 작업이 있어왔다

큰 파일 자체가 반드시 나쁜 것은 아니다.
문제는 한 파일 안에서 서로 다른 책임이 너무 많이 섞이면, 작은 기능 하나를 고칠 때도 어디까지 영향을 미칠지 판단하기 어려워진다는 데 있다.

서버도 비슷했다. 라우트 파일 안에 URL 처리, 권한 확인, 비즈니스 규칙, DB 쿼리까지 한꺼번에 들어 있으면 처음에는 편하지만 기능이 많아질수록 수정할 때 읽어야 할 범위가 커진다.

리팩토링 전 큰 파일 하나 API 호출 화면 렌더링 상태 관리 이벤트 DB 접근 역할 분리 리팩토링 후 API / Core UI / Events Service Repository Common Utils
리팩토링의 핵심은 파일 수를 늘리는 것이 아니라 서로 다른 책임을 분리하는 것이었다.

2. 처음부터 한 번에 전부 고치지는 않았다

규모가 큰 리팩토링에서 가장 위험한 방식은 “어차피 다 고칠 거니까 한 번에 바꾸자”는 접근이다. 변경량이 커질수록 오류가 생겼을 때 원인을 좁히기 어려워진다.

그래서 이번 작업은 아예 여러 단계로 잘랐다. 각 단계마다 별도의 Git 브랜치를 만들고, 그 단계의 변경만 끝낸 뒤 테스트하고, 실제 서버에서도 확인한 다음에야 main으로 올렸다.

1단계 API 계약 테스트를 먼저 만들어 기존 인터페이스를 고정했다.
2단계 simple_tax_rate 스키마 누락 문제를 보완했다.
3단계 프런트 UI Core를 역할별 파일로 분리했다.
4단계 Cabinet Admin의 거대한 화면 로직을 기능별로 나눴다.
5단계 서버 라우트를 Route · Service · Repository 계층으로 분리했다.
6단계 양도세·증여세 DOCX 생성 코드의 공통 유틸리티를 추출했다.

그리고 리팩토링이 끝난 뒤에는 그 과정에서 발견됐지만 일부러 섞지 않았던 financial_pl 신규 DB 초기화 문제와, 상담예약 제목 테스트의 오래된 기대값 문제를 각각 별도 브랜치에서 처리했다.

이번에 가장 마음에 들었던 원칙:
발견한 문제라고 해서 그 자리에서 무조건 고치지 않았다. 현재 작업의 목적과 무관하면 기록해 두고, 현재 단계가 끝난 뒤 별도의 브랜치에서 고쳤다.

3. 가장 먼저 한 일은 ‘기존 동작을 잠그는 것’이었다

리팩토링은 기능을 새로 만드는 작업이 아니다. 이상적인 리팩토링은 겉으로 보이는 동작은 그대로 두고 내부 구조만 개선하는 것이다.

그런데 이것이 말처럼 쉽지 않다. 파일을 쪼개고 함수를 옮기다 보면 “분명 같은 코드인데 왜 이 화면만 안 되지?” 같은 일이 얼마든지 생길 수 있다. 그래서 본격적으로 구조를 바꾸기 전에 먼저 기존 API의 계약을 테스트로 고정했다.

기존 window.API 공개 인터페이스
        ↓
호출 방식 / URL / Method
        ↓
인증 헤더
        ↓
FormData / JSON 처리
        ↓
401 처리
        ↓
Blob 다운로드
        ↓
계약 테스트로 고정

이렇게 해두면 내부 구현을 아무리 나눠도 외부에서 바라보는 API가 그대로인지 기계적으로 확인할 수 있다. 사람이 “대충 비슷해 보인다”고 판단하는 것보다 훨씬 확실하다.

리팩토링 전에 테스트를 추가하는 이유는 테스트를 많이 만들기 위해서가 아니다. 바꿔도 되는 부분과 바꾸면 안 되는 부분의 경계를 먼저 긋기 위해서다.

4. 프런트에서는 ‘기능’보다 ‘책임’을 기준으로 나눴다

UI Core를 나눌 때 단순히 파일 길이를 비슷하게 맞추지는 않았다. 대신 각각의 코드가 무슨 책임을 갖는지에 따라 분리했다.

분리된 영역담당 역할
state.js공통 상태 관리
initialization.js초기화 흐름
routing.js화면 전환과 라우팅
staffView.js직원 관련 화면
appearance.js표시·스타일 관련 동작
events.js전역 이벤트 연결
index.js각 모듈을 묶는 진입점

Cabinet Admin도 같은 방식이었다. 기존 하나의 큰 파일에서 탭 이동, 보고서, 재무자료, 문서함, 고객관리, 알림, 이벤트 초기화를 각각 분리했다.

이 방식의 장점은 단순히 “코드가 예뻐졌다”가 아니다. 예를 들어 고객관리 기능을 수정할 때 보고서나 재무업로드 코드까지 함께 읽을 필요가 줄어든다. 수정 범위가 작아지고, 테스트할 범위도 더 명확해진다.

5. 서버에서는 Route · Service · Repository를 분리했다

서버 쪽에서는 Todo, MyNotes, Users, Calendar처럼 비교적 외부 의존성이 적은 라우트부터 분리했다.

Route URL · 인증 HTTP 응답 Service 권한 · 기본값 비즈니스 규칙 Repository SQLite 쿼리 DB 접근
HTTP 처리, 업무 규칙, DB 쿼리를 같은 파일에 두지 않고 책임별로 분리했다.

여기서도 욕심을 내지 않았다. 이메일, 문서, Cabinet, Financial처럼 파일·외부 서비스 의존성이 크고 복잡한 라우트는 이번 저위험 리팩토링 범위에서 제외했다.

리팩토링에서도 ‘안 하는 것’을 정하는 것이 중요했다.
한 번에 완벽한 구조를 만들려고 하지 않고, 현재 안전하게 분리할 수 있는 영역까지만 진행했다.

6. DOCX 생성 코드도 공통 부분만 꺼냈다

양도소득세와 증여세 문서 생성 코드에는 같은 형태의 DOCX 스타일 처리와 숫자 포맷, 테두리 처리 등이 반복되고 있었다.

그래서 공통 폰트, 한국식 정수 포맷, 단일선·전체 테두리 같은 부분만 documentCommon/docx.js로 추출했다.

반면 세금별 문구, 계산 내용, 색상, 레이아웃, AI 프롬프트처럼 업무 의미가 있는 부분은 건드리지 않았다.

공통 기술 요소  → 추출
세목별 업무 규칙 → 유지

"비슷해 보인다"가 아니라
"같은 책임을 가진다"는 것만 공통화

7. 중간에 Node 버전 차이라는 뜻밖의 문제도 만났다

리팩토링 도중 로컬 또는 서버 쉘에서 테스트를 돌릴 때 better-sqlite3의 ABI가 맞지 않는 문제가 나타났다. 처음 보면 패키지가 깨진 것처럼 보일 수 있었다.

하지만 확인해 보니 운영 PM2는 Node 22.23.2를 사용하고 있었고, 일반 쉘의 Node 버전은 달랐다.

여기서 중요한 건 패키지를 바로 재빌드하지 않았다는 것이다. 운영 중인 앱이 정상인데 테스트 환경만 다른 것일 수 있으니 먼저 실제 PM2가 어떤 Node를 사용하고 있는지 확인한 뒤, 운영과 동일한 Node 22.23.2에서 다시 검증했다.

증상만 보고 즉시 패키지를 다시 설치하거나 재빌드했다면, 오히려 정상 운영 중인 환경을 건드릴 수도 있었다. 코드 문제인지 환경 문제인지 먼저 분리해서 보는 과정이 필요했다.

8. 그리고 리팩토링이 숨어 있던 DB 문제 하나를 드러냈다

스키마 테스트를 강화하면서 financial_pl이라는 별도의 잠복 문제가 발견됐다. 기존 운영 DB에서는 아무 문제도 없었지만, 완전히 빈 DB에서 새로 시작하면 재무 테이블이 만들어지지 않아 financialDb 로딩이 실패할 수 있었다.

원인은 재무 테이블 생성 SQL이 수동 실행용 migration 파일에는 있었지만, 실제 신규 Cabinet DB 초기화 경로에서는 실행되지 않았기 때문이었다.

기존 운영 DB
financial_pl 이미 존재
→ 정상

완전히 새 DB
financial_pl 생성 로직 누락
→ financialDb가 쿼리 prepare
→ no such table 가능

이 문제는 리팩토링 도중 바로 섞어서 고치지 않았다. 6단계 리팩토링을 모두 끝낸 뒤 fix/financial-pl-schema라는 별도 브랜치에서 수정했다.

수정 후에는 빈 임시 DB에서 financial_pl, financial_vat 생성과 financialDb 로딩을 확인했고, 실제 운영 DB의 행 수도 병합 전후 비교했다.

운영 DB 항목확인 결과
DB 무결성 검사ok
cabinet_clients24건 유지
cabinet_files6건 유지
financial_pl154건 유지
financial_vat 거래 상세 데이터 32,606행 유지
위하고 매입매출장에서 파싱된 매출·매입 개별 거래 레코드

즉 “새 DB에서도 정상 생성된다”와 “기존 운영 데이터는 건드리지 않는다”를 둘 다 확인한 셈이다.

9. 오래된 실패 테스트도 하나 정리했다

전체 테스트에는 예전부터 상담예약 제목 관련 실패가 하나 남아 있었다. 실제 캘린더에는 상담 일정이 다음처럼 표시되고 있었다.

11:00 테스트 (기타)

하지만 오래된 테스트는 시간 없이 제목만 나오는 과거 형식을 기대하고 있었다. 확인해 보니 2026년 7월 말에 캘린더 제목 앞에 상담 시간을 표시하도록 기능이 의도적으로 바뀌었는데, 그때 테스트 기대값만 같이 갱신되지 않았던 것이었다.

따라서 실제 기능 코드는 건드리지 않고 테스트 기대값만 현재 사양에 맞춰 수정했다. 그 결과 기존에 매번 보이던 “원래 있던 1 fail”도 사라졌다.

이제 회귀 테스트에서 fail 0이 기준점이 되었다. 앞으로 빨간 실패가 하나라도 생기면 “원래 있던 거였나?”를 따질 필요 없이 새 변경에서 생긴 문제인지 바로 조사할 수 있다.

10. 이번 작업에서 Git 브랜치가 정말 유용했던 이유

며칠 전까지만 해도 브랜치를 공부하면서 “브랜치는 특정 커밋을 가리키는 움직이는 포인터”라고 이해했다. 개념적으로는 이해했지만, 이번 리팩토링에서 그 의미를 실제로 체감했다.

이번 작업은 대략 다음 식으로 진행됐다.

안정된 main
   │
   ├─ test/api-contract
   │      ↓ 검증 후 main 반영
   │
   ├─ fix/simple-tax-rate-schema
   │      ↓ 검증 후 main 반영
   │
   ├─ refactor/ui-core
   │      ↓ 검증 후 main 반영
   │
   ├─ refactor/cabinet-admin
   │      ↓ 검증 후 main 반영
   │
   ├─ refactor/server-routes
   │      ↓ 검증 후 main 반영
   │
   └─ refactor/document-common
          ↓ 검증 후 main 반영

각 브랜치는 그 단계의 변경만 담고 있었다. 그래서 만약 4단계에서 문제가 생겼다면 1~3단계까지 완료된 안정적인 상태를 그대로 둔 채 4단계만 다시 살펴볼 수 있었다.

이 점이 단순 백업과는 완전히 달랐다.

기준점 1단계 2단계 3단계 4단계 5·6단계 완료 작업 브랜치 별도 검증 후 승격
main은 검증된 기준점으로 유지하고, 변경은 별도 브랜치에서 만든 뒤 한 단계씩 승격했다.

11. 로컬 · GitHub · 서버가 서로 다른 저장소라는 것도 훨씬 선명해졌다

이번 작업에서는 내 PC에서 브랜치를 만들고 GitHub에 push한 뒤, 실제 서버에서 그 브랜치를 받아 운영 환경의 Node 버전으로 테스트했다. 검증이 끝나면 서버의 main에 fast-forward 병합하고, 그 서버 main을 다시 GitHub로 push했다. 마지막으로 로컬 main을 pull해서 세 곳을 같은 커밋으로 맞췄다.

로컬 PC
작업 브랜치 commit
      │
      ▼ push
GitHub 작업 브랜치
      │
      ▼ fetch / pull
운영 서버에서 검증
      │
      ▼ fast-forward merge
서버 main
      │
      ▼ push
GitHub main
      │
      ▼ pull
로컬 main

이 과정을 반복하다 보니 main, origin/main, 서버의 main이 “알아서 한 몸처럼 움직이는 것”이 아니라 각각 별도의 포인터라는 사실이 자연스럽게 이해됐다.

Git에서 “동기화”란 어떤 신비한 자동 기능이 아니었다. 서로 다른 저장소의 브랜치 포인터를 같은 커밋을 가리키도록 명시적으로 맞추는 과정에 가깝다.

12. fast-forward merge가 왜 깔끔한지도 실감했다

각 작업 브랜치는 항상 최신 main에서 시작했고, 그 작업을 하는 동안 main을 따로 변경하지 않았다. 그래서 검증 후 병합할 때 대부분 fast-forward가 가능했다.

작업 전

A ── B
     ↑
    main


브랜치 작업

A ── B ── C ── D
     ↑         ↑
    main      feature


검증 후 fast-forward

A ── B ── C ── D
                  ↑
              main, feature

별도의 병합 커밋을 만들 필요 없이 main이라는 이름표만 검증된 마지막 커밋으로 이동한다. 브랜치가 단순한 포인터라는 개념이 가장 직관적으로 보이는 순간이었다.

13. ‘되돌릴 수 있다’는 것이 개발 방식 자체를 바꿨다

예전에는 서버의 코드를 직접 고치는 일이 많았다. 문제가 생기면 “방금 뭘 바꿨더라?”를 떠올리면서 파일을 다시 수정해야 했다.

Git을 제대로 사용하면 사고방식이 달라진다.

예전 방식
수정 → 이상 발생 → 다시 파일을 손으로 고침

Git 기반 방식
변경 격리 → 커밋 → 검증 → 승격

문제 발생
→ 어느 단계인지 확인
→ 어떤 커밋인지 확인
→ 해당 변경만 되돌리거나 다시 수정

특히 이미 공유된 main에서는 과거 자체를 지워버리는 reset보다 문제가 된 변경의 반대 커밋을 새로 만드는 revert가 더 안전하다는 이유도 실제 운영 흐름 안에서 이해되기 시작했다.

14. AI 코딩 도구를 쓰는 입장에서도 리팩토링의 효과가 컸다

나는 실제 코딩의 상당 부분을 Codex나 Claude Code 같은 AI 도구와 함께 한다. 그런데 파일 하나에 700~800줄이 넘는 서로 다른 기능이 섞여 있으면 사람뿐 아니라 AI도 작업 범위를 넓게 읽고 판단해야 한다.

반대로 책임이 분리되어 있으면 지시 자체가 훨씬 명확해진다.

예전:
"cabinet-admin.js에서 고객관리 부분만 고쳐줘"
→ 같은 파일에 보고서·재무·문서·이벤트가 모두 존재

리팩토링 후:
"clients.js의 고객 CRUD만 수정해줘"
→ 작업 경계가 훨씬 명확

AI가 더 똑똑해지는 것과 별개로, 코드 구조가 AI가 실수하기 어려운 형태가 되는 것도 중요하다는 생각이 들었다.

좋은 구조는 사람에게만 읽기 좋은 것이 아니다. 작업 범위가 명확해지기 때문에 AI에게도 더 정확한 컨텍스트를 줄 수 있다.

15. 그렇다고 웹앱이 갑자기 빨라진 것은 아니다

이번 리팩토링의 효과를 “성능이 좋아졌다”라고 표현하면 조금 다르다. 사용자가 화면을 열었을 때 체감하는 로딩 속도나 API 응답 속도를 직접 최적화한 작업은 아니기 때문이다.

이번에 좋아진 것은 개발과 유지보수의 속도, 변경의 안전성, 문제 추적의 용이성에 가깝다.

항목이번 리팩토링 효과
페이지 로딩 속도직접적인 성능 최적화 목적은 아님
코드 읽기책임별 분리로 범위 축소
기능 수정영향 범위를 좁히기 쉬워짐
테스트계약·회귀 테스트 기준점 강화
장애 원인 추적단계와 브랜치 기준으로 좁히기 쉬워짐
AI 코딩작업 컨텍스트와 책임 범위가 더 명확

실제 속도를 개선하려면 별도로 API 응답시간, SQLite 쿼리, 프런트 로딩, 스크립트와 이미지 요청 등을 측정한 뒤 병목을 찾아야 한다.

16. 마지막 검증은 ‘테스트 통과’만으로 끝내지 않았다

로컬 테스트가 통과했다고 바로 끝내지는 않았다. 실제 운영 서버가 사용하는 Node 22.23.2 환경에서 다시 테스트했고, PM2 프로세스와 내부 health, 외부 도메인의 health까지 확인했다.

코드 테스트
      ↓
운영 Node 버전에서 재검증
      ↓
PM2 online 확인
      ↓
내부 /api/health → HTTP 200
      ↓
외부 crm 도메인 /api/health → HTTP 200
      ↓
server main / GitHub main / local main HEAD 일치 확인

결국 “코드가 맞다”와 “실제 서비스가 정상이다”는 서로 다른 확인 절차였다.

17. 오늘 작업이 꽤 의미 있었다고 느끼는 이유

오늘 실제 사용자에게 보이는 새로운 버튼을 만든 것도 아니고, 화려한 기능을 추가한 것도 아니다. 그런데 작업이 끝난 뒤 프로젝트를 바라보는 느낌은 확실히 달라졌다.

이전에는 기능들이 잘 돌아가기는 하지만 내부적으로 한 파일에 여러 역할이 섞여 있고, 기존 실패 테스트도 하나 남아 있으며, 빈 DB에서만 나타나는 잠재 오류도 숨어 있었다.

이제는 주요 기능이 책임별로 나뉘었고, 계약 테스트와 회귀 테스트가 그 구조를 감싸고 있으며, 테스트 기준도 fail 0으로 깨끗해졌다.

무엇보다 Git을 공부하면서 배운 브랜치, 커밋, HEAD, fast-forward 같은 개념이 책 속의 그림이 아니라 실제 운영 웹앱을 안전하게 바꾸는 도구로 연결됐다.

Git은 단순히 코드를 백업하는 프로그램이 아니었다.
변경을 격리하고, 검증하고, 승격하고, 필요하면 특정 변경만 되돌릴 수 있게 만드는 변경 관리 시스템이라는 말이 오늘은 훨씬 실감났다.

18. 이번 리팩토링 전체 흐름을 한 장으로 정리하면

안정된 main 검증된 현재 버전 작업 브랜치 생성 API 계약 → UI Core → Cabinet → Server Routes → DOCX Common 각 단계마다 별도 branch · commit · push 자동 테스트 계약 · 회귀 · 스키마 · 문서 git diff –check 실제 서버 검증 Node 22.23.2 · PM2 내부/외부 health 200 검증된 변경만 main으로 승격 server main = GitHub main = local main 작은 변경 단위 + 명확한 검증 + 되돌릴 수 있는 기록
이번 작업의 핵심은 ‘크게 고쳤다’가 아니라 ‘큰 변경을 작은 검증 단위로 쪼개 안전하게 통과시켰다’는 데 있었다.

19. 마치며

처음 Git을 공부할 때 브랜치를 “가상 작업공간”이라고 설명하는 글을 많이 봤다. 그 표현도 편하지만, 직접 사용해 보니 결국 브랜치는 특정 커밋을 가리키는 아주 가벼운 이름표라는 설명이 더 본질에 가까웠다.

그 단순한 구조 위에서 여러 변경을 각각 격리하고, 검증이 끝난 변경만 main으로 이동시키는 흐름이 만들어진다. 오늘처럼 꽤 큰 리팩토링에서도 각 단계를 따로 통제할 수 있었던 이유다.

이번 작업을 하기 전에도 새솔CRM은 돌아가고 있었다. 하지만 지금은 단순히 “돌아가는 프로그램”에서 조금 더 나아가, 다음 변경을 받아들이기 쉬운 프로그램이 된 느낌이다.

리팩토링의 가치가 바로 이런 것인지도 모르겠다. 사용자 화면에서 당장 크게 달라 보이지 않아도, 앞으로 수십 번의 수정과 기능 추가를 더 안전하고 편하게 만들어 주는 일.

그리고 개인적으로는 오늘 작업을 통해 Git을 책에서 배운 것보다 훨씬 많이 이해한 것 같다. branch, HEAD, commit, push, pull, fast-forward가 더 이상 각각의 명령어가 아니라, 하나의 실제 개발 흐름으로 연결됐기 때문이다.

오늘 리팩토링에서 남은 것

  • 큰 파일을 역할별 모듈로 분리했다.
  • 서버 코드를 Route · Service · Repository로 나눴다.
  • 공통 DOCX 유틸리티를 추출했다.
  • 계약 테스트와 회귀 테스트로 기존 동작을 보호했다.
  • 신규 DB에서만 나타나는 financial_pl 초기화 문제를 찾아 수정했다.
  • 오래된 상담예약 테스트 기대값을 현재 사양에 맞췄다.
  • 운영 Node 환경에서 다시 검증하고 health 200을 확인했다.
  • 로컬 · GitHub · 서버의 main을 같은 커밋으로 동기화했다.
  • 그리고 무엇보다 Git을 ‘백업’이 아니라 ‘변경 관리’의 관점에서 이해하게 됐다.

댓글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 기록은 여기까지지만,
생각은 아직 이동 중이다