Flutter/Dartアプリケーションをレビューするための包括的なライブラリに依存しないチェックリスト。これらの原則は、どの状態管理ソリューション、ルーティングライブラリ、またはDIフレームワークを使用していても適用されます。
pubspec.yamlが整理されている — 未使用の依存関係がなく、バージョンが適切に固定されているanalysis_options.yamlに厳格なリントセットと厳格なアナライザー設定が含まれているprint()文がない — dart:developerのlog()またはロギングパッケージを使用.g.dart、.freezed.dart、.gr.dart)が最新か.gitignoreに含まれているdynamicにつながる — strict-casts、strict-inference、strict-raw-typesを有効にするif (value case var v?))の代わりに過度な!(bang演算子)this.fieldを使用on句なしのcatch (e); 常に例外型を指定するErrorのキャッチ: Errorのサブタイプはバグを示し、キャッチすべきでないasync: awaitしないasyncマークされた関数 — 不要なオーバーヘッドlateの過剰使用: nullable型やコンストラクターの初期化がより安全な場所でのlateの使用; エラーをランタイムに先送りにする+の代わりにStringBufferを使用constコンテキストでの可変状態: constコンストラクタークラスのフィールドは可変であるべきでないFutureの戻り値の無視: 意図を示すためにawaitを使用するか明示的にunawaited()を呼び出すfinalが使える場所でのvar: ローカル変数にはfinalを、コンパイル時定数にはconstを優先package:インポートを使用List/Mapではなく変更不可能なビューを返すべきisチェックと手動キャストの代わりにswitch式とif-caseを優先(String, int)を使用print(): dart:developerのlog()またはプロジェクトのロギングパッケージを使用; print()はログレベルがなくフィルタリングできないbuild()メソッドが約80-100行を超える単一ウィジェットがない_build*()ヘルパーメソッドが別のウィジェットクラスに抽出されている(要素の再利用、const伝播、フレームワーク最適化を可能にする)constコンストラクターを可能な限り使用 — 不要な再構築を防ぐconstリテラルを使用(const []、const {})constとして宣言されているValueKeyをリスト/グリッドで使用GlobalKeyは控えめに使用 — ツリー全体の状態アクセスが本当に必要な場合のみUniqueKeyをbuild()内で使用しない — フレームごとに再構築を強制するObjectKeyを使用Theme.of(context).colorSchemeから取得 — Colors.redやhex値のハードコードなしTheme.of(context).textThemeから取得 — 生のフォントサイズのインラインTextStyleなしbuild()内にネットワーク呼び出し、ファイルI/O、または重い計算がないbuild()内にFuture.then()またはasync作業がないbuild()内にサブスクリプション作成(.listen())がないsetState()が可能な限り小さいサブツリーに限定されているこれらの原則はすべてのFlutter状態管理ソリューション(BLoC、Riverpod、Provider、GetX、MobX、Signals、ValueNotifier など)に適用されます。
ref.watchを通じて他のプロバイダーに依存することは予期されている — 循環または過度に絡み合ったチェーンのみフラグを立てるcopyWith()またはコンストラクターで新しいインスタンスを作成==とhashCodeを適切に実装(すべてのフィールドが比較に含まれる)Equatable、freezed、Dartレコード、またはその他List/Mapとして公開されていない@action、signalでの.value、GetXでの.obs)を通じてのみ変異される — 直接フィールド変異は変更追跡をバイパスするReactionDisposer、Signalsでのeffectクリーンアップ)AsyncValue)を使用 — ブールフラグ(isLoading、isError、hasData)は使わない// 悪い例 — ブールフラグの混乱が不可能な状態を許可する
class UserState {
bool isLoading = false;
bool hasError = false; // isLoading && hasErrorが表現可能!
User? user;
}
// 良い例(イミュータブルアプローチ) — sealed型が不可能な状態を表現不可能にする
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
}
class UserError extends UserState {
final String message;
const UserError(this.message);
}
// 良い例(リアクティブアプローチ) — observableのenum + データ、リアクティビティAPIを通じた変異
// enum UserStatus { initial, loading, loaded, error }
// ソリューションのobservable/signalを使用してstatusとdataを別々にラップする
constウィジェットを使用.listen())がdispose() / close()でキャンセルされる.listen()よりも宣言的ビルダー)setState前にmountedチェックawait後にBuildContextをcontext.mountedをチェックせずに使用しない(Flutter 3.7+) — 古いコンテキストはクラッシュを引き起こすBuildContextをシングルトン、状態マネージャー、または静的フィールドに保存しないsetState、ValueNotifier)を使用setState()がルートウィジェットレベルで呼び出されない — 状態変更をローカル化するconstウィジェットが再構築の伝播を止めるために使用されるRepaintBoundaryが独立して再描画する複雑なサブツリーの周りに使用されるAnimatedBuilderのchildパラメーターがアニメーションから独立したサブツリーに使用されるbuild()内で大きなコレクションのソート、フィルタリング、マッピングがない — 状態管理レイヤーで計算するbuild()内でregexのコンパイルがないMediaQuery.of(context)の使用が具体的(例: MediaQuery.sizeOf(context))Image.assetとcacheWidth/cacheHeightを使用して表示サイズでデコードするListView(children: [...])の代わりにListView.builder / GridView.builderを使用(小さくて静的なリストにはコンクリートコンストラクターが適切)deferred as)を使用Opacityウィジェットを避ける — AnimatedOpacityまたはFadeTransitionを使用operator ==をオーバーライドしない — 代わりにconstコンストラクターを使用IntrinsicHeight、IntrinsicWidth)を控えめに使用(追加のレイアウトパス)pumpWidgetとpumpが非同期操作に対して正しく使用されているfind.byType、find.text、find.byKeyが適切に使用されているpumpAndSettleまたは明示的なpump(Duration)を使用Semanticsウィジェットを使用ExcludeSemanticsを使用MergeSemanticsを使用semanticLabelプロパティが設定されているonPressedコールバックがない — すべてのボタンが何かをするか無効化されているSafeAreaウィジェットで処理されているAndroidManifest.xmlとInfo.plistで宣言されているLayoutBuilderまたはMediaQueryを使用Flexible、Expanded、FittedBoxを使用--dart-define、VCSから除外された.envファイル、またはコンパイル時設定を使用.gitignoreを確認^1.2.3)を使用 — 互換性のある更新を許可flutter pub outdatedを実行pubspec.yamlでは依存関係のオーバーライドなし — コメント/問題リンク付きの一時的な修正のみpackage:other/src/internal.dartなし(Dartパッケージのカプセル化を壊す)path: ../../相対文字列でないanalysis_options.yamlを共有または継承するNavigator.pushの混在なしMap<String, dynamic>やObject?キャストなしFlutterError.onErrorがフレームワークエラー(ビルド、レイアウト、描画)をキャプチャするためにオーバーライドされているPlatformDispatcher.instance.onErrorがFlutterにキャッチされない非同期エラー用に設定されているErrorWidget.builderがリリースモードのためにカスタマイズされている(赤い画面の代わりにユーザーフレンドリー)runAppの周りにグローバルエラーキャプチャラッパー(例: runZonedGuarded、Sentry/Crashlyticsラッパー)ifチェックではないanalysis_options.yamlが厳格な設定を有効にして存在するstrict-casts: true、strict-inference: true、strict-raw-types: true
// ignore:)が理由を説明するコメントで正当化されているflutter analyzeがCIで実行され、失敗がマージをブロックするprefer_const_constructors — ウィジェットツリーのパフォーマンスavoid_print — 適切なロギングを使用unawaited_futures — fire-and-forget非同期バグを防ぐprefer_final_locals — 変数レベルのイミュータビリティalways_declare_return_types — 明示的なコントラクトavoid_catches_without_on_clauses — 特定のエラー処理always_use_package_imports — 一貫したインポートスタイル以下の表は普遍的な原則を人気のソリューションでの実装にマッピングしています。プロジェクトが使用するソリューションにレビュールールを適応させるために使用してください。
| 原則 | BLoC/Cubit | Riverpod | Provider | GetX | MobX | Signals | ビルトイン |
|---|---|---|---|---|---|---|---|
| 状態コンテナ | Bloc/Cubit |
Notifier/AsyncNotifier |
ChangeNotifier |
GetxController |
Store |
signal() |
StatefulWidget |
| UIコンシューマー | BlocBuilder |
ConsumerWidget |
Consumer |
Obx/GetBuilder |
Observer |
Watch |
setState |
| セレクター | BlocSelector/buildWhen |
ref.watch(p.select(...)) |
Selector |
N/A | computed | computed() |
N/A |
| 副作用 | BlocListener |
ref.listen |
Consumerコールバック |
ever()/once() |
reaction |
effect() |
コールバック |
| 廃棄 | BlocProviderで自動 |
.autoDispose |
Providerで自動 |
onClose() |
ReactionDisposer |
手動 | dispose() |
| テスト | blocTest() |
ProviderContainer |
ChangeNotifierを直接 |
テストでGet.put |
ストアを直接 | signalを直接 | ウィジェットテスト |