프로젝트 개요
jQuery로 운영되었던 기존 서비스를 React 기반으로 개편하는 업무를 맡았습니다.
기간: 4개월 대상: 5년간의 History가 담긴 Legacy 코드 목표: 기술 부채 해소 및 유지보수성 향상
이 적잖은 개발 기간 동안 Legacy 코드를 분석하고 재개발하면서 많은 부분을 느낄 수 있었습니다.
시작하기 전: 나 자신과의 다짐
결심: “이해하고 코드를 작성하자”
리뉴얼 작업을 시작하기 전, ‘나 자신’과의 다짐과 굳은 결심을 했습니다.
“이해하고 코드를 작성하자.”
왜 Ctrl+C, Ctrl+V를 하지 않았나?
기존 코드를 복사-붙여넣기 식으로 작업을 진행할 수도 있었습니다.
하지만 이렇게 하지 않은 이유:
-
기술 부채의 반복
- 기존 문제가 그대로 이어짐
- 향후 유지보수 비용 증가
-
미해결 이슈의 누적
- 추후 발생할 수 있는 이슈의 투자 시간
- 근본 원인 파악 불가능
-
문제의 악순환
- 기존 프로젝트의 문제 패턴 반복
- 개선의 기회 상실
-
리뉴얼의 목적 상실
- 단순 기술 스택 변경이 아닌 근본적 개선이 목표
- 형식만 바뀌고 본질은 그대로
레거시 코드와의 만남
”왜?, 왜지?”
회사 코드를 보면서 항상 내뱉었던 질문입니다.
만난 문제들:
-
난독화 수준의 핵심 코드
// 실제 코드 예시 (단순화) function a(b,c,d,e,f,g){ var x=b?c:d; var y=e&&f||g; return x?y:!y; } // 이게 뭐하는 함수인지 알 수 없음 -
죽은 코드(Dead Code)
// 어디서도 사용하지 않는 함수들 function unusedFunction() { } // 도달할 수 없는 코드 if (false) { // 이 코드는 절대 실행되지 않음 } -
중복 코드
// 10곳에서 거의 동일한 코드가 반복 function validateEmail1() { /* ... */ } function validateEmail2() { /* ... */ } function validateEmailCopy() { /* ... */ } -
의미 없는 코드와 상수
const A = 1; // A가 뭘 의미하는지 알 수 없음 const B = 2; let temp = 0; // temp가 무엇을 임시 저장하는지 불명확 -
복잡한 예외처리와 분기
if (a) { if (b) { if (c) { if (d) { if (e) { // 5단계 중첩... } } } } }
고민: React 프로젝트에 어떻게 담을 것인가?
이 모든 레거시를 React로 어떻게 변환할 것인가?
단순 변환이 아닌, 개선을 목표로 했습니다.
채택한 방법론
방법 1: 기능의 이해
Step 1 - 사용자 입장에서 체험
레거시 코드 참고 전에
→ 실제 서비스 사용해보기
→ 기능의 의도 파악
→ 사용자 플로우 이해
장점:
- 코드에 편향되지 않은 순수한 이해
- 실제 사용자 관점에서의 요구사항 파악
- 불필요한 기능 식별 가능
Step 2 - 문서 참고
기획 문서 검토
→ 개발 문서 검토
→ 유효성 체크 규칙 파악
→ 예외 처리 케이스 정리
장점:
- 공식적인 스펙 확인
- 의도된 동작과 실제 구현 비교
- 누락된 기능 발견
Step 3 - 코드 분석 및 평가
Legacy 코드 분석
→ 의도와 구현 비교
→ 개선 방안 도출
→ 자체 코드 평가
평가 기준:
- 가독성
- 유지보수성
- 성능
- 확장성
방법 2: Help Me - 살아있는 문서 활용
문제점:
- 프로젝트 크루(기존 작업자, 기획자, PM)의 도움이 필요
- 서비스 로직이 강한 코드는 신규 작업자가 이해하기 어려움
- 문서 검색만으로는 시간 소모가 너무 큼
해결책: 걸어다니는 문서 활용
기획서 참고 (정적 문서)
↓
개발 문서 검색 (정적 문서)
↓
??? 여전히 이해 안 됨 ???
↓
기존 작업자에게 직접 물어보기! (살아있는 문서)
↓
커피 한 잔으로 30분 설명 듣기
↓
몇 시간의 삽질 절약
핵심:
- 살아 숨 쉬는 문서들이 회사에 존재합니다
- 그분들이 떠나시기 전에 도움을 요청하세요
- 커피를 제공하면서 걸어다니는 문서를 이용하세요
얻은 것:
- 코드의 역사적 맥락
- 특정 구현의 이유
- 숨겨진 비즈니스 로직
- 함정과 주의사항
방법 3: Cooperation - 작업자 간 협력
실시간 업무 내용 공유:
작업자 A가 공통 유틸 함수 개발
↓
슬랙으로 공유
↓
작업자 B, C도 활용
↓
중복 작업 방지 + 일관성 확보
공통 헬퍼/유틸 함수 제공:
// 공통 유틸 라이브러리
// utils/validation.js
export const validateEmail = (email) => { /* ... */ };
export const validatePhone = (phone) => { /* ... */ };
// utils/formatter.js
export const formatDate = (date) => { /* ... */ };
export const formatCurrency = (amount) => { /* ... */ };작업자 간 시너지:
- 코드 재사용성 증가
- 일관된 코드 스타일
- 버그 수정 시 일괄 적용
- 학습 곡선 단축
프로젝트 결과
완료와 QA
해당 방법을 통해 ‘리모트 미팅’ 서비스 리뉴얼 작업이 완료되었습니다.
QA 진행 중:
- 발견되는 이슈량은 상당함
- 하지만 이는 당연한 결과이자 거쳐야 할 큰 산
- 적잖은 시간만 투자하면 쉽게 해결될 것
긍정적 관점:
- 이슈가 발견된다는 것은 철저한 테스트의 증거
- 출시 전에 발견하는 것이 출시 후보다 훨씬 나음
- 각 이슈 해결로 시스템이 더욱 견고해짐
깨달음: 함수의 변질
Git History를 훑어보며
특정 함수의 Git 히스토리를 추적했습니다.
5년간의 변화:
// 2016년 - 첫 커밋
function calculateDiscount(price) {
return price * 0.1; // 맑고 깨끗한 순수 함수
}
// 2018년
function calculateDiscount(price, userType) {
if (userType === 'VIP') return price * 0.2;
return price * 0.1;
}
// 2019년
function calculateDiscount(price, userType, seasonalEvent) {
// API 호출 추가 (Side Effect!)
const eventDiscount = fetchEventDiscount();
if (seasonalEvent) return price * eventDiscount;
if (userType === 'VIP') return price * 0.2;
return price * 0.1;
}
// 2021년
function calculateDiscount(price, userType, seasonalEvent, coupon, membershipLevel, ...) {
// 전역 변수 수정 (Side Effect!)
globalDiscountApplied = true;
// 복잡한 분기 처리
// 죽은 코드 포함
// 중복 로직 산재
// ... 100줄 이상의 코드
}관찰 결과:
- 함수의 첫 시작은 맑디 맑은 순수 함수
- 5년 후의 결과물은 부수 효과(Side Effect)를 일으키는 함수로 변화
- 여러 작업자를 거쳐가며 코드의 양만 증가
왜 변질되었나?
원인 분석:
-
기능 요구사항이 명확하지 않음
- “할인 계산에 이것도 추가해주세요”
- 어디에 어떻게 추가해야 하는지 불명확
-
변수명이 명확하지 않음
temp,data,result같은 모호한 이름- 원래 의도 파악 불가
-
초기 작업자의 의도와 다르게 변질
- 코드를 완전히 이해하지 못한 채 수정
- 임시방편으로 코드 추가
-
코드 리뷰 부재
- 아무도 전체적인 품질을 관리하지 않음
- 기술 부채 누적
코드 리뷰의 필요성
깨달음
이를 통해 코드 리뷰의 필요성을 절실히 느꼈습니다.
코드 리뷰가 있었다면:
PR 제출
↓
리뷰어: "이 함수는 너무 많은 책임을 가지고 있습니다"
↓
리뷰어: "부수 효과가 있네요. 순수 함수로 리팩토링 해주세요"
↓
리뷰어: "변수명이 명확하지 않습니다"
↓
개선된 코드 머지
↓
건강한 코드베이스 유지
건강한 코드 유지를 위한 요소
프로젝트의 건강한 코드를 유지하려면:
-
작업자 간 협력
- 코드 리뷰 문화
- 지식 공유
- 페어 프로그래밍
-
긴장감
- “누군가 내 코드를 볼 것이다”
- 더 나은 코드를 작성하려는 동기
-
책임감
- 내 코드가 시스템에 미치는 영향 인식
- 미래의 유지보수자를 배려
건강한 코드란?
정의
작업자 어느 누구라도 코드의 흐름과 문맥을 쉽게 파악이 가능하고, 코드 리뷰가 가능할 만한 부끄러움이 없는 코드.
특징:
-
가독성
// ❌ 나쁜 코드 function a(b,c){return b?c:0} // ✅ 좋은 코드 function calculateDiscount(isVIP, basePrice) { return isVIP ? basePrice * 0.2 : 0; } -
유지보수성
// ❌ 나쁜 코드 - 하드코딩 if (userType === 'VIP') return price * 0.2; // ✅ 좋은 코드 - 설정 분리 const DISCOUNT_RATES = { VIP: 0.2, REGULAR: 0.1, NEW: 0.05 }; return price * DISCOUNT_RATES[userType]; -
테스트 가능성
// ❌ 나쁜 코드 - 부수 효과 function process(data) { globalState = data; // 전역 상태 변경 callAPI(); // 외부 API 호출 return result; } // ✅ 좋은 코드 - 순수 함수 function process(data) { return transformData(data); } -
명확한 의도
// ❌ 나쁜 코드 const x = data.filter(d => d.a > 10).map(d => d.b); // ✅ 좋은 코드 const activeUsers = users .filter(user => user.loginCount > 10) .map(user => user.email);
배운 교훈
1. 이해가 먼저다
Ctrl+C, Ctrl+V는 금물
- 코드의 의도를 이해하지 못하면
- 문제는 반복되고
- 기술 부채는 쌓인다
2. 문서도 중요하지만 사람이 더 중요하다
살아있는 문서를 활용하라
- 기존 작업자의 경험과 지식
- 문서로는 전달되지 않는 컨텍스트
- 커피 한 잔의 가치
3. 협업은 필수다
혼자 하는 것보다 함께 하는 것이 낫다
- 공통 라이브러리 구축
- 실시간 소통
- 지식 공유
4. 코드 리뷰는 선택이 아닌 필수다
건강한 코드베이스를 위해
- 코드의 변질 방지
- 지속적인 품질 관리
- 팀의 코드 품질 향상
5. 시간이 지날수록 코드는 부패한다
적극적인 관리가 필요하다
- 정기적인 리팩토링
- 죽은 코드 제거
- 명확한 네이밍
- 단순함 유지
감사의 말
이 프로젝트를 완성할 수 있었던 것은 주위 동료들의 도움 덕분입니다.
감사드립니다:
- 레거시 코드에 대해 설명해주신 선배 개발자분들
- 함께 고민하고 코드 리뷰를 해주신 동료분들
- 인내심을 가지고 QA를 진행해주신 QA팀
- 지원을 아끼지 않으신 PM과 기획자분들
마치며
지속적인 코드 관리
건강한 코드를 위해 필요한 것:
- 지속적인 코드 관리
- 프로젝트의 깊은 이해
- 활발한 코드 리뷰 문화
이 모든 것을 몸소 느낀 4개월이었습니다.
다음 프로젝트를 위해
이번 경험을 통해 배운 것들을:
- 다음 프로젝트에 적용하고
- 팀 문화로 정착시키고
- 후배 개발자들에게 전달하고 싶습니다
건강한 코드는 우연히 만들어지지 않습니다. 의도적인 노력과 지속적인 관리의 결과입니다.