프로젝트 개요

jQuery로 운영되었던 기존 서비스를 React 기반으로 개편하는 업무를 맡았습니다.

기간: 4개월 대상: 5년간의 History가 담긴 Legacy 코드 목표: 기술 부채 해소 및 유지보수성 향상

이 적잖은 개발 기간 동안 Legacy 코드를 분석하고 재개발하면서 많은 부분을 느낄 수 있었습니다.

시작하기 전: 나 자신과의 다짐

결심: “이해하고 코드를 작성하자”

리뉴얼 작업을 시작하기 전, ‘나 자신’과의 다짐과 굳은 결심을 했습니다.

“이해하고 코드를 작성하자.”

왜 Ctrl+C, Ctrl+V를 하지 않았나?

기존 코드를 복사-붙여넣기 식으로 작업을 진행할 수도 있었습니다.

하지만 이렇게 하지 않은 이유:

  1. 기술 부채의 반복

    • 기존 문제가 그대로 이어짐
    • 향후 유지보수 비용 증가
  2. 미해결 이슈의 누적

    • 추후 발생할 수 있는 이슈의 투자 시간
    • 근본 원인 파악 불가능
  3. 문제의 악순환

    • 기존 프로젝트의 문제 패턴 반복
    • 개선의 기회 상실
  4. 리뉴얼의 목적 상실

    • 단순 기술 스택 변경이 아닌 근본적 개선이 목표
    • 형식만 바뀌고 본질은 그대로

레거시 코드와의 만남

”왜?, 왜지?”

회사 코드를 보면서 항상 내뱉었던 질문입니다.

만난 문제들:

  1. 난독화 수준의 핵심 코드

    // 실제 코드 예시 (단순화)
    function a(b,c,d,e,f,g){
      var x=b?c:d;
      var y=e&&f||g;
      return x?y:!y;
    }
    // 이게 뭐하는 함수인지 알 수 없음
  2. 죽은 코드(Dead Code)

    // 어디서도 사용하지 않는 함수들
    function unusedFunction() { }
     
    // 도달할 수 없는 코드
    if (false) {
      // 이 코드는 절대 실행되지 않음
    }
  3. 중복 코드

    // 10곳에서 거의 동일한 코드가 반복
    function validateEmail1() { /* ... */ }
    function validateEmail2() { /* ... */ }
    function validateEmailCopy() { /* ... */ }
  4. 의미 없는 코드와 상수

    const A = 1;  // A가 뭘 의미하는지 알 수 없음
    const B = 2;
    let temp = 0;  // temp가 무엇을 임시 저장하는지 불명확
  5. 복잡한 예외처리와 분기

    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)를 일으키는 함수로 변화
  • 여러 작업자를 거쳐가며 코드의 양만 증가

왜 변질되었나?

원인 분석:

  1. 기능 요구사항이 명확하지 않음

    • “할인 계산에 이것도 추가해주세요”
    • 어디에 어떻게 추가해야 하는지 불명확
  2. 변수명이 명확하지 않음

    • temp, data, result 같은 모호한 이름
    • 원래 의도 파악 불가
  3. 초기 작업자의 의도와 다르게 변질

    • 코드를 완전히 이해하지 못한 채 수정
    • 임시방편으로 코드 추가
  4. 코드 리뷰 부재

    • 아무도 전체적인 품질을 관리하지 않음
    • 기술 부채 누적

코드 리뷰의 필요성

깨달음

이를 통해 코드 리뷰의 필요성을 절실히 느꼈습니다.

코드 리뷰가 있었다면:

PR 제출
  ↓
리뷰어: "이 함수는 너무 많은 책임을 가지고 있습니다"
  ↓
리뷰어: "부수 효과가 있네요. 순수 함수로 리팩토링 해주세요"
  ↓
리뷰어: "변수명이 명확하지 않습니다"
  ↓
개선된 코드 머지
  ↓
건강한 코드베이스 유지

건강한 코드 유지를 위한 요소

프로젝트의 건강한 코드를 유지하려면:

  1. 작업자 간 협력

    • 코드 리뷰 문화
    • 지식 공유
    • 페어 프로그래밍
  2. 긴장감

    • “누군가 내 코드를 볼 것이다”
    • 더 나은 코드를 작성하려는 동기
  3. 책임감

    • 내 코드가 시스템에 미치는 영향 인식
    • 미래의 유지보수자를 배려

건강한 코드란?

정의

작업자 어느 누구라도 코드의 흐름과 문맥을 쉽게 파악이 가능하고, 코드 리뷰가 가능할 만한 부끄러움이 없는 코드.

특징:

  1. 가독성

    // ❌ 나쁜 코드
    function a(b,c){return b?c:0}
     
    // ✅ 좋은 코드
    function calculateDiscount(isVIP, basePrice) {
      return isVIP ? basePrice * 0.2 : 0;
    }
  2. 유지보수성

    // ❌ 나쁜 코드 - 하드코딩
    if (userType === 'VIP') return price * 0.2;
     
    // ✅ 좋은 코드 - 설정 분리
    const DISCOUNT_RATES = {
      VIP: 0.2,
      REGULAR: 0.1,
      NEW: 0.05
    };
    return price * DISCOUNT_RATES[userType];
  3. 테스트 가능성

    // ❌ 나쁜 코드 - 부수 효과
    function process(data) {
      globalState = data;  // 전역 상태 변경
      callAPI();  // 외부 API 호출
      return result;
    }
     
    // ✅ 좋은 코드 - 순수 함수
    function process(data) {
      return transformData(data);
    }
  4. 명확한 의도

    // ❌ 나쁜 코드
    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개월이었습니다.

다음 프로젝트를 위해

이번 경험을 통해 배운 것들을:

  • 다음 프로젝트에 적용하고
  • 팀 문화로 정착시키고
  • 후배 개발자들에게 전달하고 싶습니다

건강한 코드는 우연히 만들어지지 않습니다. 의도적인 노력과 지속적인 관리의 결과입니다.


참고 자료