DRY 원칙
DRY 원칙이란?
DRYDon’t Repeat Yourself 원칙은 동일한 코드, 로직, 데이터를 반복하지 말라는 규칙입니다. 중복된 부분이 많을수록 유지보수가 어려워지고, 오류 발생 가능성이 커집니다. 하나의 로직이나 데이터는 단일화하여 필요할 때마다 재사용하는 방식이 DRY 원칙의 핵심입니다.
DRY 원칙 예시
다음 코드는 DRY 원칙을 위반한 예시입니다.
public void PrintUser1()
{
Console.WriteLine("Name: John, Email: john@example.com");
}
public void PrintUser2()
{
Console.WriteLine("Name: Jane, Email: jane@example.com");
}
DRY 원칙을 적용한 예시는 다음과 같습니다.
public void PrintUser(string name, string email)
{
Console.WriteLine($"Name: {name}, Email: {email}");
}
public void PrintUsers()
{
PrintUser("John", "john@example.com");
PrintUser("Jane", "jane@example.com");
}
이렇게 중복을 제거하면 유지보수성과 확장성이 크게 향상됩니다.
DRY 원칙의 장점
- 유지보수 용이: 중복된 부분을 모두 수정할 필요가 없습니다.
- 가독성 향상: 코드가 간결해지고 복잡성이 줄어듭니다.
- 효율적인 테스트: 테스트 범위가 줄어들어 유지보수가 쉽습니다.
DRY와 기술 부채 관리
기술 부채는 코드의 중복이나 비효율적인 설계로 인해 발생하는 유지보수 비용을 의미합니다. DRY 원칙을 무시하고 코드를 중복해서 작성하면 기술 부채가 점점 쌓이게 되어 나중에 이를 해결하는 데 많은 시간이 소요될 수 있습니다. DRY 원칙을 준수하면 기술 부채를 줄일 수 있지만, 지나치게 적용하면 코드가 과도하게 복잡해질 수 있으므로 적절한 균형을 유지하는 것이 중요합니다.
DRY 원칙 적용 시 유의할 점
DRY 원칙을 적용할 때는 코드의 중복을 제거하는 것만이 목적이 아닙니다. 코드의 가독성, 유지 보수성, 추상화 수준 등을 모두 고려해야 하며, 다음과 같은 점들을 유의할 필요가 있습니다.
의미 있는 중복과 불필요한 중복 구분
DRY 원칙은 불필요한 중복을 제거하는 데 중점을 둡니다. 하지만 모든 중복이 제거되어야 하는 것은 아닙니다. 같은 코드라도 문맥에 따라 다른 의미를 가질 수 있으며, 이런 경우 중복 코드라도 분리하지 않는 것이 더 가독성이 좋을 수 있습니다. 의미가 명확한 중복은 그대로 두고, 진정으로 불필요한 중복만 제거하는 것이 중요합니다.
함수 분리로 인한 오버엔지니어링 방지
중복을 피하기 위해 지나치게 작은 단위로 코드를 함수로 분리하는 것은 오버엔지니어링이 될 수 있습니다. 함수가 너무 작아지거나 과도하게 분리되면 코드의 흐름을 파악하기 어렵고, 오히려 유지 보수가 힘들어질 수 있습니다. 최근 프로그래밍 언어들은 람다식, 고차 함수, 리스트 컴프리헨션 등의 기능을 통해 복잡한 작업을 간결하게 처리할 수 있습니다. 이런 코드를 굳이 함수로 분리할 필요는 없으며, 의도가 명확한 경우에는 한두 줄의 코드로 처리하는 것이 오히려 가독성과 유지 보수성을 높일 수 있습니다. 함수는 하나의 책임을 지니도록 분리하는 것이 이상적이며, 무조건적인 함수 분리는 피하는 것이 좋습니다.
추상화의 적정 수준 유지
DRY 원칙을 적용할 때는 추상화의 수준을 잘 조절해야 합니다. 지나치게 높은 추상화는 코드의 의미를 모호하게 만들고, 너무 낮은 추상화는 중복을 줄이지 못할 수 있습니다. 적절한 추상화는 해당 코드가 재사용될 가능성과 유지 보수의 편의성을 함께 고려해야 합니다. 동일한 추상화 수준에서 코드를 모듈화하는 것이 이상적입니다.
모든 상황에 DRY 원칙을 강요하지 않기
모든 상황에 DRY 원칙을 엄격히 적용하는 것은 오히려 코드를 복잡하게 만들 수 있습니다. 예를 들어, 단순한 코드에서 중복이 생기더라도, 그 코드가 충분히 명확하고 가독성을 해치지 않는다면 그대로 두는 것이 더 나을 수 있습니다. 가독성이 DRY 원칙보다 우선시되어야 할 경우가 있으며, 이를 무시하고 무조건 DRY를 적용하면 코드를 읽기 어렵게 만들 수 있습니다.
적절한 DRY 적용 시점
DRY 원칙을 초기 설계 단계에서 과도하게 적용하는 것은 피해야 합니다. 프로젝트의 요구 사항이 아직 명확하지 않거나 변경될 가능성이 큰 초기 단계에서는 불필요한 복잡성을 가져올 수 있습니다. 대신, 리팩토링 단계에서 DRY를 적용하는 것이 더 효과적일 수 있습니다. 코드가 어느 정도 작성된 후, 유사한 패턴이 반복적으로 나타날 때 DRY 원칙을 적용하여 중복을 제거하는 것이 더 자연스럽고, 오버엔지니어링을 방지할 수 있습니다.
코드의 유지 보수성 우선 고려
DRY 원칙을 적용하는 궁극적인 이유는 코드의 유지 보수성을 높이기 위함입니다. 따라서 코드를 분리하거나 중복을 제거할 때, 이후의 변경 가능성을 고려하는 것이 중요합니다. 중복된 부분을 잘못 분리하면, 한 부분을 수정할 때 다른 부분에 의도하지 않은 영향을 줄 수 있습니다. DRY를 적용하면서도, 변경 시의 영향 범위를 최소화할 수 있도록 구조를 잡아야 합니다.
DRY 원칙 적용 범위
DRY 원칙은 코드뿐 아니라 설계, 데이터베이스 구조, 문서화 등 소프트웨어 개발의 전 과정에 적용될 수 있습니다.
맺음말
DRY 원칙은 중복을 피하고 코드를 일관성 있게 유지함으로써 소프트웨어의 유지보수성과 효율성을 크게 높일 수 있는 중요한 원칙입니다. 그러나 성능과의 균형, 가독성 등을 고려해 유연하게 적용하는 것이 필요합니다
심화 학습
DRY 원칙을 적용할 때 성능, SRP, 로컬/글로벌 DRY 등의 요소들이 서로 영향을 주고받기 때문에, 각 상황에 맞는 적절한 조정이 필요합니다.
DRY 원칙과 성능의 균형
DRY 원칙을 적용할 때 성능을 함께 고려해야 할 때가 있습니다. 중복된 코드를 제거하여 공통 모듈을 만들면 성능 저하가 발생할 수 있기 때문입니다. 특히 실시간 성능이 요구되는 시스템에서는 DRY 원칙보다는 성능 최적화가 더 중요한 경우가 있습니다. 예를 들어, 성능이 중요한 그래픽 처리 코드에서는 함수 호출 자체가 성능에 부정적인 영향을 줄 수 있으므로, 같은 로직이 반복되더라도 인라인으로 처리하는 것이 더 효율적일 수 있습니다. 즉, DRY 원칙과 성능 간의 균형을 잡는 것이 중요하며, 상황에 맞는 유연한 적용이 필요합니다.
// 성능에 민감한 코드에서 DRY를 적용했을 때
public void ProcessData()
{
// 반복되는 작은 작업을 함수로 추출
for (int i = 0; i < 1000000; i++)
{
PerformCalculation(i);
}
}
private void PerformCalculation(int value)
{
// 계산 작업
}
위 코드에서 PerformCalculation
함수 호출이 빈번하게 발생하면서 함수 호출 오버헤드가 성능 저하를 일으킬 수 있습니다. 이런 경우에는 인라인으로 처리하거나 성능 최적화가 필요한 부분에서는 DRY를 적용하지 않는 것이 더 나을 수 있습니다.
로컬 DRY와 글로벌 DRY
DRY 원칙은 적용 범위에 따라 로컬 DRY와 글로벌 DRY로 나눌 수 있습니다. 로컬 DRY는 특정 클래스나 모듈 내에서 중복된 코드를 제거하는 것으로, 주로 코드의 간결성을 높이고 유지보수를 쉽게 하기 위해 사용됩니다. 예를 들어, 한 클래스 내에서 동일한 로직이 여러 메서드에서 반복되는 경우 이를 하나의 메서드로 추출하여 재사용하는 것이 로컬 DRY의 적용입니다. 반면, 글로벌 DRY는 프로젝트 전반에 걸친 중복을 제거하는 것입니다. 예를 들어, 여러 모듈에서 공통적으로 사용하는 로직이나 데이터는 전역적으로 관리되는 모듈로 이동시켜 여러 곳에서 재사용할 수 있도록 만듭니다. 작은 프로젝트에서는 로컬 DRY가 충분할 수 있지만, 프로젝트가 커질수록 글로벌 DRY를 적용하여 코드 전반의 일관성을 유지하는 것이 중요합니다. 하지만 글로벌 DRY는 과도하게 적용되면 프로젝트 전체가 특정 공통 모듈에 의존하게 되어, 해당 모듈에 문제가 생기거나 변경이 필요할 때 전체 프로젝트에 영향을 미칠 수 있습니다. 따라서 글로벌 DRY는 신중하게 적용해야 하며, 프로젝트 구조를 잘 분석한 후 적절한 범위 내에서 사용해야 합니다.
DRY와 단일 책임 원칙의 균형
DRY 원칙과 단일 책임 원칙SRP은 자주 혼동되거나 서로 충돌할 수 있는 원칙입니다. DRY는 중복을 제거하는 데 초점을 맞추고 있지만, SRP는 모듈이나 클래스가 하나의 책임만 가지도록 설계하는 것을 강조합니다. 이 둘을 함께 고려할 때, 중복을 제거하는 과정에서 하나의 모듈에 너무 많은 책임이 부여되지 않도록 주의해야 합니다. DRY를 과도하게 적용하면 SRP를 위반하게 되는 경우도 발생할 수 있으므로, 코드의 역할 분배에 세심한 고려가 필요합니다.
DRY와 WET
DRY 원칙이 항상 즉각적으로 적용될 필요는 없습니다. WETWrite Everything Twice 원칙은 처음에는 코드를 중복해 작성하고, 코드가 안정되면 나중에 중복된 부분을 제거하는 방식입니다. 이는 처음부터 모든 중복을 없애기 위한 DRY를 적용하려다가 지나치게 복잡한 구조를 만들지 않도록 방지하는 역할을 합니다. 특히, 변화가 자주 일어나거나 아직 최종 형태가 결정되지 않은 코드에서는 WET 원칙을 따르는 것이 더 적합할 수 있습니다. 초기 개발 단계에서는 중복된 코드를 허용하고, 프로젝트가 발전하면서 중복이 많아질 때 리팩토링을 통해 DRY 원칙을 적용하는 것이 자연스럽습니다. 작은 프로젝트나 짧은 생명 주기를 가진 프로젝트에서는 중복이 큰 문제가 되지 않지만, 프로젝트가 확장됨에 따라 중복을 제거하고 유지보수성을 높이기 위해 DRY 원칙을 적용하는 것이 효과적입니다.
문서화와 DRY의 상호작용
코드만이 아니라 문서화에도 DRY 원칙을 적용할 수 있습니다. 많은 개발자들이 코드를 문서화할 때 중복된 설명을 반복해서 작성하는 경우가 많습니다. 하지만 문서화 역시 코드처럼 중복을 피하고 중앙화된 설명을 유지하는 것이 바람직할 수 있습니다. API 문서, 클래스 설명, 함수 사용법 등을 한 곳에 명시하고, 코드 변경 시 문서화도 함께 관리하는 것이 DRY 원칙의 일환으로 고려될 수 있습니다.
DRY와 TDD의 관계
DRY 원칙은 코드뿐만 아니라 테스트 코드에서도 중요한 역할을 합니다. TDDTest-Driven Development를 통해 테스트 코드를 작성할 때, 중복된 테스트 로직을 최소화하는 것은 유지보수성과 재사용성을 높이는 데 필수적입니다. 특히 여러 테스트 케이스에서 동일한 초기화나 설정 코드가 반복되지 않도록 하여 테스트 코드의 DRY 원칙을 준수하는 것이 중요합니다. 이로 인해 테스트 코드의 효율성도 극대화될 수 있습니다.
// 중복된 테스트 코드
[Test]
public void TestSpeedForCar1() {
var car = new Car("Red", 200);
Assert.AreEqual(200, car.GetSpeed());
}
[Test]
public void TestSpeedForCar2() {
var car = new Car("Blue", 150);
Assert.AreEqual(150, car.GetSpeed());
}
// DRY 적용 후
[TestCase("Red", 200)]
[TestCase("Blue", 150)]
public void TestCarSpeed(string color, int expectedSpeed) {
var car = new Car(color, expectedSpeed);
Assert.AreEqual(expectedSpeed, car.GetSpeed());
}