DRY(Don't Repeat Yourself)は、Andy HuntとDave Thomasが著書“The Pragmatic Programmer”で提唱した基本的な開発原則です。この原則は、システム内の知識の各部分は単一で明確かつ信頼できる表現を持つべきだと述べています。The Pragmatic Programmer, 20th Anniversary Editionによると、DRYに違反すると、1つの要素を変更するために数十か所の編集が必要になり、見逃された断片がすべてバグの原因になります。
重要なポイント
DRY(Don't Repeat Yourself)は、プロジェクト内の各知識要素を正確に1回だけ保存することを要求する開発原則です。つまり、ロジック、設定、メタデータはすべて1か所にのみ存在する必要があります。
この用語は1999年にAndy HuntとDave Thomasによって著書“The Pragmatic Programmer”で導入されました。著者はDRYを“知識の各部分はシステム内で単一で明確かつ信頼できる表現を持たなければならない”と定義しました。DRYの反対はWET(Write Everything Twice)アプローチで、重複が正常と見なされます。
University of California, Davis(2019)の研究によると、コード重複の多いプロジェクトはバグ修正に42%多くの時間を費やしています。その理由は、開発者が同じ断片のすべてのコピーを見つけて変更する必要があり、手動検索では必然的に見落としが発生するためです。
DRYをコード品質の基準として適用してください。同じパターンがプロジェクト内で3回出現することに気付いたら、4回目の繰り返しを待たずに抽象化に抽出してください。
SOLIDの単一責任原則(SRP)は、クラスには変更理由が1つだけあるべきだと述べています。DRYはより広範囲です。クラスだけでなく、データ、設定、ドキュメント、さらにはビジネスルールも対象とします。SRPは責任の境界に関するものであり、DRYはコピー防止に関するものです。
モバイル開発では、この違いは特に顕著です。同じビジネスルール(税金計算、日付フォーマット)がプロジェクトのAndroid部分とiOS部分の両方で繰り返される場合、各プラットフォーム内でSRPが形式的に遵守されていても、DRY違反となります。解決策は、共通ロジックを共有モジュール(KMM、C++)に抽出することです。
Google Android Architecture Guidelines(2023)レポートによると、ビジネスロジックに共有モジュールを使用するチームは、要件変更時のバグ数を、プラットフォーム間でロジックを重複するプロジェクトと比較して37%削減します。
重複はモバイルプロジェクトにおける技術的負債の主な原因です。コードのコピーごとに隠れた依存関係が生まれます。動作を変更するには、すべてのコピーを見つけて更新する必要があります。1つでも見逃すとバグになります。
古典的なシナリオを考えてみましょう。Androidアプリで、日付フォーマットが3つの異なるActivityで行われています。新しい形式(ISO 8601など)に切り替える際、開発者は2つのファイルを修正し、3つ目を忘れます。するとユーザーは古い形式で日付を表示されます。アプリの評価が下がり、バグを見つけるのに2倍の時間がかかります。
Google Research(2020)の調査では、モバイルアプリケーションの重大なバグの68%が重複コードの非同期変更に関連していることが示されました。さらに、本番環境でのそのようなバグの修正コストは、コードが最初から統一されていた場合の4.5倍です。
静的アナライザー(Detekt、SwiftLint)をcopy-pasteを検出するルールと共に使用してください。N行を超える重複を含むプルリクエストが正当な理由なしにレビューを通過しないようにCIを設定してください。
典型的なアンチパターンは、RecyclerViewアダプターをわずかな変更でコピーすることです。1つの汎用アダプターの代わりに、開発者は画面ごとに個別のクラスを作成します。共通の基底クラスを抽出するリファクタリングにより、コードが30–50%削減されます。
// 重複:2つの別々のアダプター
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>() { /* ... */ }
最初の例では、各アダプターがbindメカニズムをゼロから再実装しています。新しいロジック(分析、ロギング)を追加する場合、すべてのファイルを変更する必要があります。基底クラスはこの重複を排除します。共通ロジックは1か所に、特定のロジックはサブクラスに配置されます。
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に抽出することで、すべてのサービスが同じヘッダーとタイムアウトを使用することが保証されます。1か所の変更が自動的にすべてのリクエストに適用され、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()拡張は1回宣言され、プロジェクト全体で利用可能です。フォーマットを“dd.MM.yyyy”から“yyyy-MM-dd”に変更する必要がある場合、修正は1ファイルのみで、フォーマットが行われるすべてのActivityやFragmentを修正する必要はありません。これがDRYの本質です。
マルチモジュールAndroidプロジェクトでは、各build.gradleで依存関係のバージョンが重複されることがよくあります。解決策は、すべてのバージョンを1ファイルに集中管理するバージョンカタログ(libs.versions.toml)です。
Androidデベロッパードキュメント(2024)によると、バージョンカタログへの移行により依存関係の競合が52%削減され、単一の編集ポイントによってビルドが高速化されます。
プロジェクト開始時または最初のモジュール再編成時にバージョンカタログを導入してください。プロジェクトに既に重複がある場合は、移行に1日を割り当ててください。次のライブラリ更新で効果が現れます。
時期尚早な抽象化は初心者に最もよくある間違いです。開発者は2行の類似コードを見て、すぐに共通関数に抽出します。1か月後、要件が変わり、共通関数はパラメーターとフラグで溢れかえり、元の重複よりも複雑になります。Rule of Threeはまさにこれから保護します。1回か2回しか出現していないものを抽象化しないでください。
Martin Fowlerは著書Refactoring(2019)で次のように推奨しています。“コードの重複は常に悪いわけではない。知識の重複が悪いのだ。” 2行が偶然一致しても異なる概念を表現している場合、それは重複ではなく偶然です。Rule of Threeは、偶発的な偶然と体系的な重複を区別するのに役立ちます。
抽象化する前に、意味論を評価してください。同じ意味でコピーされたコードはDRY違反です。異なる意味だが構文が類似したコードは、抽象化を必要としない偶然です。
過剰なパラメータ化は、1つの関数がフラグとブールパラメータを通じてすべての可能なシナリオをカバーしようとするときに発生します。そのようなコードはSRPに違反し、読みにくくなります。症状:関数に3つ以上のブールパラメータがある場合、それは過剰な抽象化のコードスメルです。
useCache: Booleanフラグを持つ1つの関数の代わりに、fetchFromNetwork()とfetchFromCache()という明確な名前の2つの別々の関数を作成することをお勧めします。明確さは乾いた抽象化よりも重要です。これはKISS原則と一致します。
関数が3つ以上のブールパラメータに達したら、過剰なパラメータ化をリファクタリングしてください。明確な名前の個別の関数に分割することで、各呼び出しが自己文書化されます。
よくある質問
DRY(Don't Repeat Yourself)は、各論理ユニットを1か所に保存することを要求する原則です。同じコードがプロジェクトの複数の部分に現れる場合、それはDRY違反です。修正方法:繰り返しロジックを個別の関数、クラス、またはモジュールに抽出します。
WET(Write Everything Twice)はDRYの対極で、重複が許容されると見なされます。WETプロジェクトでは、同じコード断片が5つのコピーで存在する可能性があり、要件が変更されると開発者は各コピーを個別に修正します。WETはバグのリスクを高め、開発を遅くします。
DRYは時期尚早な抽象化につながる場合に有害です。2つの類似しているが意味論的に異なるコードセクションが強制的に1つの関数に統合されると、複雑でパラメータが過負荷なコードが生成されます。Rule of Threeはこの間違いを避けるのに役立ちます。3回目の繰り返し後にのみ抽象化してください。
Androidでは、DRYはバージョンカタログ(libs.versions.toml)、アダプターの共通基底クラス、ViewModelファクトリ、ユーティリティKotlin拡張を通じて適用されます。ビジネスロジックを共有モジュール(KMM)に抽出し、View Bindingを使用してfindViewByIdの重複を排除することをお勧めします。
iOSでは、DRYはデフォルト実装を持つプロトコル、共有ネットワーク設定(NetworkConfig)、UICollectionViewセルファクトリ、共通ビジネスロジックを持つSPMパッケージを通じて達成されます。標準型(Date、String、URL)の拡張は、フォーマットとバリデーションの重複を削減します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。