【Unity C#】DRY原則を勉強する


前置き

書籍を読んだり、ググったりして、自分に分かりやすいようにまとめた記事です。
より詳しく知りたい方は、下記の参考文献を読んでみてください。

参考文献

達人プログラマー ―熟達に向けたあなたの旅― 第2版 | Amazon
あなたはDRY原則を誤認している? - Qiita
本当の問題にたどり着かない――思考の重複を起こさないための「DRY」原則:問題解決力を高めるコツはプログラミングの原則・思考にあり - @IT

DRY原則(Don't Repeat Your Self) とは

  • 一言でいうと知識を二箇所以上に重複させるな
  • 同じロジックが複数箇所で使われるなら、コピペは厳禁
    • 同じようなコードが複数あると、修正する際に修正漏れする危険性がある
    • 同じようなコードだと思って、修正不要な箇所に誤って手を加えることになる可能性もある

  • 誤解しがちなところは「コードを重複させるな」ではない
    • 「知識」や「意図」の二重化についての原則
    • 異なった場所に同じことを表現するという問題を避けるための
    • コードだけでなく、データベーススキーマ、テスト計画、ビルドシステムやドキュメントなども含まれる

コードの二重化

商品情報から消費税を計算したり、値引した価格を求めたりするコード例

Product.cs
public class Product
{
    public string Name { get; set; }
    public int Price { get; set; }
}
PriceCalculation.cs
public class PriceCalculation
{
    /// <summary>
    /// 商品の10%値引きした価格を求める
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public int GetTenPercentPrice(Product product)
    {
        return product.Price * 0.10;
    }

    /// <summary>
    /// 商品の消費税を求める
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public int GetConsumptionTax(Product product)
    {
        return product.Price * 0.10;
    }
}

改善方法

  • GetTenPercentPriceGetConsumptionTax のどちらも計算内容は同じ = 共通化できる
    • Product.cs の内容は同じなので割愛
PriceCalculation.cs
public class PriceCalculation
{
    /// <summary>
    /// 商品の10%値引きした価格(消費税)を求める
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public int GetTenPercentPrice(Product product)
    {
        return product.Price * 0.10;
    }
}

本当にこれで良いの??

  • 消費税が10%から8%に変更されたとする
    • GetTenPercentPrice を以下のように変更する可能性あり
PriceCalculation.cs
public class PriceCalculation
{
    /// <summary>
    /// 商品の8%値引きした価格(消費税)を求める
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public int GetEightPercentPrice(Product product)
    {
        return product.Price * 0.08;
    }
}
  • 消費税の計算に関する修正としては問題ないが「10%値引きした価格計算」する処理がなくなった
    • 値引き計算している処理のところでコンパイルエラーや意図しない挙動になってしまう
    • 共通化しすぎかも??

本当の改善方法(例)

  • 「値引き価格計算」と「消費税計算」は別々に管理するほうがよいかも
    • 変更する理由を明確に分離できる
DiscountCalculation.cs
public class DiscountCalculation
{
    /// <summary>
    /// 商品の10%を求める
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public int GetTenPercentPrice(Product product)
    {
        return product.Price * 0.10;
    }
}
TaxCalculation.cs
public class TaxCalculation
{
    /// <summary>
    /// 商品の消費税を求める
    /// </summary>
    /// <param name="product"></param>
    /// <returns></returns>
    public int GetConsumptionTax(Product product)
    {
        return product.Price * 0.10;
    }
}

コードのコメントによる二重化

コード例

Bullet.cs
// 違反例
public class Bullet : MonoBehaviour
{
    /// <summary>
    /// Enemyオブジェクトに衝突したときに発火する
    /// GameObjectに格納している IEnemy の Component を取得
    /// Enemyオブジェクトにダメージを与える
    /// Component が取得できなかったら何もしないで処理を終了する
    /// </summary>
    /// <param name="collision"></param>
    void OnCollisionEnter(Collision collision)
    {
        var hit = collision.gameObject;
        var enemy = hit.GetComponent<IEnemy>();

        if (enemy == null)
            return;

        enemy.ApplyDamage();
    }
}

良くない点

  • コメントとコードで、この関数の情報が2度繰り返されている
    • コードを修正した場合、コメントを修正する必要がある
      • コメント修正を忘れる可能性大
      • 時間が経つと、コードとコメントが乖離していく

改善方法

  • 変数名、関数名などを工夫することで改善可能
    • 名前で意味や意図、役割を表すようにする
    • コードを読むだけで分かることはコメントに書かない

データ構造の二重化

縦、横、面積のデータ構造を持つコード例

Rectangle.cs
// 違反例
public class Rectangle
{
    public int Height { get; set; }
    public int Width { get; set; }
    public int Area { get; set; }
}

良くない点

  • 縦か横の長さが変化すると面積も変わる

改善方法

  • Area() は縦x横で面積を計算するメソッドに変更する
Rectangle.cs
// 改善例
public class Rectangle
{
    public int Height { get; set; }
    public int Width { get; set; }
    public int Area()
    {
        return Height * Width;
    }
}

終わりに

SOLID原則に続いてDRY原則をまとめました。
間違いや改善点あれば、ぜひ教えてください。
SOLID原則の記事は以下です。

  1. SOLID原則を勉強する その1~単一責任の原則 (SRP)~
  2. SOLID原則を勉強する その2~オープン・クローズド原則(OCP)~
  3. SOLID原則を勉強する その3~リスコフの置換原則(LSP)~
  4. SOLID原則を勉強する その4~インターフェース分離の原則(ISP)~
  5. SOLID原則を勉強する その5~依存性逆転の原則(DIP)~