LoginSignup
25
11

More than 3 years have passed since last update.

Dart 2.9 で新しく追加された exhaustive_cases Lint が便利

Last updated at Posted at 2020-08-07

Dart 2.9 で追加された、exhaustive_cases という Lint が便利でした。特別な制約を満たした Classを、 enum のように switch 文での網羅的な検査を可能にします。

Dart の enum

Dart の enum は特殊な Class です。

enum の特徴として、switch 文で使用すれば、すべての enum の値を列挙しないと警告されますので、列挙漏れの可能性を無くせます。

enum Color { red, green, blue }

var aColor = Color.blue;

switch (aColor) {
  case Color.red:
    print('Red as roses!');
    break;
  case Color.green:
    print('Green as grass!');
    break;
  default: // Without this, you see a WARNING.
    print(aColor); // 'Color.blue'
}

しかし、enum は(おそらくは Dart 1 時代の哲学としてベストな手法を決めるまでの対応として意図的に)表現力が低く、他言語で機能が豊富な enum を経験した開発者からの不満も多く聞こえてきます。

例えば toString()では、

print(Color.blue.toString()); // prints "Color.blue".

となりますので、value 部分の "blue" だけがほしいならば、

print(Color.blue.split('.').last); // prints "blue".

などとアドホックなコードを書くこともあります。

Flutter では、describeEnumという、上記の処理に相当する関数さえ用意されています。

Dart 2.9 で追加された lint である、exhaustive_cases を有効にすれば、記載の通りの制約付き class を用いて、switch 文で enum と同じように網羅的な検査がされ、かつ class の表現力を得られます。

Enum のように使える Class

この lint を適用すると、enum のように switch 文で網羅的な検査がされます。網羅的な検査に引っかかると、静的解析器が警告します。

ただし、そのように使える Class にするには、以下の制約を満たす必要があります。

  • only private non-factory constructors
  • two or more static const fields whose type is the enclosing class and
  • no subclasses of the class in the defining library

制約を満たせば、Class の表現力を使えますので、たとえば、実行時にdescribeEnumで文字列処理をして enum value を取り出さなくとも、単に instance 変数に String 表現を保存しておけます。

class EnumLike {
  final String name;
  const EnumLike._(this.name);

  static const red = EnumLike._('red');
  static const green = EnumLike._('green');
  static const blue = EnumLike._('blue');
}

void bad(EnumLike e) {
  switch (e) {
    // LINT
    case EnumLike.red:
      // red case
      print(e.name); // red;
      break;
    case EnumLike.green:
      // green case
      print(e.name); // green;
      break;
  }
}

Instance変数をそのまま文字列化したい場合には、enum を採用して describeEnum するのが素直です。上記の方法でも、static 変数とコンストラクターパラメーターで記述が重複しているのが少し気になりますが、2 つの距離が近いので許容範囲内の冗長さでしょう。

(ちなみに、exhaustive_cases lint のページに に記載されているサンプルコードはコンストラクターの文法違反でコンパイルエラーです)

Class ですので、もちろん final instance 変数や method を追加していくことができます。enum に extension をつける手法よりもイディオマティックになり、class の表現力を得られます。

また、describeEnumと比較して、処理負荷が下がります。気にする必要もない水準ですが。

まとめ

導入しても不都合なことはないので、無条件に導入しておきたい Lint です。

Dart 言語にパターンマッチングの仕様策定が進行中ですし、現状でも現行の仕様のもとenumのインターフェース実装で疑似 Union Type を作ってswitch文で使えば、やや冗長ながらもさらに表現力が増します。実装が単純で明白なので、私はその手法を好みます。Dart の基本的な言語機能のみを使用してパターンマッチングもどきを実装し、静的に網羅的な検査をする手法 - Qiita

それも不満ならば、package:freezed などのパッケージの採用を検討すればよいのではないでしょうか。

25
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
11