tag:crieit.net,2005:https://crieit.net/tags/OAOO/feed
「OAOO」の記事 - Crieit
Crieitでタグ「OAOO」に投稿された最近の記事
2021-01-11T21:48:56+09:00
https://crieit.net/tags/OAOO/feed
tag:crieit.net,2005:PublicArticle/16578
2021-01-11T21:48:56+09:00
2021-01-11T21:48:56+09:00
https://crieit.net/posts/csharp-dry-oaoo
【C#】DRYとOAOO
<h1 id="DRY原則とは"><a href="#DRY%E5%8E%9F%E5%89%87%E3%81%A8%E3%81%AF">DRY原則とは</a></h1>
<p>知っている人も多いと思うが、<a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY原則</a>というものがある。</p>
<p><strong>D</strong>on't <strong>R</strong>epeat <strong>Y</strong>ourselfの頭文字をとった言葉で、簡単に言うと「同じことを繰り返すな」という意味。</p>
<h1 id="サンプルコード"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89">サンプルコード</a></h1>
<p>例を出してみる。</p>
<pre><code class="csharp">public class Person
{
public string FirstName { set; get; } = "";
public string LastName { set; get; } = "";
public string FullName { set; get; } = "";
}
</code></pre>
<p>上記のような<code>Person</code>クラスのインスタンスを作成するとしよう。</p>
<pre><code class="csharp">var haruki = new Person();
haruki.FirlstName = "Haruki";
haruki.LastName = "Yachizaki";
haruki.FullName = "Yachizaki Haruki";
Console.Write(haruki.FullName);
// 結果 -> Yachizaki Haruki
</code></pre>
<p>FullNameを出力すると、"Yachizaki Haruki"となる。</p>
<p>上記のクラスには問題がある。</p>
<p>それは、<code>FirstName</code>と<code>LastName</code>を設定して、<code>FullName</code>の設定が漏れていた場合に、<code>FullName</code>が出力されないという問題だ。</p>
<pre><code class="csharp">var haruki = new Person();
haruki.FirstName = "Haruki";
haruki.LastName = "Yachizaki";
// haruki.FullName の設定を忘れている
Console.Write(haruki.FullName);
// 結果 -> ""(空白)
// PersonクラスのFullNameのデフォルト値が表示される
</code></pre>
<p>設定されるべき値を設定していなかったのだから当然の挙動だが、そもそも一般的に<code>FullName</code>は<code>FirstName</code>と<code>LastName</code>さえ分れば求められるにも関わらず、個別に設定しなければならない方がおかしいと言えるだろう。<code>FullName</code>の設定を繰り返しているとも言える。</p>
<p>そこで、<code>FullName</code>を修正し、以下のようにしてみる。</p>
<pre><code class="csharp">public class Person
{
public string FirstName { set; get; } = "";
public string LastName { set; get; } = "";
public string FullName => $"{LastName} {FirstName}";
}
</code></pre>
<p><code>FullName</code>は個別に設定するのではなく、<code>FirstName</code>と<code>LastName</code>を組み合わせて返すように変更する。</p>
<p>すると、</p>
<pre><code class="csharp">var haruki = new Person();
haruki.FirstName = "Haruki";
haruki.LastName = "Yachizaki";
// haruki.FullName は設定しない(setterを持たないのでそもそもできない)
Console.Write(haruki.FullName);
// 結果 -> Yachizaki Haruki
</code></pre>
<p>となる。</p>
<p>他のプロパティから求められるプロパティは個別に用意しないという考え方では、DB設計などでも用いられる。DB設計においては、主にパフォーマンスの面から冗長なフィールドを用意することもあるが(<code>価格</code>と<code>税率</code>と<code>税込み価格</code>を持っておくなど)、プログラミングのソースコードにおいてはそこまで意識することは少ないと言えると思う(※ぱっと例が思いつかないが、冗長な実装をした方がいいケースもきっとあると思う)。</p>
<p>プロパティが増えれば増えるほど管理しなければいけない情報が増え、バグの元になる。</p>
<h1 id="メリット・デメリット"><a href="#%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88%E3%83%BB%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88">メリット・デメリット</a></h1>
<p>メリット</p>
<ul>
<li>テストの箇所を集約できる
<ul>
<li>複数個所に書いていればその分テストが必要だが、一か所であればそれだけでカバーできる</li>
</ul></li>
<li>ロジックの改善や仕様変更の際に修正の手間が激減する
<ul>
<li>トレースログを仕込む時などに便利</li>
</ul></li>
</ul>
<p>デメリット</p>
<ul>
<li>変更が呼び出し箇所全てに反映されるため、影響範囲を把握しておく必要がある</li>
</ul>
<h1 id="OAOO"><a href="#OAOO">OAOO</a></h1>
<p>DRY原則とよく似た考え方に、OAOO(<strong>O</strong>nce <strong>A</strong>nd <strong>O</strong>nly <strong>O</strong>nce:ただ一度だけ書く)がある。</p>
<p>例を示す。下記のようなコードがあるとする。</p>
<pre><code class="csharp">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);
// 結果 -> あけましておめでとうございます。今年もよろしくお願いします。
</code></pre>
<p>上記コードは2つのメッセージを連結して出力するが、各メッセージが読点(<code>。</code>)で終わっていなければ付け足した後に連結するということをしている。</p>
<p>この、「読点がなければ付け足す」という処理は<code>message1</code>に対して行うか<code>message2</code>に対して行うかだけの違いだが、2回に渡って書かれている。</p>
<p>これにOAOOを当てはめると、以下のようにできる。</p>
<pre><code class="csharp">// 以下のメソッドを用意
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);
// 結果 -> あけましておめでとうございます。今年もよろしくお願いします。
</code></pre>
<p>「読点がなければ付け足す」という処理をメソッドに抽出したことで流用できるようになった。</p>
<p>DRYはシステム全体での重複を(排除すべきところでは)排除しようという考え方、OAOOはあくまでソースコード上において、同じような内容はまとめようという考え方だと理解している。</p>
あぱしょに