Dart / Flutter 知ったこと雑多

Extension methods
イメージとして、 Vue.js の mixins とか、 PHP の Traits のような、継承ではない機能拡張。
ただ、上二つは横串での拡張のため、「実際何ができるか」が対象のクラスでわかりづらい気がする。
Extension methods は、既存のクラスに対してぶら下がる感じで宣言する。
標準のクラスにないけどアプリ内で共通で使いたい便利メソッド(フォーマットを整えるとか)を実装すると、ロジックを集約できて良さそう。
既存のコードを見る場合は標準に無いメソッドが急に現れることになるので、落ち着いてコードを辿ること。

Extension types
Extension methods を調べてて出てきたので、一緒にメモ。
プリミティブ型などを拡張して、新たな型に再定義する。
ただ、これは "wraps" しているだけで、クラスではない。
基本は int
なんだけど四則演算させたいものではない(IDとかコードとか)、について、わざわざクラスを作らずにうまく対応したい場合に使える。
あくまで実装時のラップなので、コンパイル時には展開される、のかな。
つまり、実行時はオブジェクトサイズが増えない、速度への影響が少ない、とかの利点があるから、こういうことができるのかな。
という想像。

static メソッドの override ができない
こういうことができない。
abstract class BaseModel {
static bool match(int typeNumber) => false;
}
class ModelA extends BaseModel {
static bool match(int typeNumber) => typeNumber == 1;
// ここでエラーになる
}
class ModelFactory {
static create(int typeNumber) {
// 注意: これは実装できない例!
// 判定時はインスタンス化しない
if (ModelA.match(1)) {
// 必要なときだけインスタンス化
return ModelA();
}
throw UnimplementedError();
}
}
モデルをインスタンス化するかどうかの判定をモデル自身に持たせたいなぁ、インスタンス化前だから static でやりたいなぁ、と思ったけど、そういう実装はできなそう。

Records 型
「値の組み合わせ」を無名の型として利用できる。
メソッドの戻り値として複数の値を使いたいが、クラス定義するほど汎用性も必要ない、、というときは便利。
利用箇所が増えると各所の型定義が煩雑になるので、その時は素直にクラス定義しよう。
使い勝手はいわゆる tuple に近いイメージな気がするが、自分は tuple を正確に捉えていない気がする。
Java の Record クラスは別物なので、 Java 出身者は混同しないように注意が必要。
手軽に行(値の組み合わせ)を表す意味では近しいが、 Java の方は DAO の側面が大きいイメージがある。
Dart / Flutter で循環的複雑度を調べたかったら
DCM
外部コマンド型。
登録必要だが、無料でも使えそう。
Dart Code Linter
パッケージ型。
簡単に導入・実行できる。
DCMとルールについては遜色なさそう?
Metrics も含めて、基本的にはこちらを運用した方が良いかも。
パッケージなのでチームメンバーも使える、CI/CDでも使いやすい。

TypeScript の Union みたいなのはできない (validation的なニュアンスで)
文字列と数値、みたいなものというより、取りうる値を「数値型の文字」みたいな制限を型で表現できたら嬉しいなぁ、というお気持ち。
Zod的なライブラリはありそう。
独自の型定義は Typedefs がある。Alias的なもの。
ここで、なんやかんやの制限を組み込めるなら、使用時点でエラーになるような型が作れる、かも?

freezed + json_anotation で特定のパラメータの変換を実装したいとき
freezed で JSON 変換周りは楽ができるけど、変わった日付フォーマットだった場合などに独自で変換処理を実装したい時、あるよね。
こういう書き方で良いらしい。
abstract class Weather with _$Weather {
factory Weather({
(name: 'Type') required String type,
(name: 'Date') () required DateTime date,
(name: 'Rainfall') required double rainfall,
}) = _Weather;
factory Weather.fromJson(Map<String, dynamic> json) =>
_$WeatherFromJson(json);
}
class DateTimeConverter implements JsonConverter<DateTime, String> {
const DateTimeConverter();
DateTime fromJson(String json) {
final dateStr = json.substring(0, 8);
final timeStr = json.substring(8, 12);
return DateTime.parse('${dateStr}T${timeStr}00').toLocal();
}
String toJson(DateTime object) {
initializeDateFormatting('ja_JP', '');
var formatter = DateFormat("yyyyMMddHHmm", 'ja_JP');
return formatter.format(object);
}
}
これは、 Yahoo! の天気予報API の日時に「秒」が無いフォーマットなので、それを対処する感じの書き方。

環境変数で実行環境切り分けしたい
--dart-define-from-file=config/dev.json
みたいにする。
ファイルは env
or json
どちらか。
使う側は const
で変数定義してやる必要がある。
これは、 const
がビルド時に評価されるため。 (ファイルから読み込んで環境変数化するので、ビルド時の評価が必要。)
const param = String.fromEnvironment('someParamKey');
SomeClass({
param: const String.fromEnvironment('someParamKey');
});

alchemist
E2Eテスト用のライブラリ。
CI/CD で使えるようにできそうなので、試しておきたい。