대형 SI 프로젝트나 공공기관 차세대 시스템 구축에 투입되면, 어김없이 거대한 엑셀 파일 두 개를 마주하게 된다. 수개월의 일정이 빼곡히 적힌 WBS와 수백 페이지에 달하는 요구사항 정의서다. 폭포수처럼 위에서 아래로 흐르는 워터폴(Waterfall) 방법론의 본질과, 이 구조 속에서 개발자가 맞닥뜨리는 치명적인 문제, 그리고 이를 방어하기 위한 객체지향적 아키텍처 설계 전략에 대해 알아본다.
1. 워터폴(Waterfall) 방법론이란?
워터폴 방법론은 소프트웨어 개발 생명주기(SDLC)를 순차적인 단계로 나누어 진행하는 고전적인 방식이다. 물이 폭포에서 아래로 떨어지듯, 한 단계가 완전히 끝나야만 다음 단계로 넘어갈 수 있다.
진행단계
요구사항 분석 -> 시스템 설계 -> 구현 -> 테스트 -> 배포 및 유지보수
사용 이유
관리자 입장에서 프로젝트의 진척도를 퍼센트로 측정하기 쉽고, 각 단계별 산출물이 명확하게 남기 때문에 계약 기반의 외주 프로젝트에서 주로 사용
2. BDUF의 착각과 UAT(인수 테스트)의 재앙
워터폴 방법론은 하나의 거대한 전제를 깔고 있다. 바로 요구사항은 프로젝트 초기에 완벽하게 정의될 수 있다는 믿음, 즉 BDUF(Big Design Up Front)다. 하지만 현실의 비즈니스는 끊임없이 변하며, 고객은 눈으로 완성된 화면을 보기 전까지 자신이 무엇을 원하는지 정확히 알지 못한다.
피드백의 지연
개발의 90% 이상 완료된 후반부 통합 테스트나 UAT(사용자 인수 테스트) 단계에서야 고객이 결과물을 확인한다.
빅뱅(Big Bang) 리스크
뒤늦게 부정적인 피드백을 받거나 핵심 아키텍처의 성능 결함이 발견되면, 이미 설계와 DB 스키마가 굳어진 상태로 시스템 전체를 뒤엎어야 하는 대 참사가 발생한다. 폭포수는 거꾸로 흐를 수 없기 때문이다.
3. 워터폴의 경직성을 방어하는 코드 설계 전략
요구사항 변경은 필연적이므로, 후반부에 쏟아질 변경 요청의 충격을 흡수할 수 있도록 코드를 유연하게 설계해야 한다.
Bad Code: 기획서만 믿고 짠 절차 지향적 하드코딩
기획서에 명시된 일반고객과 VIP 고객의 할인 정책만을 보고, 서비스 로직 내부에 제어문을 강하게 결합시킨 최악의 코드다.
@Service
public class OrderService {
// 문제: 오픈 1주일 전, 기획팀에서 "VVIP 등급과 신규 가입자 전용 할인을 추가해 주세요"라고 한다면?
// 이 클래스의 코드를 직접 수정해야 하며, 이는 OCP(개방-폐쇄 원칙) 위반이다.
public int calculateDiscount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price - 1000;
} else if (member.getGrade() == Grade.NORMAL) {
return price; // 할인 없음
}
// 끝없는 else if의 늪...
return price;
}
}
Good Code: 다형성을 활용한 변경에 열려있는 설계
워터폴의 폭탄(막판 요구사항 변경)을 방어하기 위해서는 비즈니스 로직을 추상화하여 OCP를 준수해야 한다.
// 1. 변하는 것(할인 정책)을 인터페이스로 추상화한다.
public interface DiscountPolicy {
int discount(Member member, int price);
boolean isSupport(Grade grade);
}
// 2. 구체적인 할인 정책을 구현한다. (새로운 정책이 추가되어도 기존 코드는 수정되지 않는다)
@Component
public class VipDiscountPolicy implements DiscountPolicy {
@Override
public int discount(Member member, int price) { return price - 1000; }
@Override
public boolean isSupport(Grade grade) { return grade == Grade.VIP; }
}
@Service
@RequiredArgsConstructor
public class OrderService {
// 3. Spring의 List 주입을 통해 모든 할인 정책을 동적으로 관리한다.
private final List<DiscountPolicy> discountPolicies;
public int calculateDiscount(Member member, int price) {
return discountPolicies.stream()
.filter(policy -> policy.isSupport(member.getGrade()))
.findFirst()
.map(policy -> policy.discount(member, price))
.orElse(price); // 해당하는 정책이 없으면 원가 반환
}
}
아키텍처적 이점
워터폴 막바지에 새로운 고객 등급이나 할인 정책이 추가되더라도, OrderSerivce의 핵심 로직은 단 한 줄도 수정할 필요가 없다. 그저 DiscountPolicy를 구현한 새로운 클래스만 하나 추가하면 스프링이 알아서 의존성을 주입하고 분기 처리를 완료한다.
결론
워터폴 방법론 자체가 절대적인 악은 아니다. 우주선 제어 시스템이나 의료 기기 소프트웨어처럼 요규사항이 절대로 변해서는 안 되고 치밀한 사전 검증이 필요한 도메인에서는 여전히 최고의 선택이다.
하지만 요구사항 변화가 극심한 현대의 웹/서버 비즈니스 환경에서는 워터폴의 경직성이 독이 되는 경우가 많다. 만약 현재 워터폴 기반의 프로젝트를 수행중이라면, 방법론의 한계를 명확히 인지하고 인터페이스와 다형성을 활용하여 언제든 기획이 뒤집힐 수 있다는 전제하에 아키텍처를 설계해야 한다. 방법론이 코드를 보호해 주지 못한다면, 개발자는 유연한 설계로 스스로를 보호해야 한다.
'Software Engineering' 카테고리의 다른 글
| [Software Enginnering] 애자일 선언문과 12가지 원칙 (0) | 2026.05.04 |
|---|---|
| [Software Enginnering] 에자일(Agile) 이란? (0) | 2026.04.16 |
| [Clean Code] 실무에서 알아야할 5가지 네이밍 컨벤션과 데이터 매핑 전략 (0) | 2026.04.07 |
| [Software Engineering] 테스트 주도 개발(TDD)란? (0) | 2026.04.06 |
| [Software Engineering] 트랜잭션 스크립트 패턴(Transaction Script Pattern) (0) | 2026.03.31 |
