【C#】DRYとOAOO

DRY原則とは

知っている人も多いと思うが、DRY原則というものがある。

Don’t Repeat Yourselfの頭文字をとった言葉で、簡単に言うと「同じことを繰り返すな」という意味。

サンプルコード

例を出してみる。

public class Person
{
    public string FirstName { set; get; } = "";
    public string LastName { set; get; } = "";
    public string FullName { set; get; } = "";
}

上記のようなPersonクラスのインスタンスを作成するとしよう。

var haruki = new Person();
haruki.FirlstName = "Haruki";
haruki.LastName = "Yachizaki";
haruki.FullName = "Yachizaki Haruki";

Console.Write(haruki.FullName);
// 結果 -> Yachizaki Haruki

FullNameを出力すると、”Yachizaki Haruki”となる。

上記のクラスには問題がある。

それは、FirstNameLastNameを設定して、FullNameの設定が漏れていた場合に、FullNameが出力されないという問題だ。

var haruki = new Person();
haruki.FirstName = "Haruki";
haruki.LastName = "Yachizaki";
// haruki.FullName の設定を忘れている

Console.Write(haruki.FullName);
// 結果 -> ""(空白)
// PersonクラスのFullNameのデフォルト値が表示される

設定されるべき値を設定していなかったのだから当然の挙動だが、そもそも一般的にFullNameFirstNameLastNameさえ分れば求められるにも関わらず、個別に設定しなければならない方がおかしいと言えるだろう。FullNameの設定を繰り返しているとも言える。

そこで、FullNameを修正し、以下のようにしてみる。

public class Person
{
    public string FirstName { set; get; } = "";
    public string LastName { set; get; } = "";
    public string FullName => $"{LastName} {FirstName}";
}

FullNameは個別に設定するのではなく、FirstNameLastNameを組み合わせて返すように変更する。

すると、

var haruki = new Person();
haruki.FirstName = "Haruki";
haruki.LastName = "Yachizaki";
// haruki.FullName は設定しない(setterを持たないのでそもそもできない)

Console.Write(haruki.FullName);
// 結果 -> Yachizaki Haruki

となる。

他のプロパティから求められるプロパティは個別に用意しないという考え方では、DB設計などでも用いられる。DB設計においては、主にパフォーマンスの面から冗長なフィールドを用意することもあるが(価格税率税込み価格を持っておくなど)、プログラミングのソースコードにおいてはそこまで意識することは少ないと言えると思う(※ぱっと例が思いつかないが、冗長な実装をした方がいいケースもきっとあると思う)。

プロパティが増えれば増えるほど管理しなければいけない情報が増え、バグの元になる。

メリット・デメリット

メリット

  • テストの箇所を集約できる
    • 複数個所に書いていればその分テストが必要だが、一か所であればそれだけでカバーできる
  • ロジックの改善や仕様変更の際に修正の手間が激減する
    • トレースログを仕込む時などに便利

デメリット

  • 変更が呼び出し箇所全てに反映されるため、影響範囲を把握しておく必要がある

OAOO

DRY原則とよく似た考え方に、OAOO(Once And Only Once:ただ一度だけ書く)がある。

例を示す。下記のようなコードがあるとする。

var message1 = "あけましておめでとうございます";
var message2 = "今年もよろしくお願いします。";

var newMessage1 = "";
var newMessage2 = ""+

const string Dot = "。";

if (message1.EndsWith(Dot))
    newMessage1 = message1;
else
    newMessage1 = message1 + Dot;

if (message2.EndsWith(Dot))
    newMessage2 = message2;
else
    newMessage2 = message2 + Dot;

Console.Write(newMessage1 + newMessage2);
// 結果 -> あけましておめでとうございます。今年もよろしくお願いします。

上記コードは2つのメッセージを連結して出力するが、各メッセージが読点()で終わっていなければ付け足した後に連結するということをしている。

この、「読点がなければ付け足す」という処理はmessage1に対して行うかmessage2に対して行うかだけの違いだが、2回に渡って書かれている。

これにOAOOを当てはめると、以下のようにできる。

// 以下のメソッドを用意
static string AppendDotIfNeeded(string message)
{
    const string Dot = "。";

    // nullチェックは省略
    if (message.EndsWith(Dot))
        return message;
    else
        return message + Dot;
}

// メソッドを用意すると以下のように書ける
var message1 = "あけましておめでとうございます";
var message2 = "今年もよろしくお願いします。";

var message1EndsWithDot = AppendDotIfNeeded(message1);
var message2EndsWithDot = AppendDotIfNeeded(message2);

Console.Write(newMessage1 + newMessage2);
// 結果 -> あけましておめでとうございます。今年もよろしくお願いします。

「読点がなければ付け足す」という処理をメソッドに抽出したことで流用できるようになった。

DRYはシステム全体での重複を(排除すべきところでは)排除しようという考え方、OAOOはあくまでソースコード上において、同じような内容はまとめようという考え方だと理解している。