DRY(Don't Repeat Yourself)는 Andy Hunt와 Dave Thomas가 저서 “The Pragmatic Programmer”에서 공식화한 기본적인 개발 원칙입니다. 이 원칙은 시스템의 지식 각 부분이 단일하고 명확하며 권위 있는 표현을 가져야 한다고 말합니다. The Pragmatic Programmer, 20th Anniversary Edition에 따르면, DRY를 위반하면 한 요소를 변경하기 위해 수십 군데를 수정해야 하며, 놓친 모든 조각이 버그의 원인이 됩니다.
핵심 사항
DRY(Don't Repeat Yourself)는 프로젝트에서 각 지식 요소를 정확히 한 번만 저장하도록 요구하는 개발 원칙입니다. 즉, 모든 로직, 구성 또는 메타데이터는 정확히 한 곳에만 존재해야 합니다.
이 용어는 1999년 Andy Hunt와 Dave Thomas가 저서 “The Pragmatic Programmer”에서 도입했습니다. 저자는 DRY를 “지식의 각 부분은 시스템 내에서 단일하고 명확하며 권위 있는 표현을 가져야 한다”고 정의했습니다. DRY의 반대는 중복이 정상으로 간주되는 WET(Write Everything Twice) 접근 방식입니다.
University of California, Davis(2019)의 연구에 따르면, 코드 중복이 높은 프로젝트는 버그 수정에 42% 더 많은 시간을 소비합니다. 그 이유는 개발자가 동일한 조각의 모든 복사본을 찾아 변경해야 하며, 수동 검색에서는 필연적으로 누락이 발생하기 때문입니다.
DRY를 코드 품질 기준으로 적용하세요. 동일한 패턴이 프로젝트에서 세 번 나타나는 것을 발견하면 네 번째 반복을 기다리지 않고 추상화로 추출하세요.
SOLID의 단일 책임 원칙(SRP)은 클래스에 변경 이유가 하나만 있어야 한다고 말합니다. DRY는 더 광범위합니다. 클래스뿐만 아니라 데이터, 구성, 문서 및 비즈니스 규칙까지 포함합니다. SRP는 책임 경계에 관한 것이고, DRY는 복사 방지에 관한 것입니다.
모바일 개발에서 이 차이는 특히 두드러집니다. 동일한 비즈니스 규칙(세금 계산, 날짜 형식)이 프로젝트의 Android와 iOS 부분 모두에서 반복된다면, 각 플랫폼 내에서 SRP가 공식적으로 준수되더라도 DRY 위반입니다. 해결책은 공통 로직을 공유 모듈(KMM, C++)로 추출하는 것입니다.
Google Android Architecture Guidelines(2023) 보고서에 따르면, 비즈니스 로직에 공유 모듈을 사용하는 팀은 플랫폼 간에 로직을 중복하는 프로젝트에 비해 요구사항 변경 시 버그 수를 37% 줄입니다.
중복은 모바일 프로젝트에서 기술 부채의 주요 원천입니다. 각 코드 복사본은 숨겨진 종속성을 만듭니다. 동작을 변경하려면 모든 복사본을 찾아 업데이트해야 합니다. 하나라도 놓치면 버그입니다.
고전적인 시나리오를 고려해보세요. Android 앱에서 세 개의 서로 다른 Activity에서 날짜 형식이 지정됩니다. 새 형식(예: ISO 8601)으로 전환할 때 개발자는 두 파일을 수정하고 세 번째를 잊어버립니다. 그러면 사용자는 이전 형식으로 날짜를 보게 됩니다. 앱 평점이 떨어지고 버그를 찾는 데 두 배의 시간이 걸립니다.
Google Research(2020)의 연구에 따르면 모바일 애플리케이션의 중요한 버그 중 68%가 중복 코드의 비동기화된 변경과 관련되어 있습니다. 게다가 프로덕션에서 이러한 버그를 수정하는 비용은 코드가 처음부터 통합되어 있었을 때보다 4.5배 더 높습니다.
정적 분석기(Detekt, SwiftLint)를 copy-paste를 감지하는 규칙과 함께 사용하세요. N줄 이상의 중복이 있는 풀 리퀘스트가 정당한 이유 없이 리뷰를 통과하지 못하도록 CI를 구성하세요.
일반적인 안티패턴은 약간의 수정과 함께 RecyclerView 어댑터를 복사하는 것입니다. 하나의 범용 어댑터 대신 개발자는 각 화면에 대해 별도의 클래스를 만듭니다. 공통 기본 클래스를 추출하는 리팩토링은 코드를 30–50% 줄입니다.
// 중복: 두 개의 별도 어댑터
class UserAdapter {
fun bind(item: User) { /* ... */ }
}
class ProductAdapter {
fun bind(item: Product) { /* ... */ }
}
// DRY 리팩토링: 공통 기본 클래스
abstract class BaseAdapter<T> {
abstract fun bind(item: T)
}
class UserAdapter : BaseAdapter<User>() { /* ... */ }
class ProductAdapter : BaseAdapter<Product>() { /* ... */ }
첫 번째 예제에서 각 어댑터는 바인드 메커니즘을 처음부터 다시 구현합니다. 새 로직(분석, 로깅)을 추가할 때 모든 파일을 변경해야 합니다. 기본 클래스는 이 중복을 제거합니다. 공통 로직은 한 곳에 있고 특정 로직은 하위 클래스에 있습니다.
iOS 프로젝트에서 URLSession 구성(헤더, 타임아웃, 오류 처리)이 자주 중복됩니다. 각 서비스가 반복된 설정으로 자체 세션을 만듭니다.
// 중복: 각 서비스가 세션을 새로 구성
class UserService {
let session = URLSession(configuration: {
let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 30
cfg.httpAdditionalHeaders = ["Authorization": "Bearer ..."]
return cfg
}())
}
// DRY: 통합 세션 팩토리
struct NetworkConfig {
static var session: URLSession {
let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 30
cfg.httpAdditionalHeaders = ["Authorization": "Bearer ..."]
return URLSession(configuration: cfg)
}
}
구성을 통합된 NetworkConfig로 추출하면 모든 서비스가 동일한 헤더와 타임아웃을 사용하도록 보장됩니다. 한 곳의 변경이 모든 요청에 자동으로 적용되어 API 키나 프로토콜 버전 변경 시 오류 위험이 줄어듭니다.
상속은 중복을 제거하는 자연스러운 방법입니다. 공통 로직은 기본 클래스로 이동되고 특정 로직은 하위 클래스로 이동됩니다. 그러나 모바일 개발에서 상속을 과도하게 사용하면 유지 관리가 어려운 경직된 계층 구조가 생성됩니다. 구성(의존성 주입)은 더 유연한 대안입니다.
Google I/O 2023: Modern Android Architecture의 분석에 따르면 Google 팀의 76%가 중복 제거를 위해 상속보다 구성을 선호합니다. 수십 개의 메서드가 있는 BaseViewModel 대신 각 비즈니스 작업에 대해 별도의 UseCase 클래스를 추출하고 필요한 곳에 주입하는 것이 좋습니다.
“is-a” 관계를 제외한 모든 경우에 구성을 선택하세요. 클래스 A가 클래스 B의 특수화인 경우 상속이 적절합니다. A가 단순히 B의 기능을 사용하는 경우 구성을 사용하세요.
유틸리티 클래스(Extensions, Helpers)는 중복을 피하는 가장 간단한 방법입니다. 일반적인 대상: 날짜 형식, 이메일 검증, 단위 변환, SharedPreferences/UserDefaults 작업.
// DRY: 통합 날짜 형식 함수
fun Date.toDisplayFormat(): String {
val sdf = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
return sdf.format(this)
}
// 애플리케이션 어디서나 사용
textView.text = Date().toDisplayFormat()
Date.toDisplayFormat() 확장은 한 번 선언되고 프로젝트 전체에서 사용 가능합니다. 형식을 “dd.MM.yyyy”에서 “yyyy-MM-dd”로 변경해야 하는 경우 수정은 하나의 파일에서만 이루어지며, 형식이 발생하는 모든 Activity나 Fragment에서 이루어지지 않습니다. 이것이 DRY의 핵심입니다.
멀티모듈 Android 프로젝트는 각 build.gradle에서 종속성 버전을 자주 중복합니다. 해결책은 모든 버전을 단일 파일에 중앙 집중화하는 버전 카탈로그(libs.versions.toml)입니다.
Android 개발자 문서(2024)에 따르면, 버전 카탈로그로 마이그레이션하면 종속성 충돌이 52% 감소하고 단일 편집 지점을 통해 빌드 속도가 빨라집니다.
프로젝트 시작 시 또는 첫 번째 모듈 재구성 중에 버전 카탈로그를 구현하세요. 프로젝트에 이미 중복이 있는 경우 마이그레이션에 하루를 할당하세요. 다음 라이브러리 업데이트에서 효과를 볼 수 있습니다.
성급한 추상화는 초보자의 가장 흔한 실수입니다. 개발자가 두 줄의 유사한 코드를 보고 즉시 공통 함수로 추출합니다. 한 달 후 요구사항이 변경되고 공통 함수는 매개변수와 플래그로 가득 차 원래 중복보다 더 복잡해집니다. Rule of Three는 정확히 이것으로부터 보호합니다. 한두 번만 나타난 것을 추상화하지 마세요.
Martin Fowler는 저서 Refactoring(2019)에서 다음과 같이 권장합니다. “코드 중복이 항상 나쁜 것은 아닙니다. 지식의 중복이 나쁩니다.” 두 줄이 우연히 일치하지만 다른 개념을 표현한다면 그것은 중복이 아니라 우연입니다. Rule of Three는 우연한 일치와 체계적인 중복을 구분하는 데 도움이 됩니다.
추상화하기 전에 의미를 평가하세요. 동일한 의미로 복사된 코드는 DRY 위반입니다. 다른 의미지만 유사한 구문의 코드는 추상화가 필요하지 않은 우연입니다.
과도한 매개변수화는 하나의 함수가 플래그와 부울 매개변수를 통해 가능한 모든 시나리오를 커버하려고 할 때 발생합니다. 이러한 코드는 SRP를 위반하고 읽기 어려워집니다. 증상: 함수에 두 개 이상의 부울 매개변수가 있으면 과도한 추상화의 코드 스멜입니다.
useCache: Boolean 플래그가 있는 하나의 함수 대신 fetchFromNetwork()와 fetchFromCache()라는 명확한 이름의 두 개의 별도 함수를 만드는 것이 좋습니다. 명확성은 건조한 추상화보다 중요합니다. 이는 KISS 원칙과 일치합니다.
함수가 3개 이상의 부울 매개변수에 도달하면 과도한 매개변수화를 리팩토링하세요. 명확한 이름의 개별 함수로 분할하면 각 호출이 자체 문서화됩니다.
자주 묻는 질문
DRY(Don't Repeat Yourself)는 각 논리 단위를 단일 위치에 저장하도록 요구하는 원칙입니다. 동일한 코드가 프로젝트의 여러 부분에 나타나면 DRY 위반입니다. 해결 방법: 반복 로직을 별도의 함수, 클래스 또는 모듈로 추출하세요.
WET(Write Everything Twice)는 DRY의 반대이며, 중복이 허용 가능한 것으로 간주됩니다. WET 프로젝트에서는 동일한 코드 조각이 다섯 개의 복사본으로 존재할 수 있으며, 요구사항이 변경되면 개발자가 각 복사본을 개별적으로 수정합니다. WET은 버그 위험을 증가시키고 개발을 지연시킵니다.
DRY는 성급한 추상화로 이어질 때 해롭습니다. 두 개의 유사하지만 의미적으로 다른 코드 섹션이 강제로 하나의 함수로 병합될 때 발생합니다. 이는 복잡하고 매개변수가 과부하된 코드를 생성합니다. Rule of Three는 이 실수를 피하는 데 도움이 됩니다. 세 번째 반복 후에만 추상화하세요.
Android에서 DRY는 버전 카탈로그(libs.versions.toml), 어댑터의 공통 기본 클래스, ViewModel 팩토리 및 유틸리티 Kotlin 확장을 통해 적용됩니다. 비즈니스 로직을 공유 모듈(KMM)로 추출하고 View Binding을 사용하여 findViewById 중복을 제거하는 것이 좋습니다.
iOS에서 DRY는 기본 구현이 있는 프로토콜, 공유 네트워크 구성(NetworkConfig), UICollectionView 셀 팩토리 및 공통 비즈니스 로직이 있는 SPM 패키지를 통해 달성됩니다. 표준 유형(Date, String, URL)의 확장은 형식 지정 및 유효성 검사에서 중복을 줄입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.