本シリーズ
単体テストと副作用 - 第1話 はじまり
単体テストと副作用 - 第2話 オブジェクト指向
単体テストと副作用 - 第3話 DIとモック
単体テストと副作用 - 第4話 副作用の回避
単体テストと副作用 - 第5話 オブジェクト指向への反乱
前回のあらすじ
- 副作用を回避することに成功した。
アカシックレコード
『…ここに…アカシックレコードに格納されたファイルを開き…未来を書き換えるプログラムがあります…』
女神さま、突然何を言い出すのかお(´・ω・`)
『…コードを以下に示します…』
using System;
using System.Collections.Generic;
using Unity;
public interface IAkashicFileFactory
{
IAkashicFileWrapper OpenFile(string path);
}
public interface IAkashicFileWrapper : IDisposable
{
IAkashicRecord GetRecord(string address);
void SetRecord(string address, IAkashicRecord record);
}
public interface IAkashicRecord
{
IEnumerable<string> Contents { get; }
}
public interface IAkashicProcessor
{
IAkashicRecord ProcessRecord(IAkashicRecord record);
}
public class MainFlow
{
[Dependency]
public IAkashicFileFactory FileFactory { get; set; }
[Dependency]
public IAkashicProcessor Processor { get; set; }
public void Run(string path, string address)
{
using (var file = FileFactory.OpenFile(path))
{
var oldRecord = file.GetRecord(address);
var newRecord = Processor.ProcessRecord(oldRecord);
file.SetRecord(address, newRecord);
}
}
}
なんちゅうプログラムだお(´・ω・`)
『…このプログラムには…4つのインターフェイスが登場します…。それぞれ全く異なる性質を持っていることに気づきましたか…?』
…(´・ω・`)?
『…フィールド…メソッド…副作用…。これらに着目すると…オブジェクトは以下ように分類できます…』
女神さまの見解
『…オブジェクトの分類は下記のようになります…
- 不変なフィールドのみを持つ「不変オブジェクト」(副作用のないメソッドを持つ場合もある)
-
IAkashicRecord
が相当
-
- メソッドのみを持つ「振る舞いオブジェクト」
-
IAkashicProcessor
が相当
-
- フィールドとメソッドを持つ「状態変異オブジェクト」
-
IAkashicFileWrapper
が相当
-
- 状態変異オブジェクトを生成する「ファクトリオブジェクト」
-
IAkashicFileFactory
が相当
-
…ここでの分類名は…私が勝手に命名したものです…悪しからず…』
どうしてこういう分類になるんだお(´・ω・`)?
『…愚かな人の子よ…すぐに神頼みせず…自分で考えるのです…』
…(´・ω・`)
考察
前回までを振り返ると…。
私は単体テストがしたかった。
そのために、副作用を回避する必要があったよね。
だから、副作用を持つ機能を隔離した。
その結果、不変なデータと副作用のないメソッドのみを持つ TypedValue
クラスが生まれたんだお(・ω・)
そして、隔離された副作用は、状態を持たない、メソッドだけのクラス ConsoleWrapper
と TaskWrapper
として形成されたね。
状態を持たないクラスでないと、DIの時に都合が悪い。
状態を持つクラスをDIで生成できないわけではないけど、クラスの生成と管理が面倒になるもんね。
以上のことから、女神さまの言う「不変オブジェクト」と「振る舞いオブジェクト」の意義が生まれるお。
『…では…「状態変異オブジェクト」は…管理が面倒だから排除すべきでしょうか…?』
うーん…。
基本的には無い方がいいと思うけど、さっきのアカシックレコードの例のように、状態と振る舞いと副作用を備えたクラスが必要になる場合もあるよね。
少なくとも、C#をやっている限りはそうなる。
こういうオブジェクトは、テストのときに困る。
副作用があるからテストが難しいのに、状態を持っているからDIによる外部からの注入も困難だ。
何とかモックを注入したい。
その時に考えられるのは…。
「Abstract Factory パターン」だ!
「状態変異オブジェクト」のモックを生成するファクトリを外部から注入すれば、副作用を回避できる。
ファクトリは状態を持たないから、簡単に注入が可能だ。
これで、「状態変異オブジェクト」と「ファクトリオブジェクト」の意義が見出せたお。
(^ω^)
『…頭空っぽな人の子よ…一つの答えに辿り着いたようですね…』
女神さまのおかげだお(・∀・)!
『…ところで…これは既にオブジェクト指向から逸脱していることに気づいていますか…?』
…(´・ω・`)?
『…データと振る舞いが分離されているでしょう…?』
ふーむ。
確かに「不変オブジェクト」が持つのはデータのみで、振る舞いを持っていたとしても簡単に分離可能だお。
「振る舞いオブジェクト」は、継承を使わずに、注入によって機能の拡張ができるお。
「状態変異オブジェクト」だけはオブジェクトっぽいけど、これは基本的には特別扱いのオブジェクトだお。
『…「振る舞いオブジェクト」をパズルのように組み合わせて…「不変オブジェクト」を操作してゆく…。これはつまり…』
何だか関数型プログラミングに似ているお(・ω・)
『…そうですね…。"オブジェクト指向への反乱"の意味が分かりましたか…?』
オブジェクト指向をやっていたら、いつの間にか関数型っぽくなっていたでござる(・ω・)
『…関数型は…オブジェクト指向と比較して…テストが容易だと言われます…。この結末は必然なのかもしれません…』
そうだったのかー(・ω・)
~ 完 ~
まとめ
- 「不変オブジェクト」「振る舞いオブジェクト」「状態変異オブジェクト」「ファクトリオブジェクト」を使うとテスタブルなコードが書きやすくなる。
- 最終的に関数型みたいになった気がする。
本シリーズ
単体テストと副作用 - 第1話 はじまり
単体テストと副作用 - 第2話 オブジェクト指向
単体テストと副作用 - 第3話 DIとモック
単体テストと副作用 - 第4話 副作用の回避
単体テストと副作用 - 第5話 オブジェクト指向への反乱