技術

値が不変なプログラムの書き方

w2solution

w2solution

2020/12/28

こんにちは。エンジニアの今井です。

最近のプログラミングでは、テスト容易性や保守性などの観点から値の不変性が重視されています。
今回はそんな値の不変性を実現する手法を2つご説明します。

純粋関数

純粋関数とは以下の条件を満たした関数のことです。
・入力に対して出力が一意に決まる。
・副作用が発生しない

「入力に対して出力が一意に決まる」とは、例えば以下のようなメソッドのことを言います。
このようなメソッドは「〇回繰り返すと挙動が変わる」というようなことがないため、テストが容易になります。

int add(int value1, int value2)
{
	return value1 + value2;
}

逆に以下のようなものは純粋関数ではありません。このような関数の場合、テストが難しく、また処理を追うのも難しいです。

// 値を変更している
void add(ref int value1, int value2)
{
	value1 += value2;
}
// 外部の値に結果が依存している
int value2

int add(int value1)
{
	return value1 + value2;
}

さて、副作用とはなんでしょうか?
これは値を変更しないことに加えて、外部システムと連携しないということなどを指します。
例えば、以下のコードは結果が一意ですが副作用があります。

void add(int value1, int value2)
{
	Console.WriteLine(((value1 + value2).ToString());
}

上記は標準出力なので画面を見ればデバッグできますが、ユニットテストができなくなってしまいます。
さらに、副作用がDBや外部サービスのAPIになってしまった場合、相手側の通信や状態によって、戻ってくる結果が異なることも明らかです。
こうなってくると、テスト環境の構築も難しくなります。
言い換えれば、副作用がない場合はテスト環境の構築が容易ですし、何よりも純粋なロジックの世界で処理を書くことができます。

このようにメソッドが純粋関数だとテストが簡単になり、動作も容易に予想ができるようになります。

値オブジェクト

値オブジェクトとは、コンストラクタ以外で値が変化しないオブジェクトのことを言います。
初期化ですべてが決まるため、他のクラスにオブジェクトを渡してもステートをいちいち考慮しなくていいのが魅力的な手法です。

値オブジェクトの例は以下のものがあげられます(説明のため、C#の糖衣構文である自動実装プロパティは利用していません)。

class hoge{
	private readonly int foo;
	
	public int Get(){
		return this.foo;
	}
	
	public hoge(int foo){
		this.foo = foo;
	}
}

値オブジェクトではないものは以下になります。
以下の例の場合、hogeオブジェクトを扱う間、他の誰かがfooの値を変化していないか意識し続ける必要があります。

// Setメソッドがあることによって値が変化可能になっている
class hoge{
	private int foo;
	
	public int Get(){
		return this.foo;
	}
	
	public void Set(int foo){
		this.foo = foo;
	}
	
	public hoge(int foo){
		this.foo = foo;
	}
}

このように値オブジェクトでクラスを書くと、オブジェクトを一度生成すれば、その後はオブジェクト内の値を意識しなくて良いため、状態管理が楽になります。

おわりに

だいぶ簡単に書いてしまいましたが、、、如何だったでしょうか?
最近のC#では自動実装プロパティにreadonlyのような特性を与えることができ、不変な値が扱いやすくなっています。
それだけ、値が不変であることや他の値に依存しないことが重要になっているのでしょう。

私自身、こういうことを気を付けることでコードの品質が上がったように感じます。
もしこのようなことを意識してなかった方がいたら、今後は是非意識してみてくださいね。