【Flutter, Dart】ミュータブルの代償とイミュータブルの代償、そしてfreezed

# はじめに
本記事は [The Mutability Tax](https://medium.com/@davidmorgan_14314/the-mutability-tax-6403d84f21c0) をベースにしています。
意訳・抜粋しまくったので翻訳記事と呼ぶには忍びないですが、記述の足らない箇所があれば元の記事を参照してください。
筆者の David Morgan 氏はGoogleのソフトウェアエンジニアです。
<a href="https://medium.com/@davidmorgan_14314"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209400/0b75753f-80b5-f287-8b56-37cc956584a9.png" width=600px"></a>
元記事の公開は2019年7月15日です。
本文中に登場するコードは `Dart` で記述されています。

[The Mutability Tax](https://medium.com/@davidmorgan_14314/the-mutability-tax-6403d84f21c0) では、MutableとImmutableそれぞれの設計によって生じるコードメンテナンスコストのことを **Tax(税金)** と形容しています。
本記事では **代償** と表現します。

## 3点要約
1. Mutableな型はバグを生みやすいです。
2. Immutableな型も正しく扱わないとコードが肥大化してバグを生みやすく、遅くなります。
3. コード生成（freezed）の力を借りて、簡単に安全な型を定義しましょう。

## Mutable（値が変更可能）
システム間でMutableなデータを渡す疎結合モジュールを持つシステムは、Mutability-Taxという名のメンテナンスコストを支払います。

<img src="https://miro.medium.com/max/500/1*Nv-KeQz_5Kpb2j7AGRUGMA.png" width="49%">
### 🚨 Mutableの代償①：予期せぬ値の変更

上司 👹 ： おい新人。世界の都市圏人工TOP5のランキングと、その中で最大の都市を表示するコードを書いてみろ。ランキングは以下の通りやで。

1位：Tokyo
2位：Jakarta
3位：Delhi
4位：Manila
5位：Seoul

新人 😀 ： はい！わかりました！

上司 👹 ： こういう感じで出力されればOKやで。この中で最大の都市は東京やからな。

```
// Output
アルファベット順に表示: 
Delhi, Jakarta, Manila, Seoul, Tokyo
この中で最も大きいのは Tokyo です。
```

---

新人 😀 ： [ Dart List アルファベット順に表示 🔍 ] でググって…

```dart
void display(List<String> strings) {
  (strings..sort()).forEach(print);
}
```

新人 😀 ： あとは上司に指定されたリストを使って表示すればいけるやろ！

```dart
var biggestCitiesRanking = ['Tokyo', 'Jakarta', 'Delhi', 'Manila', 'Seoul'];

print('アルファベット順に表示: ');
DisplayAlphabetically(biggestCitiesRanking);

print('この中で最も大きいのは ${biggestCitiesRanking.first} です。');
```

`biggestCitiesRanking` の `first` を指定しているのですから、最大の都市である `Tokyo` が出力されて欲しいです。

```
アルファベット順に表示: 
Delhi, Jakarta, Manila, Seoul, Tokyo

この中で最も大きいのは Delhi です。
```

新人 😀 ： なんでや！Tokyoが最大のはずやのになんでDelhiなんや！

どうやら `DisplayAlphabetically().display()` を実行することで `biggestCitiesRanking ` が勝手に書き換えられているようです。

例えば上のような実装がされていると、本来は **表示するだけ** の関数を実装したつもりが、 **勝手にリストの中身を変更** してしまい 、結果間違った値を表示したというわけです。

これは `DisplayAlphabetically().display` を書いた人間が悪いという話ではありません。このような **予期せぬ値の変更** を行わないことを人間に徹底させること自体が非常にナンセンスです。

そしてこの問題への最適な取り組み方は、 `biggestCitiesRanking` を **変更不可能にする(Immutable)** にするということです。

上司 👹 ： おいドアホ！ちゃんとImmutableにしとかんかい！


## Immutable（値が変更不可能）
オブジェクト指向言語では、Immutabilityを人力で管理しようとすることでバグ、パフォーマンス上のリスクを発生させ、Immutability-Taxという名のメンテナンスコストを支払います。

<img src="https://miro.medium.com/max/500/1*ToYE5LWPhch4jaNBld_4fw.png" width="49%">
### 🚨 Immutableの代償①：巨大なクラス

Mutableなオブジェクトの受け渡しでは、オブジェクトが変更されたあとのことをアレコレ考える必要があります。
特にViewの実装や並列処理を行うようなコードにおいては、Mutableなオブジェクトの状態を推論することはとても難しく、莫大なメンテナンスコストと重大なバグを発生させる原因となります。
さきほどの例におけるオブジェクトの受け渡しはたった2個のモジュールの間に発生していましたが、モジュールが大量にある状態ではどうでしょうか。
モジュールの組み合わせの数は `nCr` で計算されるように爆発的に増加し、組み合わせの数だけコストが増えていきます。

<img src="https://miro.medium.com/max/500/1*Nv-KeQz_5Kpb2j7AGRUGMA.png" width="49%">

残念ながら、ほとんどのオブジェクト指向の言語は **Mutableの代償（Mutability-Tax）** を考慮して設計されていません。そのためにプログラマーはImmutableに関するルールやアンチパターンを勉強し、ライブラリを入れて構築しなければなりません。
しかし人間の頭に頼っていては、いつかは落とし穴にハマってしまいます。

落とし穴をもう少し深掘ってみます。

---

上司 👹 ： おい新人、顧客管理システムを想定したカスタマーデータのクラスを作ってみろ。

新人 😀 ： わかりました！

上司 👹 ： 名前をString、年齢をintで定義するところまではええな。

新人 😀 ： はい！

上司 👹 ： 次に `positional parameters (位置指定パラメーター)` か `named parameters （名前指定パラメーター)` のどちらを採用するか、という議論では、のちにパラメーターが増えたときの可読性をあげるために後者を選択するで。

新人 😀 ： なるほど！それは賢いですね！

```dart
class Customer {
  final String name;
  final int age;
  Customer({this.name, this.age});
}
```

上司 👹 ： 次に新しいカスタマー情報を更新する場合を考えるで。年齢を+1するコードを書いてみろ。

```dart
var customer = Customer(name: 'John Smith', age: 34);
var updatedCustomer = Customer(
    name: customer.name, age: customer.age + 1);
```

新人 😀 ： こんな感じですかね？

上司 👹 ： そうや。例えばマーケチームから、顧客が何回サイトを訪れたかを記録したいと言われたときを考えてみろ。

新人 😀 ： わかりました！じゃあ `visits` パラメーターを追加すればいいですね！仮に12回訪問している場合こんな感じですかね…

```dart
var customer = Customer(
    name: 'John Smith', age: 34, visits: 12);
```


上司 👹 ： じゃあさっき書いた、年齢を+1するコードを書いてみろ。

新人 😀 ： コピペでぽいっと

```dart
var customer = Customer(
    name: 'John Smith', age: 34, visits: 12);

var updatedCustomer = Customer(
    name: customer.name, age: customer.age + 1);
```

上司 👹 ： おいこら、そんなことしたら `visits` がリセットされるやないか！

新人 😀 ： うわ〜

上記のコードでは `visits` の値がリセットされてしまいます。
ここでの対応としては、 `positional parameters` を採用するか、 `@required` アノテーションで全てのパラメーターを必須とすることが考えられます。
しかし前述の通り位置指定パラメーターは可読性を落としますし、`@required` を全てに付与するということは、例えばカスタマーが訪れたときに `visits` に1を加算する処理においても全てのパラメーターを指定する必要があります。

上司 👹 ： これをメソッドで書くとこんな感じになるやろ？

```dart
class Customer {
  final String name;
  final int age;
  Customer({this.name, this.age});
  Customer copyWith({String name, String age}) =>
      Customer(name: name ?? this.name, age: age ?? this.age);
}
```

新人 😀 ： 完璧っすね！

```dart
var updatedCustomer = customer.copyWith（age： customer.visit + 1）;
```

上司 👹 ： 甘いねん。Dartは `null` に弱いねん。このメソッドやと `visit` に `null` が渡されたことを認識できひんねん。

```dart
// nullは認識されないため、このコードは動きません
var customerWithoutVisits = customer.copyWith(visits: null);
```

上司 👹 ： Nullを認識させるためにはな、それぞれのフィールドに `with` メソッドを使ったら一応イケるねん。

```dart
class Customer{
  final String name;
  final int age;
  final int visits;
  Customer({this.name, this.age, this.visits});
  Customer withName(String name)
      => Customer(name: name, age: age, visits: visits);
  Customer withAge(int age)
      => Customer(name: name, age: age, visits: visits);
  Customer withVisits(int visits)
      => Customer(name: name, age: age, visits: visits);
}
```

新人 😀 ： フィールドがリセットされることも無いし、Nullにも対応したし、今度こそ完璧じゃないですか！

上司 👹 ： お前な、実際のサービス考えてみい。`Customer` が持つフィールド何個あると思てんねん。余裕で10個以上あるわ。

新人 😀 ： 確かに…。その分だけ `with` を書いて、その分だけ引数を渡すことになりますね。

上司 👹 ： そんなデカいボイラープレートで運用してたらお前絶対ミスるやろ。何よりクソ重なるわ。

新人 😀 ： はい…

### 🚨 Immutableの代償②：ネストされた型への対応
上司 👹 ： コレクションと、ネストされた型が使用される場合を考えてみろ。

Mutable(変更可能)なコレクションをImmutable(変更不可能)なオブジェクトに組み込む場合には、それらを安全にコピーする必要があります。
またImmutableな型を使用する利点は高速であることですが、遅くなってしまうことが考えられます。
そして、先ほどのように `with` メソッドをしてしまうとネストされたフィールドの扱いが面倒になってきます。

```dart
class ShoppingBasket {
  final Customer customer;
  final List<Item> items;
  final List<Offer> offers;
  ShoppingBasket(
      this.customer,
      Iterable<Item> items,
      Iterable<Offer> offers)
    // Copy defensively to ensure immutability.
    : this.items = List.unmodifiable(items),
      this.offers = List.unmodifiable(offers);
  // TODO: add "with" method per field.
}
```

上司 👹 ： 例えば上のECサイトのカート(ShoppingBasket)に商品を追加する場合、 `with` を使ってこう書けるよな。

```dart
var updatedBasket = basket
    .withCustomer(basket.customer.withName(updatedName))
    .withItems([...basket.items, newItem, newItem2]);
```

新人 😀 ： でもこんなのいちいち `with` でコピーしてたら遅くなりませんか…？

上司 👹 ： …その通りや。Immutableは高速であることが利点やのに、遅くしてどないすんねんっちゅう話や。

上司 👹 ： 追加に関しては `add` で書くこともできるけどな、まあこれも同じような話や。

```dart
var updatedBasket = basket
    .withCustomerName(updatedName)
    .addItem(newItem)
    .addItem(item2);
```

新人 😀 ： なんかまたボイラープレートが大きくなりそうですね。

上司 👹 ： せやな。その上毎回フルコピーが実行されるから遅いしな。

## 結局何が実現されていれば良いのか

ここまで問答を繰り返してきましたが、理想としては以下の4点が実現されると良さそうです。

**1. Immutableなデータが提供され**
**2. 更新が簡単かつ高速で**
**3. Nullを扱うことができ**
**4. フィールドが追加されても既存のコードに影響を与えない**

上司 👹 ： 結論としては、ボイラープレートをコード生成するライブラリを使えっちゅう話や

新人 😀 ： 例えばどんなのがあるんですか？

上司 👹 ： Dartの独自のライブラリやと `Built Value` やな。

### Built Value
**※現在はこれに代わってfreezedの使用が推奨されています**

**特徴**
1. Immutableな型 （コレクションは [built_collection](https://github.com/google/built_collection.dart#built-collections-for-dart)）
2. EnumClass（enumsのような機能）
3. JSONのシリアル化

上司 👹 ： ボイラープレートの管理は本来コストがかかるしバグを生みやすいよな。でもコード生成の場合はそのコストがかかるのは最初だけやねん。

新人 😀 ： なるほど…Mutableの代償みたいに税金みたいに段々重くなるコストじゃなくて入場料を払えばOKって感じですね！

上司 👹 ： お前もわかってきたな。 `built_value` を使ったらボイラープレートは生成してくれるからな、人間が書くのはこれだけでええねん。

```dart
abstract class Customer implements Built<Customer, CustomerBuilder> {
  String get name;
  int get age;
  @nullable
  int get visits;
factory Customer(void Function(CustomerBuilder) updates) = 
    _$Customer;
  Customer._();
}
```

新人 😀 ： 基本的なプロパティとアノテーションだけでいいのはありがたいですね！これならエンバグしなさそうです！

上司 👹 ： これはJavaでいうところの [AutoValue.Builder](https://github.com/google/auto/blob/master/value/userguide/builders.md) にあたるな。ほんでその開発チームがコード生成ライブラリの必要性についてわかりやすくまとめたスライド [AutoValue: what, why and how?](https://docs.google.com/presentation/d/14u_h-lMn7f1rXE1nDiLX0azS3IkgjGl5uxp5jGJ75RE/edit#slide=id.g2a5e9c4a8_00)  を公開してくれてるわ。

> **[AutoValue: what, why and how?](https://docs.google.com/presentation/d/14u_h-lMn7f1rXE1nDiLX0azS3IkgjGl5uxp5jGJ75RE/edit#slide=id.g2a5e9c4a8_00) の要約**
多くのオブジェクト指向言語では、標準の言語仕様に従って **人力で** Immutabilityを実現しようとすると、メンテナンスコスト、バグ、パフォーマンスの問題が発生します。つまりMutableの代償を避けるために、今度はImmutableの代償を払う事態になっています。Immutableオブジェクトのボイラープレートを生成し、管理する **ライブラリを利用する** ことこそが、Mutabilityへの正しい向き合い方です。

## 結論、 freezed でコード生成しなさい

上司 👹 ： さっきは `built_value` を例に上げたけど、最近は `state_notifier` とセットで `freezed` がよう使われてんねん。

---
[元の記事](https://medium.com/@davidmorgan_14314/the-mutability-tax-6403d84f21c0) の公開後、 `built_value` より更に優秀な [freezed](https://github.com/rrousselGit/freezed) というライブラリが発表されました。

ここでは簡単に `freezed` によって解決される、 `built_value` が劣っている点を記しておきます。
1. 大量のボイラープレートが必要となります。
2. `built_value` は `Dart1` 用に作られており、順次v2に対応しているようですが、所々未対応の部分があるようです。
3. コード生成がかなり遅いです。

※ コード生成については `freezed` もそれなりに遅いですが、その問題に対してはmonoさんが素晴らしいTipsをtweetしておられました。これには作者であるRemi氏も同意していました。

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Clever idea!<br><br>Honestly, I wonder if this sort of optimization could be built right into package:build<br><br>It&#39;s probably analyzing files that don&#39;t need to be analyzed</p>&mdash; Remi Rousselet (@remi_rousselet) <a href="https://twitter.com/remi_rousselet/status/1284307477878767616?ref_src=twsrc%5Etfw">July 18, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

---


新人 😀 ： なるほど、 `freezed` 使ってみます！

上司 👹 ： もう人力でやろうとすんなよ！


---

P.S.
誤りがありましたらコメントにてご指摘いただけますと幸いです。