【Flutter・Dart】metadata annotationについて解説

Flutter

こんにちは、教育系エンジニアのひらまつ(@hiramatsuu)です。

ひらまつの簡単な自己紹介

書籍「ゼロからわかる Linuxコマンド200本ノック(技術評論社)」の著者。Udemy受講者8万人。
プログラミング教育をメインに活動するエンジニアとして、動画教材の作成・技術書の執筆・学習アプリの開発などを行なっています(詳しくはこちら)。

本記事では、Dart・Flutterにおける、メタデータアノテーション(metadata annotation)という機能について解説していきます。

メタデータアノテーションを曖昧な理解で使っているという方は、ぜひ読んでみてください。

metadata annotationとは

  • metadata annotation(メタデータアノテーション)を使うことで、コードに関する追加の情報(metadata)を与えることができるようになる。
  • annotation(アノテーション)は、「注釈」の意味。コードにつける注釈がアノテーション。
  • コメントと比較すると、アノテーションとは何かが理解しやすい。すなわち、コメントは「人間」への情報を付加する場所であるのに対して、アノテーションはコンパイラや静的解析ツールなど、「ツール」への情報を付加する場所1
  • メタデータアノテーションは「@」から始まり、後にはコンパイル時定数(compile-time constant)constantコンストラクタ(constant constructor)が続く。
  • 例えば、@Deprecatedアノテーションを廃止予定・非推奨のクラスにつけることで、それを静的解析ツール(Dart analyzer2が解釈して、ユーザーがそのクラスを利用した時に警告を出せるようになる。より詳しくは後述。
  • Dartにおいてアノテーションは、メタデータアノテーションだけでなく、type annotation(型アノテーション)などもあるが、共通するのはツールへ情報を与える目的で使用されているということ
  • 型アノテーションは、以下のように、変数などの型を指定するもの。型アノテーションをつけることで、静的解析ツールに型を伝えることができるので、静的解析ツールのサポートを受けやすくなる。Dartでは常に、型アノテーションをつける必要がある3
int foo = 10; // 「var foo = 10」としない
final Bar bar = Bar();
String baz = 'hello';
const int quux = 20;

dart:coreに含まれる4つのメタデータアノテーション

dart:coreライブラリに含まれるメタデータアノテーションとしては、以下の4つがある。(「dart:coreライブラリに含まれる」ということは、言い換えれば「インポート不要で、あらゆるDartコードで使える」ということ。4

@Deprecated / @deprecated

  • どちらも、機能が廃止予定・非推奨(deprecated)であることを示すアノテーション。@Deprecated / @deprecatedのいずれかのアノテーションがついた機能を、ユーザーが使用すると、警告が表示されるようになる。
  • 廃止予定の機能を使用している人に、「現在はうまく動いていても、将来的にはうまく動作しなくなるため、使用をやめる必要がある」ことを知らせる役割を担う。
  • 「廃止予定の機能」と書いたが「機能」のサイズは、ライブラリ全体でも良いし、1つのパラメータでも良い。APIの任意の部分を、deprecatedとして指定できる。
  • @Deprecatedと@deprecatedの違いは、メッセージを付加できるかどうか。@Deprecatedを使えば、いつ廃止されるのか?どのように対応すれば良いのか?などの付加情報を伝えることができるが、@deprecatedでは、機能が次のリリースで廃止されるという情報以上を伝えることは出来ない。
  • 言い換えれば、@deprecatedアノテーションは、次のリリースまでの間、移行の指示なしで非推奨にするためのアノテーション。
  • なので、追加のメッセージが必要ないなら、@Deprecatedではなく@deprecatedを使う、という方針になるが、基本的には常に@Deprecatedでメッセージをつけることが推奨されている
  • このアノテーションは、アノテーションをつけた機能の「部分」にも遷移的に適用される。つまりは、以下のようになる。
    • ライブラリが廃止予定ならば、ライブラリに含まれるすべての機能も廃止予定。
    • クラスが廃止予定なら、そのクラスのすべてのメンバーも廃止予定。
    • 変数が廃止予定なら、暗黙的なgetterとsetterも廃止予定。
  • 注意点としては、スーパークラスのある機能が非推奨でも、サブクラスの該当機能も必ず非推奨になるわけではない。なので、廃止予定のスーパークラスから、サブクラスへメンバーを移すことは妥当な選択。あるサブクラスでは依然として、その機能が重要な機能であるかもしれない。
  • また、ユーザーが使用している非推奨の機能の内部で、非推奨の機能が使われている場合は、内部の非推奨の情報を、ユーザーに通知する必要はない。非推奨の機能の内部で、別の非推奨の機能が使われることはよくある。
  • 実際のコードでも確認してみる。次のTelevisionクラスのactivateメソッドにアクセスすると、警告が表示される。@Deprecated(‘Use turnOn instead’)と、コンストラクタで指定されている通り、ユーザーは、turnOnメソッドを代わりに利用する必要がある。
class Television {
  /// Use [turnOn] to turn the power on instead.
  @Deprecated('Use turnOn instead')
  void activate() {
    turnOn();
  }

  /// Turns the TV's power on.
  void turnOn() {...}
  // ···
}
const Deprecated deprecated = Deprecated("next release");

@override

  • オーバーライドしたメソッドやフィールドには、必ず@overrideアノテーションをつける必要がある。これは、コードの可読性を向上させ、意図しないオーバーライドを防ぐのに役立つ。6
  • オーバーライドしていることは、サブクラスの宣言だけからは読み取れない。なので、アノテーションでオーバーライドしていることを、明示する方が良い。明示することによって、Dart analyzerの補助を受けられるようにもなり、意図通りにできていないことを警告してくれるようになる。
class Lucky extends Cat {
  final int lives = 14; // Catクラスも見ないと、オーバーライドしているかわからない
}
abstract class Dog {
  String get breed;
  void bark() {}
}

class Husky extends Dog {
  @override // オーバーライドしていることを明示する
  final String breed = 'Husky';
  @override // オーバーライドしていることを明示する
  void bark() {}
}
  • @overrideアノテーションは、インスタンスの次の要素に使用できる。
    • メソッド
    • ゲッター
    • セッター
    • 変数(フィールド)
  • インスタンス変数にアノテーションが付与されると、その変数が持つgetterとsetterにも、暗黙的にアノテーションが付与される。

@pragma

  • @pragmaアノテーションは、Dartのプログラムを扱うツールに対して、ツールの振る舞いをガイドするヒントを提供するためのアノテーション。
  • 「pragma(プラグマ)」とは、ソフトウェア開発の一般的な用語で、コンパイラ・インタプリタなどのツールに特定の情報を渡すために使用する指令のこと。7
  • 例えば、Dartにおいて、@pragma(‘vm:entry-point’)というアノテーションを、トップレベル関数につけると、「vm(Dart VM)に、entry-pointというプラグマを与える」という指示になる。このアノテーションをつけたトップレベル関数を含むファイルが、main関数を含むファイルに変わって、エントリーポイントになる。8
  • 一般的に、エントリーポイント(entrypoint)とは「プログラムなどの実行に際して、一番最初に実行される箇所」のこと9
  • Dartにおける、エントリーポイントとは、Dartの実装10によって、直接呼び出されるDartライブラリのこと。例えば、Dartライブラリを<script>タグで参照したり、スタンドアロンのDart VMにコマンドライン引数として渡したりする場合、そのライブラリがエントリーポイントになる11
  • 言い換えると、エントリーポイントは通常、main関数を含む.dartファイルのことを指す。だが、@pragma(‘vm:entry-point’)アノテーションを、別のトップレベル関数につければ、main関数のない.dartファイルを、エントリーポイント変更することもできる。
  • このように、Dart VMのようなツールに、指令を与える用途で使われるのが、pragmaであり、Dartにおける@pragmaアノテーションの役割。

dart:core以外のメタデータアノテーション

  • その他、package:metaなど、パッケージごとに様々なメタデータアノテーションが用意されている。
  • 例えば、package:metaには、@immutableアノテーションがある。
  • これは、あるクラスCと、クラスCのすべてのサブタイプが、イミュータブルである必要がある場合に、クラスCにつけるアノテーション(すべてのフィールドがfinalなら、そのクラスはイミュータブルなクラスと言える)。
  • @immutableアノテーションをつけることで、イミュータブルでない場合に、警告をアナライザーが出すようになる。

メタデータアノテーションを自作する方法

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what); // constantコンストラクタ
}
  • @Todoアノテーションとして使用するには、次のようにする。
@Todo('Dash', 'Implement this function') // コンストラクタに2つの引数を渡す
void doSomething() {
  print('Do something');
}

メタデータアノテーションが使える場所

メタデータアノテーションは、

  • ライブラリ
  • クラス
  • typedef(type alias)
  • 型パラメータ
  • コンストラクタ
  • ファクトリー
  • 関数
  • フィールド
  • パラメータ
  • 変数の宣言
  • import/exportディレクティブ

の前に置くことができる。

メタデータアノテーションの注意点

  • 自作のメタデータアノテーションも、通常のクラスと同様に、UpperCamelCaseで定義する必要がある12
  • 必ず、次のようにメタデータアノテーションの「前」に、ドキュメンテーションコメント(doc comments)を書くようにする。13
/// A button that can be flipped on and off.
@Component(selector: 'toggle')
class ToggleComponent {}

参考文献

Metadata
Metadata and annotations in Dart.

脚注

  1. https://e-words.jp/w/%E3%82%A2%E3%83%8E%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3.html ↩︎
  2. https://dart.dev/tools/analysis ↩︎
  3. https://dart.dev/tools/linter-rules/always_specify_types ↩︎
  4. https://api.dart.dev/stable/3.3.3/dart-core/dart-core-library.html ↩︎
  5. https://github.com/dart-lang/sdk/blob/main/sdk/lib/core/annotations.dart ↩︎
  6. https://dart.dev/tools/linter-rules/annotate_overrides ↩︎
  7. https://docs.oracle.com/cd/E19957-01/806-4838/Pragmas.html ↩︎
  8. https://dart.dev/tools/linter-rules/unreachable_from_main ↩︎
  9. https://e-words.jp/w/%E3%82%A8%E3%83%B3%E3%83%88%E3%83%AA%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88.html ↩︎
  10. プログラミング言語の「仕様」と「実装」の違いは、私のUdemyのコース「Python でわかる オブジェクト指向 とはなにか?【Python オブジェクト指向 の「なぜ?」を「徹底的に」解説】」などを参照。 ↩︎
  11. https://dart.dev/tools/pub/glossary#entrypoint ↩︎
  12. https://dart.dev/effective-dart/style#do-name-types-using-uppercamelcase ↩︎
  13. https://dart.dev/effective-dart/documentation#do-put-doc-comments-before-metadata-annotations ↩︎
タイトルとURLをコピーしました