소프트웨어 개발 경력에서 읽은 가장 가치 있는 책 중 하나에서 배운 가장 유용한 개념이 바로 "Don't Repeat Yourself(DRY)"입니다. 중복 코드를 리팩토링으로 제거할 수 있다면, 더 범용적이고 안정적인 코드를 만들 수 있습니다. 하지만 코드에 DRY를 적용하기 시작하면 여러 문제에 부딪히게 됩니다. 엣지 케이스(edge case)를 제대로 처리하지 못하는 코드, 지나치게 일반화되어 읽기 어려운 코드, 심지어 찾기조차 힘든 코드가 대표적입니다. DRY를 향한 리팩토링이 항상 옳지 않다면, 대체 언제 리팩토링해야 하는 걸까요?
본질적 중복 vs 우연적 중복
지나치게 DRY한 코드는 '어떤 종류의 중복을 제거해야 하는가'에 대한 오해에서 비롯됩니다. 본질적(essential) 중복과 우연적(accidental) 중복의 차이를 구분할 줄 알아야 합니다.
본질적 중복은 현재 다루고 있는 문제 영역 자체를 해결하는 코드에서 나오는 중복입니다. 이런 종류의 중복은 즉시 제거해야 합니다.
반면 우연적 중복은 당면한 문제와 무관한 중복, 즉 "우연히 겹쳐 보이는 중복"입니다. 우연적 중복을 너무 공격적으로 정리해 버리면, 새로운 사례가 추가될 때마다 결국 되돌려야 하는(un-refactoring) 취약한 코드만 남게 됩니다.
구분하는 방법: '세 번째 규칙'
그렇다면 둘을 어떻게 구분할 수 있을까요? 경험이 쌓이면 감이 좋아지겠지만, 수십 년간 프로그래밍을 해온 저조차 절반도 제대로 판단하지 못합니다. 다행히 큰 도움이 되는 간단한 규칙이 있습니다. 바로 "세 번 겹치면 리팩토링하라(Three Strikes and You Refactor)"입니다.
처음 어떤 작업을 할 때는 그냥 하세요.
두 번째 비슷한 작업을 할 때는 중복이 마음에 걸리지만, 그래도 일단 중복해서 진행하세요.
세 번째 비슷한 작업을 할 때, 그때 비로소 리팩토링하세요.
왜 이 규칙이 도움이 되는지 보이시나요? 세 번째 시점에는 패턴이 눈에 보이기 시작합니다. 어떤 중복 코드가 문제 해결에 필수적인지, 어떤 코드가 단순히 우연히 같아 보이는지 감을 잡을 수 있습니다. 그제서야 해결하려는 문제의 모든 사례에서 근본적으로 동일한 부분만 골라 일반화하고 DRY하게 만들 수 있습니다.
실전에 적용해 보기
잠시 지난날을 떠올려 보세요. 리팩토링 때문에 고통스러웠던 순간이 있었나요? 두 개의 사례 사이에 중복된 코드를 DRY하게 정리하려다가, 막상 세 번째 사례는 조금 달랐던 경험 말입니다.
다음에 코드를 또 복사해야겠다는 충동이 들면, 세 번째 복사본이 나타날 때까지 기다렸다가 리팩토링해 보세요. (정말 어렵고 괴롭게 느껴지지만, 눈을 감고 일단 해보세요.) 그리고 코드를 DRY하게 만드는 동안 스스로에게 물어보세요. 두 번째 복사본 직후에 성급하게 리팩토링했다면과 비교했을 때, 지금 리팩토링하는 방식이 달라지지 않았나요?