副作用のあるコードはなぜいけないのか

このエントリーをはてなブックマークに追加

副作用のあるコードは書かないほうがいいというものは経験上わかっていたのですが、今まではなぜ書いてはいけないのかのいい例が思いつかずあまり説明できていなかったのですが、いい例を思いついたので書いておきます。

副作用のあるコードとはなんぞや

副作用のあるコードとはを語るうえで副作用のあるコードとはなんなのかを決めておかないといけませんよね。 ここではメソッド名から推測できない作用のあるコードとしておきます。

たとえばのコードを挙げましょう:

public class Repository
{
    private IList<string> messages;
    
    public string GetTitle()
    {
        if(messages == null)
        {
            messages = new List<String> {
            	"Hello",
                "World"
            };
        }
        return "Title";
    }    
}

GetTitleメソッドでは名前からTitleを返却することがわかります。(定数として"Title"を埋め込んでるのは簡略化のためでありこの話とは関係ないです)
しかし、GetTitleメソッドの名前では説明していないmessages変数の初期化という処理があります。このようなメソッド名から推測できない作用のあるコードのこと副作用のあるコードと表現します。

なぜ副作用のあるコードがいけないのか

先ほどのコードを少し拡張してみましょう:

public class Repository
{
    private IList<string> messages;
    
    public string GetTitle()
    {
        if(messages == null)
        {
            messages = new List<String> {
            	"Hello",
                "World"
            };
        }
        return "Title";
    }
    
    public string GetMessage(int index)
    {
        // 境界値チェックは省略
        return messages[index];
    }
}

このようにGetTitleメソッドを実行後にしか実行できない(実行したら例外が出る)GetMessageメソッドを追加してみました。(これはこれで問題のあるコードですがこのメソッドではGetMessageという名前が表す処理にします)

この時点での、このコードの問題点はGetTitleメソッド実行後にしかGetMessageメソッドを実行できない仕様ということです。

このコードを書いた人なら、このコードを利用する際に以下のようなコードを書くでしょう。

var repository = new Repository();
string title = repository.GetTitle();
string message = repository.GetMessage(0);

しかしながら、このような実行順序が決まっているコードは利用者となるコードを読むだけでは推測できません。 他の人がコードを書きなおす際にGetMessageGetTitleより先に呼び出すように書き換える可能性があります。本来ならばそういう場合にも耐えうるコードを用意しておくというのが保守性の向上につながります。

副作用のないコードを書くにはどうしたらいいのか

答えは単純明快でメソッド名が表す処理しか行わないことです。

先ほどのコードはGetTitleメソッドではただ単にTitleを返すだけの処理をし、messages変数の初期化は他のところ(たとえばコンストラクターとか)で行えばよいです。

おわりに

例として提示するコードがいろいろと問題のあるコードということはご了承ください……

最初思いついたときは「Get***メソッドで初期化をする副作用を見せて、メソッドの呼び出し順によってはダメだよね、という説明をすればいいじゃん」ということだったんですが、いざ実際に例を書いてみるとちょっとダメなコードになりましたね。