삭제보다 안정성: 대규모 프로젝트에서 레거시 코드와 함께 살아가는 전략
장기 운영되며 고부하를 견뎌야 하는 시스템에서는 레거시 코드를 삭제하는 것이 리그레션과 불안정을 유발할 수 있습니다. 전체 재구조화보다는, 기능 토글과 패사드를 활용해 새 코드와 오래된 코드가 함께 작동하도록 하는 것이 더 현명합니다. 이 방식은 다운타임 없이 지속적인 개발을 가능하게 하며, 검증된 컴포넌트를 백업으로 유지해 안정성을 확보합니다.
과도한 코드 제거의 위험성
사용되지 않는 코드라도 제거하면 은닉된 종속성(리플렉션, 동적 로딩, RPC 호출 등)으로 인해 실패가 발생할 수 있습니다. 레거시 시스템의 테스트는 흔히 예외 케이스를 커버하지 못하며, 실제 운영 데이터는 드문 실행 경로를 활성화하기도 합니다. 조직 내 지식 손실은 문제를 악화시킵니다. 퇴직하는 팀은 마이그레이션이나 준수 관련 특수 상황에 필요한 핵심 코드를 남겨두는 경우가 많습니다.
기업 환경에서는 벤더 전용 커스텀 모듈이 취약해집니다. 변경 사항이 벤더 지원을 무너뜨릴 수 있기 때문입니다.
"좋은 상태는 건드리지 말라" 원칙의 실천
NASA의 딥 스페이스 1 탐사선이 대표적인 사례입니다. 1970년대 코드는 그대로 보존되었으며 어댑터로 감싸져 있었습니다. 잘 작동하는 구성 요소는 변화 없이 유지하는 것이 이상적입니다. 이 성숙한 전략은 5년 이상 된 시스템에 적합합니다.
공존 아키텍처: 역설적인 스트랭글러 플랜트 전략
레거시 코드를 파괴하지 말고, 새로운 시스템을 병렬로 구축하고 점진적으로 트래픽을 이전하세요. 두 가지 전략을 선택할 수 있습니다:
| 전략 | 조치 | 적용 시기 |
|------|------|----------|
| 제거 | 새 스택으로 완전 재작성 | 단순 로직, 100% 테스트 커버리지, 종속성 없음 |
| 공존 | 새 코드와 함께 실행, 점진적 라우팅 | 핵심 기능, 약한 테스트, 높은 리스크 |
기능 토글을 통해 안전성을 확보하세요:
- 새 구현체를 개발합니다.
- 플래그 체크로 감싸줍니다.
- 트래픽의 1%에서 실행합니다.
- 성공 시 점차 확대합니다.
- 문제가 생기면 플래그로 롤백합니다.
레거시 코드는 2~3년간 저장되어 신뢰도 분석에 활용됩니다.
Spring Boot에서의 구현
원본 코드를 수정하지 않고 인터페이스로 감싸세요.
// 수정되지 않은 레거시 서비스
@Service
public class LegacyDiscountService {
public double calculateDiscount(Order order) {
// 5년 전 로직
return order.getTotal() * 0.1;
}
}
// 새 서비스
@Service
public class NewDiscountService {
public double calculateDiscount(Order order) {
return applyComplexRules(order);
}
}
토글이 포함된 패사드:
@Component
public class DiscountServiceFacade {
private final LegacyDiscountService legacyService;
private final NewDiscountService newService;
@Value("${feature.discount.new.enabled:false}")
private boolean useNewService;
public DiscountServiceFacade(LegacyDiscountService legacyService,
NewDiscountService newService) {
this.legacyService = legacyService;
this.newService = newService;
}
public double calculateDiscount(Order order) {
if (useNewService) {
return newService.calculateDiscount(order);
}
return legacyService.calculateDiscount(order);
}
}
프로파일을 통한 격리:
@Configuration
public class DiscountConfiguration {
@Bean
@ConditionalOnProperty(name = "feature.discount.new", havingValue = "false", matchIfMissing = true)
public DiscountService legacyDiscountService() {
return new LegacyDiscountService();
}
@Bean
@ConditionalOnProperty(name = "feature.discount.new", havingValue = "true")
public DiscountService newDiscountService() {
return new NewDiscountService();
}
}
레거시 테스트 제외:
@Test
@DisabledIf("!${feature.discount.new.enabled}")
void testNewDiscountLogic() {
// 새 로직만 테스트
}
트래픽 전환 워크플로우
API 게이트웨이는 플래그 기반으로 라우팅합니다. 모노리스는 백업으로, 마이크로서비스는 점진적으로 확장됩니다. 기능 토글 서비스가 실시간 비율을 관리합니다. 레거시 코드는 2~3년간 생산 환경에 유지됩니다.
삭제가 피할 수 없는 경우
- 보안: 오래된 라이브러리의 CVE 존재
- 준수: GDPR 위반 가능성
- 기술 종료: 패치 없는 자바 버전
- 전문 인력 부족: 전문가가 없음
이 경우에도 점진적 롤아웃과 롤백 기능을 반드시 사용하세요.
핵심 교훈
- 시스템 안정성은 코드 미학보다 우선합니다: 가동 시간은 삭제된 레거시 코드 줄보다 중요합니다.
- 은닉된 종속성은 삭제 전 실제 운영 호출을 모니터링해야 합니다.
- 기능 토글은 마이그레이션 리스크를 줄입니다.
- 유지보수 비용이 리팩토링보다 낮다면 기술 부채는 가치 있는 투자입니다.
- 코드 속에 남아있는 조직 기억: 100% 확신 없이 삭제하지 마세요.
— Editorial Team
아직 댓글이 없습니다.