상속받은 .NET 악몽? 레거시 코드를 다스리는 5가지 전략
Source: Dev.to

Introduction
개발자로서 결국 기존 코드베이스를 유지보수하거나 현대화하는 작업을 하게 되는 경우가 거의 불가피합니다. 이는 드물게 직관적이며, 핵심 로직이 얽힌 엉망인 경우—전형적인 big ball of mud—그 도전은 압도적으로 느껴질 수 있습니다. 이러한 명확성 부족은 자신감을 떨어뜨리고, 변경을 할 때마다 시스템이 예상치 못한 곳에서 깨질 위험을 높입니다.
레거시 .NET 코드란 무엇인가?
일반적인 오해는 레거시 코드는 반드시 오래된 것이라고 생각하는 것입니다. 연령이 요인이 될 수는 있지만, 최신 프레임워크를 사용하면서도 마감일을 맞추기 위해 조직이 부실하고 임시방편으로 조합된 시스템도 레거시가 될 수 있습니다. 레거시 코드를 정의하는 요소는 다양하지만, 오늘은 가장 큰 두 가지에 집중하겠습니다.
- 테스트 없음 – 코드에 테스트가 전혀 없으면 레거시입니다. Michael Feathers1는 “테스트가 없는 코드”를 레거시 코드라고 유명하게 정의했는데, 이는 안전망이 없기 때문에 시스템을 변경하는 것이 위험하고 비용이 많이 들기 때문입니다.
- 자동 배포 없음 – 코드와 인프라에 자동 배포가 전혀 없으면 레거시입니다. 클라우드 인프라에 호스팅되는 현대 소프트웨어는 자동화를 통해 손쉽게 배포될 수 있어야 합니다. 릴리즈가 개발자의 워크스테이션에서 복잡한 수동 작업을 필요로 한다면, 애플리케이션을 재배포하는 위험이 너무 높아집니다.
.NET 애플리케이션을 다시 구축해야 할까요?
대규모 .NET 모놀리스를 인수하게 되면, 이를 완전히 없애고 새로 시작하는 것이 매우 매력적으로 보일 수 있습니다. 유혹적인 생각이지만, “빅뱅” 재작성은 거의 항상 함정입니다.
이러한 대규모 재작성은 추정보다 오래 걸리는 경우가 일관되게 발생합니다. 개발자들이 화면이나 API에 대한 피상적인 분석만을 기반으로 일정을 잡기 때문입니다. 이 접근 방식은 코드베이스 깊숙이 숨겨진 문서화되지 않은 비즈니스 규칙을 놓치기 쉽고, 이러한 규칙은 때때로 프로덕션에서 버그가 발생할 때까지 발견되지 않습니다. 게다가 새로운 개발은 재작성 작업이 끝날 때까지 중단됩니다; 혁신 없이 2년 동안 새로운 기술을 도입하는 데만 시간을 소비하면 비즈니스는 재정적·경쟁적 손실을 입게 됩니다.

.NET 레거시 애플리케이션을 현대화하려면 무엇을 할 수 있을까?
대규모 재작성은 배제한다면, 어떻게 진행할 수 있을까? 먼저, 특정 사고방식을 채택하라: 신중하고 의도적으로.
크고 얽힌 코드베이스에서는 서두르면 시스템의 다른 부분에 알 수 없는 버그를 도입할 수 있다. 조심스럽게 진행해야 한다. 예를 들어, 메서드 시작 부분에 목적이 없어 보이는 가드 절을 발견하고 삭제했지만, 다음 릴리즈에서 문제가 발생할 수 있다. 이는 체스터턴의 울타리를 완벽히 보여준다: 레거시 코드 조각이 당신에게 명확한 목적이 없어 보인다고 해서 그것이 중요하지 않은 것은 아니다.
.NET 코드베이스를 현대화하기 위한 전략은 무엇인가요?
올바른 마인드셋을 갖추면 전술적인 솔루션을 적용할 수 있습니다. 여기 .NET 애플리케이션을 다시 제어할 수 있는 검증된 다섯 가지 전략을 소개합니다.
1. 특성화 테스트 – 안전망 구축
Michael Feathers가 강조하는 가장 중요한 전략 중 하나는 시스템이 현재 어떻게 동작하는지를 고정하는 것입니다(버그가 있더라도). 특성화 테스트는 시스템이 지금 실제로 하는 일을 검증하며, 해야 할 일을 검증하는 것이 아닙니다. 목표는 즉시 로직을 고치는 것이 아니라 테스트 가능한 기준점을 만드는 것입니다. 시스템 동작을 증명하는 안전망을 확보하면, 문서화되지 않은 비즈니스 규칙을 잃어버리지 않았다는 확신을 가지고 복잡한 코드를 리팩터링할 수 있습니다.

2. 스프라우트와 래핑
엄격한 시간 제약 때문에 주변의 난잡한 코드를 바로 리팩터링할 수 없을 때, Feathers는 안전하게 기능을 추가하는 두 가지 기법을 제안합니다:
- 스프라우트 – 새로운 기능을 완전히 새로운, 테스트가 완료된 메서드나 클래스로 작성한 뒤, 레거시 코드에서 해당 메서드를 호출합니다.
- 래핑 – 기존 메서드의 이름을 바꾸고, 원래 이름을 가진 새 메서드를 만든 뒤, 새 메서드가 기존 로직과 새로운 로직을 모두 실행하도록 합니다.
3. 보이 스카우트 규칙
Robert C. Martin(“Uncle Bob”)이 대중화한 보이 스카우트 규칙은 간단합니다: “코드를 찾을 때보다 더 깨끗하게 남겨라.” 메서드를 업데이트할 때는 모호한 변수명을 명확한 의미로 바꾸거나 사용되지 않는 매개변수를 제거하는 등 잠시 시간을 내어 개선합니다. 이러한 작은 개선이 시간이 지나면서 크게 누적되어 유지보수성을 크게 향상시킵니다. 테스트 스위트에도 동일한 규칙을 적용해 테스트를 정리하면, 구축한 안전망에 대한 신뢰도가 높아집니다.
4. 기능 플래그를 활용한 점진적 리팩터링
위험한 변경을 기능 플래그 뒤에 감쌉니다. 새로운 구현을 기존 구현과 함께 배포한 뒤, 정상 동작한다는 확신이 서면 점진적으로 트래픽을 새로운 코드로 전환합니다. 이 접근 방식의 장점은 다음과 같습니다:
- 전체 롤아웃 없이 프로덕션에서 동작을 검증할 수 있습니다.
- 문제가 발생하면 플래그를 토글해 즉시 롤백할 수 있습니다.
- 리팩터링 내내 코드베이스를 배포 가능한 상태로 유지합니다.
5. 자동화된 CI/CD 파이프라인 도입
레거시 시스템에 자동 배포가 없더라도 작은 단계부터 시작할 수 있습니다:
- CI 파이프라인을 만들어 푸시할 때마다 새로 추가한 특성화 테스트를 실행합니다.
- 빌드 단계를 추가해 애플리케이션을 패키징합니다(예:
dotnet publish사용). - 배포 단계를 도입해 GitHub Actions, Azure Pipelines, GitLab CI와 같은 도구를 이용해 스테이징 환경에 배포합니다.
이러한 단계들을 자동화하면 빠른 피드백을 얻고, 수동 오류를 줄이며, 향후 점진적인 개선을 위한 기반을 마련할 수 있습니다.
마무리 생각
레거시 .NET 모놀리스를 현대화하는 일은 드물게 빠르고 일회성 프로젝트가 아닙니다. 특성화 테스트로 안전망을 구축하고, Sprout/Wrap 기법을 적용하며, 보이 스카우트 규칙을 실천하고, 피처 플래그를 활용하며, CI/CD를 점진적으로 도입함으로써, 비용이 많이 드는 “빅뱅” 재작성 위험 없이도 가장 얽힌 코드베이스조차 다룰 수 있습니다.
4. 스트랭글러 피그 패턴
목표가 모놀리스를 모듈형 아키텍처로 현대화하는 것이라면, 가장 안전한 방법은 Strangler Fig Pattern입니다 – 이 개념은 Martin Fowler ↗(Strangler Fig Application)이 제안한 것입니다.
예를 들어, 10년 된 거대한 ASP.NET Web Forms 애플리케이션이 있고, 운영 팀이 새로운 모바일 친화적인 “Driver Dispatch” 모듈을 절실히 필요로 한다고 가정해 보세요.
전체 물류 시스템을 다시 작성하는 대신, 레거시 애플리케이션 앞에 라우팅 프록시(“Traffic Cop”)를 배치하고 초기에는 100 % 트래픽을 기존 시스템으로 라우팅합니다. 그런 다음 깔끔하게 설계되고 테스트 가능한 .NET 애플리케이션 안에 새로운 “Driver Dispatch” 기능을 구축합니다.
내부 팀이 오래된 모놀리스를 유지보수하느라 바쁘다면, .NET application development services와 같은 전문가 서비스를 활용해 첫날부터 새로운 아키텍처를 올바르게 구축하는 것이 좋은 시기입니다.
마지막으로 프록시 규칙을 업데이트하여 청구 클릭은 기존 시스템으로, 배차 클릭은 새로운 앱으로 원활하게 라우팅하도록 합니다. 사용자는 두 시스템을 동시에 사용하고 있음을 눈치채지 못하며, 이를 통해 시간을 두고 모듈을 천천히 추출할 수 있습니다.
5. Automate the “Magic” Deployments
시스템이 개발자가 SQL 스크립트를 수동으로 실행하거나 .dll 파일을 복사하거나 Visual Studio에서 “Publish” 를 오른쪽 클릭해야 한다면, 해당 시스템을 안전하게 현대화할 수 없습니다. 리팩터링은 빈번하고 위험도가 낮은 배포를 요구하지만, 수동 배포는 드물고 위험도가 높습니다.
핵심 아키텍처를 건드리기 전에 GitHub Actions 또는 Azure DevOps 와 같은 도구를 사용해 이러한 “매직” 단계를 CI/CD 파이프라인으로 코드화하십시오. 버튼 하나만 눌러도 안정적으로 컴파일하고, 테스트하고, 배포할 수 있다는 사실은 구조 개선 작업에서 오는 막대한 스트레스를 크게 줄여줍니다.
결론
레거시 소프트웨어를 구조화하는 것은 가장 영리한 C# 코드를 작성하는 것이 아니라 위험 관리에 관한 것입니다. 이 다섯 가지 전략은 레거시 .NET 시스템을 유지하거나 현대화하기 위해 통제된 점진적 변화를 시작하는 데 불과합니다.
복잡한 레거시 애플리케이션을 인수받았다고 해서 겁먹지 마세요. 테스트 안전망을 구축하고, 작고 통제된 변화를 적용하며, 코드가 테스트 가능하도록 보장하고, 배포를 자동화함으로써 두려운 레거시 시스템을 관리 가능한 시스템으로 바꿀 수 있습니다. 인내와 세심함, 그리고 올바른 워크플로우만 있다면 어떤 코드베이스도 비즈니스 요구에 맞게 제어할 수 있습니다.
1: Feathers, Michael C. (2005). Working Effectively with Legacy Code
Footnotes
-
Michael Feathers, Working Effectively with Legacy Code (Prentice Hall, 2004). ↩


