# プログラミングにおけるDRY原則とは？

> エンジニア初心者向けにDRY原則の理解とその適用について説明して、具体例を通じてDRYでないコードの改善方法や開発初期におけるDRY原則の厳格な適用による問題点を解説しています。

- 公開日: 2024-12-02
- 更新日: 2024-12-05
- 著者: Matsu
- タグ: エンジニア概念マップ
- URL: https://tech.anycloud.co.jp/articles/dry

---

## DRY原則とは？

プログラミングにおける「DRY原則」とは、「Don’t Repeat Yourself（繰り返すな）」の略で、同じコードやロジックを複数箇所に書かずに、一箇所にまとめて再利用する考え方です。

この原則を守ることで、コードの保守性や可読性が向上します。

当たり前のことですが、同じロジックを2箇所以上にコピーして使っていると、片方だけを変更した際に、もう片方の変更を忘れると、バグの原因になります。

DRY原則の目的として、重複を排除して一貫性を保つことであり、一箇所で修正を加えるだけで、他の部分にもその変更が反映される設計を目指します。

例えば以下のようなケースが考えられます。

-   コードの重複を避ける
    -   同じ処理を複数箇所に書かない
-   ロジックの重複を避ける
    -   複雑な計算や条件分岐を一箇所に集約
-   データの重複を避ける
    -   同じ情報を複数の場所に保存しない

## DRYではないコードの具体例・改善例

今回はFlutterを用いて、DRYではないコードの具体例・改善例を紹介します。

### DRYではないコードの具体例

以下のコードでは、ボタンのスタイルが繰り返し書かれており、修正が必要な場合に複数箇所を変更する必要があります。

```dart
import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('DRYの例')),
        body: Column(
          children: [
            ElevatedButton(
              onPressed: () => print('ボタン1が押されました'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.blue,
                padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
              ),
              child: Text('ボタン1'),
            ),
            ElevatedButton(
              onPressed: () => print('ボタン2が押されました'),
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.blue,
                padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
              ),
              child: Text('ボタン2'),
            ),
          ],
        ),
      ),
    );
  }
}
```

### DRYではないコードの改善例

以下のように改善することで、DRYなコードになりました。

-   ボタンのスタイルとロジックを`CustomButton`コンポーネントにまとめることで、再利用可能にする
-   ボタンのスタイルを変更する際に、`CustomButton`クラスの修正だけになる

```dart
import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('DRYの改善例')),
        body: Column(
          children: [
            CustomButton(
              text: 'ボタン1',
              onPressed: () => print('ボタン1が押されました'),
            ),
            CustomButton(
              text: 'ボタン2',
              onPressed: () => print('ボタン2が押されました'),
            ),
          ],
        ),
      ),
    );
  }
}

class CustomButton extends StatelessWidget {
  final String text;
  final VoidCallback onPressed;

  CustomButton({required this.text, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.blue,
        padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
      ),
      child: Text(text),
    );
  }
}
```

## 開発初期からDRY原則を厳格に適用しすぎた際の問題点3つ

開発初期からDRY原則を厳格に適用しすぎることは以下の問題を引き起こす可能性があります。

### **1.過剰な抽象化**

-   初期段階でDRYを意識しすぎると、仕様が固まる前に過剰な抽象化が生まれてしまい、将来の変更が必要以上に複雑になり、かえって保守が難しくなる

### **2.柔軟性の欠如**

-   DRYに従いすぎると、各コンポーネントが密接に依存するため、仕様変更時に影響範囲が広がるリスクがある
    -   特定の画面だけで変更したい場合にも影響を最小化できない
    -   新たに例外ケースを追加するたびにコードが複雑化する

### **3.初期開発のコスト増**

-   開発初期からすべてのコードを共通化しようとすると、シンプルに動作するコードを書くよりも時間と労力がかかってしまう

## まとめ

DRY原則の適切なバランスを取るために、以下のことを意識して、状況に応じた適用することが重要ですね。

1.  **開発初期はKISS（Keep It Simple, Stupid「複雑にするな、バカ」）を優先**
    -   DRY原則は後から適用しても間に合う場合が多いので、まずはシンプルに実装し、動くプロトタイプを作ることを優先する
2.  **リファクタリングのタイミングを見極める**
    -   重複したコードが複数箇所に現れた時点で、共通化を検討する
3.  **仕様が固まるまで過剰な抽象化は避ける**
    -   アプリの仕様がまだ流動的な段階では、具体的な実装を優先し、必要に応じて共通化を進める

特に初期開発では、動作するコードを優先し、リファクタリングで徐々に適用することを意識しましょう。
