副作用と参照透過性と冪等性を理解して、テストでドメインを磨く

[ドメイン駆動設計#1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/ddd-1) の 16 日目の記事です

この記事はふわーっと副作用・参照透過性・冪等性を理解して、モデルをそれっぽーくリファクタリングするものです

ゆるーっとご覧ください

では早速...

## 副作用
結果が引数以外に依存して決まる処理のこと

「グローバル変数があるので処理を読んでも結果がわからない」とか「同じ実行の仕方をしても朝と夜で結果が変わる」とか言う感じ

```java:副作用があるコードの例
int function add2(int n) {
  return this.x + n;      // x って何よ？
}
```

```java
void function init() {
  this.x = 2;             // これです
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/f05a6d85-8447-c6f6-06e6-025c9f744ebd.png" width="60%" />

## 参照透過性
処理を同じ引数で実行すると同じ結果になること

```java
int function add(int x, int y) {
  return x + y;
}
```

朝でも夜でも mac でも windows でも商用環境でも日本でもアメリカでも、`add(2, 3)`は`5`

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/614ea9b5-c5ae-d78e-70fc-072f8f560710.png" width="60%" />

+ 透過的 ( transparency ) とは「存在するものを存在しないように見せる」感じらしい
  + 透過の反対は仮想らしい
  + 透過 → あるけど見えないガラス
  + 仮想 → ないけど見えるホログラム
+ つまり「参照透過性」は「参照があるけどないように見せる」ということ
  + 変数の再代入やグローバル変数の参照が処理内になければ、処理内では変数の参照について心配する必要がないので、参照が透過的と言える

## 冪等性
処理を何回実行しても同じ結果になること

```java
void function bill(int id) {
  if billed { /* do nothing */ } else { ... }
} 
```

現状を気にせず叩けば良いので、chef やマイクロサービス等のたくさんの状態を更新する処理と相性が良い  
（一部が失敗しても全てが成功するまで叩き直せば良いから）

身近な例だと「オン | オフ」みたいなトグルボタンは冪等性がないけど「オフにする」ボタンは冪等性がある、って感じかな  
あとは絶対値とかの値計算もそう

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/43c96c74-f20c-8965-de48-505f732a7176.png" width="60%" />

## 整理
副作用と参照透過性はだいたい裏返しって感じで良いかな？

冪等性はドメイン層と言うよりアプリケーション層やコンポーネント結合の設計に関係しそうな感じかな？

この記事では整理した３つの単語のうち **副作用** に目をつけて、ドメイン層をリファクタリングしてみます

## 副作用についてもうちょっと
### 特徴を再整理
+ 処理のスコープの外の何かに依存しているとだいたいそう
  + スコープ外変数、DB、ファイル、時間、環境変数、os コマンド、http 通信、乱数
+ 戻り値がない処理もだいたいそう
  + print、setter、insert、update、send
  + 何も戻さないということはどこかを書き換えている
+ 別に例えば他の private メソッドを呼んではいけないという意味ではない
  + 自身にも private メソッドにも副作用がなければ、副作用はない

### ドメイン層に副作用があるとどうなるか
例えば時間に依存しているドメイン処理があると、動作確認できる時間が限られてしまう

毎月１日しか動作確認できない請求処理とか超怖いよね

### どうすれば良いか
そんなに難しくはない、テストコードを書いてみればわかるはず

テストコードを書くときに DB のセットアップが必要になったり、システム時計を改ざんしたり、ダミーのリクエスト先サーバを立てたりする必要があると、それはもうばっちり副作用がある

### でもなくなりはしないよ
どうしてもどこかで副作用は扱わなきゃいけない、それはその通り

だけどその層を限定しようというのが狙いだ

以下の理由により、ドメイン層では（極力）副作用は扱わない方が良いはずなんだ

+ ビジネスルールってルールなので、シンプルなただの計算のはずなんだ、**引数だけで結果決まりそうだよね？**
+ 変更に耐えられるように、たくさんのパターンを網羅できるように、ドメイン層のテストコードはあった方が良いよね？ けど **副作用があるとテストコードが{書けない|異様に書きづらい}**
+ ビジネスルールに DB とか os コマンドってそもそも **関係ないよね？**

## テストコードを書きながらリファクタリングしてみよう
### メール本文を組み立てて飛ばす
こんなコードがあったとしよう、残念ながらテストはないんだけども

↓ `send(...)`のテスト書けるかな？

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/6acba690-c2a5-38d6-da0b-1fc476db920c.png" width="80%" />

渡した引数をごにょごにょして社内ライブラリに渡しちゃってるので、加工が成功したかも社内ライブラリの言う通り渡せたかも、 **何もわからないね**

戻り値もないしね ( scala's Unit means java's void. )

モックのメールサーバでも立てる？

めんどくさいね、いやだね

↓ ちょっと整理してみよう、テストできるところを緑で、できないところを橙で、副作用がある範囲を赤で色分けしてみるよ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/cd14c71c-2b14-9fbb-a590-5837b296f271.png" width="80%" />

あぁ！なんて危険がいっぱいな処理なんだ！

↓ この処理を呼び出す部分はこんな感じね、副作用がある処理を呼ぶので必然的にこいつも副作用があるよ

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/92c5dd32-0a5e-50d9-1c99-695f8e22b551.png" width="80%" />

↓ とりあえず、タイトルと本文組み立てを`send(...)`から引き剥がしてみようかな？

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/5e3bc0ab-2ca9-f6e3-935d-843304462dfe.png" width="80%" />

でもこっちに持ってきても結局副作用の中なのであんまり変わらないね

↓ **`sub`と`body`を value object にしない限りは** ね！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/207dd3a9-c72b-1422-c3ba-a91ea97151de.png" width="80%" />

初めて緑が現れたね！

組み立て部分はただのコンストラクタに書き出したので、`Sub`と`Body`それぞれでテストして品質担保しておけるよ！

`Sub`と`Body`は **引数によってのみ決まる** ってことがはっきりしたね、つまり参照透過性があるんだ

ところで、最初の`send(...)`の引数を覚えている？

```scala
def send(to: String, name: String, items: Seq[String]): Unit
```

それが今はこう変わっているよ

```scala
def send(to: String, sub: Sub, body: Body): Unit
```

`String`もらうより全然良さそうだよね？

`String`だと「文字列って言われても...メアド？それとも件名？まさかヘッダ？」って混乱しちゃうし、`item`の仕様が変わったときに`send(...)`まで直さないといけないしね

↓ ここまで来たら「`to`も`To`にしちゃえ、っていうかまるっとまとめて`Mail`にしちゃえ」と思わないかな？

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/4aaf8425-ffd5-f1c0-b340-6da345c7dddf.png" width="80%" />

↓ 副作用がなくて品質担保できている`To`, `Sub`, `Body`の組み合わせで`Mail`が作れたので、`Mail`もそうだ！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/11d4e774-e761-13a5-7fb3-2f58f8aa11ae.png" width="80%" />

これはつまり **今まで`send(...)`の中でごっちゃになっていた「メールの組み立て」と「メールの送信」をきっちり分けられた** ってことだね！

`send(...)`に目を戻してみよう

↓ あとは`Lib.sendmail(...)`に正しく`Mail`を文字列化して渡せているかがテストできれば安心なんだけど、手はないのかな？

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/58bb2122-4ae9-e52d-4bb2-8a3bdd00d20a.png" width="80%" />

もちろん手はあるよ！さっきみたいに文字列化を別のクラスに切り出しても良いんだけど、今度はせっかくなので di の恩恵を受けてみよう

↓ 適当に`Mailer`と`MailerImpl`を作って、そいつで社内ライブラリを扱うようにしてみた

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/9d889e1c-61f7-e186-6d02-920482ad93d2.png" width="80%" />

↓ テスト時はこの impl を別で作って、中身を`Lib.sendmail`じゃあなくて`assert`にしてしまう、という手があるよ！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/4156a957-3fab-726b-1802-1d648e92df98.png" width="80%" />

これで「渡した`Mail`がちゃんと文字列化されてること」もテストできたね

↓ なのでこっちも緑になるぞ！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/113398/9fee125b-1ecd-8e7e-cba2-25831785c688.png" width="80%" />

リファクタリング完了！

> モックのメールサーバでも立てる？

それよりはるかに楽に色々テストできたね！

### やってみて
素直にテストできる範囲を広げようとコードをいじってただけだけど、ドメイン的に得られたことがある

それは`send(...)`の引数が`String`から`Mail`になったことと、メール組み立てとメール送信が分離されたこと

せっかく`Mail`ができたので「`Mail`って entity? value object?」なんて考えてみると、**さらに色々発見があるかも** しれない

他にも「メール送信履歴」や「二重送信防止」とか考えると`MailId`みたいなのがないとダメじゃね？とかね

とにかく **メールについてあれこれ考える土台ができている** はず、これは`String`の羅列では行えない

## まとめ
こんな感じで既存の実装をちょっと「テストちゃんとしよ」って思っていじると **新しいドメインクラスが生まれる** と言うのは、実は結構ある

そして副作用なく小さく品質担保されているコードが増えると、リファクタリングがしやすくなる

こんなアプローチでドメインを磨いていくのも、個人的には結構アリなんじゃあないかな、と思う　楽しいしね

## おしまい
明日はどんな内容かなー