【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”となる。
上記のクラスには問題がある。
それは、FirstName
とLastName
を設定して、FullName
の設定が漏れていた場合に、FullName
が出力されないという問題だ。
var haruki = new Person();
haruki.FirstName = "Haruki";
haruki.LastName = "Yachizaki";
// haruki.FullName の設定を忘れている
Console.Write(haruki.FullName);
// 結果 -> ""(空白)
// PersonクラスのFullNameのデフォルト値が表示される
設定されるべき値を設定していなかったのだから当然の挙動だが、そもそも一般的にFullName
はFirstName
とLastName
さえ分れば求められるにも関わらず、個別に設定しなければならない方がおかしいと言えるだろう。FullName
の設定を繰り返しているとも言える。
そこで、FullName
を修正し、以下のようにしてみる。
public class Person
{
public string FirstName { set; get; } = "";
public string LastName { set; get; } = "";
public string FullName => $"{LastName} {FirstName}";
}
FullName
は個別に設定するのではなく、FirstName
とLastName
を組み合わせて返すように変更する。
すると、
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はあくまでソースコード上において、同じような内容はまとめようという考え方だと理解している。