실패로부터 배우기
"간단해 보이네, 금방 끝나겠지"라고 생각했다가 예상보다 오래 걸리는 경험은 누구나 한 번쯤 겪어봤을 것 같은데요. 저 역시 최근에 이런 경험을 했고, 그 과정에서 제가 어떤 부분을 잘못했었는지 살펴보려고 합니다.
배경
처음 이 업무를 맡았을 때는 단순해 보였습니다. 상품 테이블에 컬럼 하나를 추가하고, 그 컬럼을 통해 상품의 배송비 할인 여부를 체크한 후, 비즈니스 로직에 따라 할인을 적용하는 기능이었습니다.
// 이해를 돕기 위한 예시 코드 입니다.
public ShippingFee calculateShippingFee(List<Product> products) {
ShippingFee shippingFee = new ShippingFee(3000);
for (Product product : products) {
if (product.isDiscountable()) {
shippingFee = shippingFee.applyDiscount();
}
}
return shippingFee;
}
하지만 코드를 검토하면서 문제점을 발견했습니다. 배송비 계산 로직이 JSP 파일과 Java 코드에 분산되어 있었고, 특히 JSP 파일에 상당 부분의 로직이 구현되어 있었습니다. 이렇게 프레젠테이션 로직과 비즈니스 로직이 혼재된 구조는 코드의 가독성과 유지보수성을 크게 저하시키고 있었습니다. 더욱이 JSP는 컴파일러의 도움을 받기 어렵고 테스트 코드로 검증하는 것이 제한적인 환경입니다. 따라서 이러한 구조에 새로운 할인 로직을 추가하는 것은 리스크가 크다고 판단했습니다.
문제의 시작
JSP에 구현된 배송비 계산 로직을 그대로 Java로 옮기려고 했지만, 상품의 복잡성 때문에 배송비 계산을 위한 별도의 상품 모델을 만들며 진행했어야 했습니다. 그래서 우아한 모델을 만들거나 TDD를 적용하려 하기보다 전체적인 흐름을 파악하기 위해 대략적인 구현을 시도했습니다. 그러나 여러 가지 사정으로 인해 이 작업 자체가 지연되기 시작했습니다. 이 시점에서 작업이 왜 지연되고 있는지 분석하고, 혼자 해결할 수 없다면 주변 동료들에게 도움을 요청했어야 했습니다. 하지만 빠르게 작업을 마무리하겠다는 생각에 사로잡혀, 프로토타입처럼 작성된 코드를 기반으로 계속해서 진행하게 되었습니다. 빈약한 코드 베이스 위에서 계속 개발을 진행하다 보니 버그도 많이 발생하고 가독성도 좋지 않은 상태가 유지되었습니다.
문제의 연속
작업을 마무리할 즈음, 저는 중요한 부분을 간과했다는 것을 깨달았습니다. 배송비 계산 로직이 두 곳에서 필요했던 것입니다. 하나는 고객이 주문 전에 금액을 확인하는 단계였고, 다른 하나는 최종 주문이 생성되는 시점이었습니다. 사실 배송비 할인의 진정한 완료 조건은 할인이 적용된 주문이 생성되는 것이었습니다. 그러나 저는 단순히 배송비 계산 기능 구현이라는 중간 목표에만 집중하여 전체 업무의 목적을 놓쳤습니다. 그 결과, 뒤늦게 주문 완료 단계에서도 배송비 할인 기능을 추가해야 했습니다.
이로 인해 또 다른 문제가 발생했습니다. 배송비 계산이 두 곳에서 이루어지다 보니, 요구사항이 변경되거나 버그가 발견될 때마다 항상 두 곳을 함께 수정해야 했습니다. 레거시 시스템의 특성상 두 부분이 약간씩 다르게 구현되어 있어서, 처음에는 리팩터링을 진행하지 않았습니다. 하지만 작업이 진행될수록 이런 중복 수정이 계속 필요해져 비효율적이었습니다. 결국 배송비 계산 로직을 한 곳으로 통합하여 두 단계에서 모두 같은 로직을 사용하도록 변경한 후에야 남은 업무를 잘 마무리할 수 있었습니다.
인수 조건의 부재
저의 실패 사례를 보면 어떤 부분을 가장 먼저 조언해주고 싶으신가요? 저는 과거의 저에게 조언을 해줄 수 있다면 인수 조건의 부재에 대한 이야기를 해주고 싶습니다. 예를 들어 아래와 같은 간단한 인수 조건만 설정했더라도 좋았을 것입니다:
- 배송비 할인 로직이 적용된 경우, 주문 생성 시 할인된 배송비로 계산되어야 한다.
이렇게라도 설정하고 상황에 따라 세부 조건들을 추가하고 수정해가며 업무를 진행했더라면, 좀 더 효율적으로 작업이 진행되었을 것으로 생각됩니다. 만약 다시 해당 업무를 진행하게 된다면, 아래와 같은 순서대로 진행할 것 같습니다:
인수 조건을 설정한다. (진행하면서 세부 조건을 추가하고 수정함)
JSP의 로직을 Java로 다시 구현한다.
배송비 로직을 한 곳에서 처리하도록 리팩터링한다.
배송비 할인 로직을 구현한다.
마치며
이 글을 쓰게 된 가장 큰 이유는 제가 자신 있게 여겼던 부분에서 실패를 경험했기 때문입니다. 그동안 잘한다고 생각했던 영역에서 문제가 생기니 스스로에게 많은 실망을 했었습니다. 하지만 변명을 찾거나 과거에 연연하기보다는, 앞으로 같은 실수를 반복하지 않기 위해 이 경험을 공유하게 되었습니다. 이 글을 읽는 분들에게도 도움이 되어 비슷한 상황을 미리 피할 수 있기를 바랍니다.