仕様変更があったときになるべく影響範囲が小さくて済むように直したつもりだけど、できてなかった話


#概要

仕様変更があったときになるべく影響範囲が小さくて済むように直したつもりだけど、
思わぬ落とし穴があった話。

* 目的
  + 失敗事例の共有
* 対象者
  + アプリケーション開発者

#経緯

## １．まず最初の状態

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/568592/9ccddf40-0207-db58-51e7-34f6bb30eb93.png)
Food.exeは好きな食べ物の紹介をし、Sports.exeは好きなスポーツの紹介をするプログラム。
Food.exe, Sports.exeともにCommon.dll内の関数を使っている。
Common, Food, Sportsのソースは以下の通り。

```c#:Common&#x20;Class1.cs
namespace Common
{
    public class Class1
    {
        /// <summary>
        /// 自己紹介文を作成します。
        /// </summary>
        /// <param name="strFavorite">好きなもの</param>
        /// <returns>自己紹介文</returns>
        public static string CreateIntroduction(string strFavorite)
        {
            string strIntroduction = "私の好きなものは{0}です。";
            return string.Format(strIntroduction, strFavorite);
        }
    }
}
```


```c#:Food&#x20;Form1.cs
namespace Food
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 私の好きなものは寿司です。
            this.label1.Text = Common.Class1.CreateIntroduction("寿司");
        }
    }
}
```


```c#:Sports&#x20;Form1.cs
namespace Sports
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            // 私の好きなものは野球です。
            this.label1.Text = Common.Class1.CreateIntroduction("野球");
        }
    }
}
```

## ２．仕様変更

運用開始後、仕様変更により、好きな食べ物を紹介するときだけ時々シャウトすることになった（謎）
で、Common.dllのCreateIntroduction関数にシャウトするかどうかの引数``isShout``を増やして対応。
単に引数を増やすとこの関数を使っているすべてのソースを修正しなければならないが、
仕様変更になったのは食べ物のときだけで、スポーツのときは関係ないのでSportsプロジェクトは触りたくない。
そのため、``isShout``のデフォルトを``false``にしておく。こうすれば２番目の引数を指定していなくても``false``を指定したのと同じになってエラーにならないはず♪

↓　修正後のCommonとFoodのソース。Sportsは修正しなくてもOK♪

```c#:Common&#x20;Class1.cs
namespace Common
{
    public class Class1
    {
        /// <summary>
        /// 自己紹介文を作成します。
        /// </summary>
        /// <param name="strFavorite">好きなもの</param>
        /// <param name="isShout">true：シャウトする</param>
        /// <returns>自己紹介文</returns>
        public static string CreateIntroduction(string strFavorite, bool isShout = false)
        {
            string strIntroduction = "";

            // シャウトするかどうか
            if (isShout)
            {
                strIntroduction = "オレの好きなものは{0}だぜ！！";
            }
            else
            {
                strIntroduction = "私の好きなものは{0}です。";
            }

            return string.Format(strIntroduction, strFavorite);
        }
    }
}
```



```c#:Food&#x20;Form1.cs
namespace Food
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            Random random = new System.Random();
            bool isShout = false;

            // ランダムな数字を作成し、偶数ならシャウトする
            if (random.Next(0, 10) % 2 == 0)
            {
                isShout = true;
            }

            // 私の好きなものは寿司です。/ オレの好きなものは寿司だぜ！！
            this.label1.Text = Common.Class1.CreateIntroduction("寿司", isShout);
        }
    }
}
```


## ３．そして何が起こったか

修正したCommonとFoodをビルドし、運用環境に配置。
Food.exeを動かして、ちゃんと時々シャウトすることを確認しミッションコンプリート、と思ったのもつかの間。
「Sportsを動かすとエラーになるんだけど」
という連絡が。

え、Sportsは修正しなくてもいいはずなのに・・・(;'∀')

実はSportsはソースの修正は必要ないのですが、使っている関数の引数が変わったのでビルドをし直さないといけなかったのです。


## ４．どうすればよかったんだろう？

今回はSportsもビルドし直さないといけなかったね、で終わるかもしれませんが、実際はもっといろんなプログラムでCreateIntroduction関数を使っている可能性があり、思わぬところで影響が出てしまうことがあります。
外部から使われているプログラムを直すときは、それ自体を作り変えるのではなく、拡張するようにするといいと思います。
やり方はいろいろあると思いますが、例えば、こんな感じ。

```c#:Common&#x20;Class1.cs
namespace Common
{
    public class Class1
    {
        /// <summary>
        /// 自己紹介文を作成します。
        /// </summary>
        /// <param name="strFavorite">好きなもの</param>
        /// <param name="isShout">true：シャウトする</param>
        /// <returns>自己紹介文</returns>
        public static string CreateIntroduction(string strFavorite, bool isShout)
        {
            if (isShout)
            {
                // シャウト版
                return CreateIntroductionShout(strFavorite);
            }
            else
            {
                // シャウトしない版
                return CreateIntroduction(strFavorite);
            }
        }

        /// <summary>
        /// 自己紹介文を作成します。（シャウトしない版）
        /// </summary>
        /// <param name="strFavorite">好きなもの</param>
        /// <returns>自己紹介文</returns>
        public static string CreateIntroduction(string strFavorite)
        {
            string strIntroduction = "私の好きなものは{0}です。";

            return string.Format(strIntroduction, strFavorite);
        }

        /// <summary>
        /// 自己紹介文を作成します。（シャウト版）
        /// </summary>
        /// <param name="strFavorite">好きなもの</param>
        /// <returns>自己紹介文</returns>
        public static string CreateIntroductionShout(string strFavorite)
        {
            string strIntroduction = "オレの好きなものは{0}だぜ！！";

            return string.Format(strIntroduction, strFavorite);
        }
    }
}
```

もともとあった``CreateIntroduction(string)``は残し、``CreateIntroduction(string, bool)``と``CreateIntroductionShout(string)``を増やすことで仕様変更に対応。
ちなみに``CreateIntroduction(string)``は引数と戻り値さえ変えなければ、中でやっている処理は変えてもOKです。


以上、本当にあった怖い話でした。