Communitygithub.com

Flutter/Dart コードレビューのベストプラクティス

ライブラリ非依存のFlutter/Dartコードレビューチェックリスト。ウィジェットのベストプラクティス、状態管理パターン(BLoC、Riverpod、Provider、GetX、MobX、Signals)、Dartのイディオム、パフォーマンス、アクセシビリティ、セキュリティ、クリーンアーキテクチャをカバーしています。FlutterまたはDartコードをレビューする際に、プロジェクトで使用されている状態管理ライブラリに関係なく使用できます。

Dart コードレビューのベストプラクティス とは?

このスキルは、Flutter/Dartアプリケーションのコードレビューを効率的に行うための包括的なチェックリストを提供します。ウィジェットの分解やconstコンストラクタの使用といった基本的なベストプラクティスから、BLoC、Riverpod、Provider、GetX、MobX、Signalsなど、あらゆる状態管理ライブラリに適用できるアーキテクチャ上の原則までを網羅しています。このスキルの主な目的は、状態管理ライブラリの違いを吸収し、イミュータビリティやリアクティビティ、ビルド最適化といった普遍的な設計原則に焦点を当てることです。また、Dart言語の落とし穴(暗黙的なdynamicやnull安全の誤用)、パフォーマンス、テスト、アクセシビリティ、セキュリティ、プラットフォーム固有の問題もカバーしています。各チェック項目は具体的であり、「BAD/GOOD」のコード例を使って理解を助け、どのFlutterプロジェクトのレビューでも一貫した品質基準を保証します。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/flutter-dart-code-review

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Dart コードレビューのベストプラクティス は何をしますか?

Flutter/Dart アプリケーションをレビューするための、ライブラリに依存しない包括的なチェックリストです。これらの原則は、使用される状態管理ソリューション、ルーティングライブラリ、DIフレームワークに関係なく適用されます。


1. プロジェクト全体の健全性

  • プロジェクトは一貫したフォルダ構造(機能優先またはレイヤー優先)に従っている
  • 関心事の適切な分離:UI、ビジネスロジック、データ層
  • ウィジェット内にビジネスロジックがない;ウィジェットは純粋にプレゼンテーション用である
  • pubspec.yaml がクリーン — 未使用の依存関係がなく、バージョンが適切に固定されている
  • analysis_options.yaml に、厳格なlintセットと厳格なアナライザー設定が有効になっている
  • プロダクションコード内に print() 文がない — dart:developer の log() またはロギングパッケージを使用する
  • 生成ファイル(.g.dart、.freezed.dart、.gr.dart)が最新であるか、.gitignore に含まれている
  • プラットフォーム固有のコードは抽象化の背後に隔離されている

2. Dart言語の落とし穴

  • 暗黙的なdynamic:型アノテーションが欠落して dynamic になっている — strict-casts、strict-inference、strict-raw-types を有効にする
  • Null安全の誤用:適切なnullチェックやDart 3のパターンマッチング(if (value case var v?))の代わりに過剰な !(bang演算子)を使用している
  • 型昇格の失敗:ローカル変数の昇格が機能する場所で this.field を使用している
  • 過度に広範なキャッチ:on 句なしの catch (e) — 常に例外の種類を指定する
  • Error のキャッチ:Error のサブタイプはバグを示しており、キャッチすべきではない
  • 未使用の async:await を決して行わない async とマークされた関数 — 不必要なオーバーヘッド
  • late の過剰使用:null許容型やコンストラクター初期化の方が安全な場所で late を使用している;エラーを実行時まで遅延させる
  • ループ内での文字列連結:反復的な文字列構築には + の代わりに StringBuffer を使用する
  • const コンテキストでの可変状態:const コンストラクタークラスのフィールドは可変であってはならない
  • Future の戻り値の無視:await を使用するか、明示的に unawaited() を呼び出して意図を示す
  • final で十分な場所での var:ローカル変数には final を、コンパイル時定数には const を優先する
  • 相対インポート:一貫性のために package: インポートを使用する
  • 可変コレクションの公開:公開APIは生の List/Map ではなく、変更不可能なビューを返すべき
  • Dart 3のパターンマッチングの欠如:冗長な is チェックや手動キャストよりも、switch式と if-case を優先する
  • 複数の戻り値のための使い捨てクラス:単一用途のDTOの代わりにDart 3のレコード (String, int) を使用する
  • プロダクションコードでの print():dart:developer の log() またはプロジェクトのロギングパッケージを使用する;print() にはログレベルがなく、フィルタリングできない

3. ウィジェットのベストプラクティス

ウィジェットの分解:

  • 単一のウィジェットで build() メソッドが約80〜100行を超えていない
  • ウィジェットはカプセル化と、それらがどのように変化するか(リビルド境界)の両方で分割されている
  • ウィジェットを返すプライベートな _build*() ヘルパーメソッドは、個別のウィジェットクラスに抽出されている(要素の再利用、const伝播、およびフレームワークの最適化を可能にする)
  • 可変なローカル状態が必要ない場合は、StatefulウィジェットよりもStatelessウィジェットが優先されている
  • 抽出されたウィジェットは、再利用可能な場合は別のファイルにある

constの使用法:

  • 可能な限り const コンストラクターが使用されている — 不必要なリビルドを防ぐ
  • 変更されないコレクションには const リテラル(const []、const {})
  • すべてのフィールドがfinalの場合、コンストラクターは const と宣言されている

Keyの使用法:

  • リスト/グリッドで再並び替え全体にわたって状態を保持するために ValueKey が使用されている
  • GlobalKey は控えめに使用されている — ツリー全体で状態にアクセスする必要がある場合にのみ
  • build() 内での UniqueKey の使用は避けられている — 毎フレームのリビルドを強制する
  • 同一性が単一の値ではなくデータオブジェクトに基づく場合に ObjectKey が使用されている

テーマとデザインシステム:

  • 色は Theme.of(context).colorScheme から取得 — ハードコードされた Colors.red や16進数値は使用しない
  • テキストスタイルは Theme.of(context).textTheme から取得 — 生のフォントサイズを持つインラインの TextStyle は使用しない
  • ダークモードの互換性が確認されている — 明るい背景を想定していない
  • スペーシングとサイズは、マジックナンバーではなく、一貫性のあるデザイントークンまたは定数を使用している

Buildメソッドの複雑さ:

  • build() 内にネットワーク呼び出し、ファイルI/O、または重い計算がない
  • build() 内に Future.then() または async 処理がない
  • build() 内でのサブスクリプション作成(.listen())がない
  • setState() は可能な限り小さなサブツリーにローカライズされている

4. 状態管理(ライブラリ非依存)

これらの原則は、すべてのFlutter状態管理ソリューション(BLoC、Riverpod、Provider、GetX、MobX、Signals、ValueNotifierなど)に適用されます。

アーキテクチャ:

  • ビジネスロジックはウィジェット層の外側 — 状態管理コンポーネント(BLoC、Notifier、Controller、Store、ViewModelなど)にある
  • 状態マネージャーは依存関係をインジェクションで受け取り、内部で構築しない
  • サービスまたはリポジトリ層がデータソースを抽象化する — ウィジェットと状態マネージャーはAPIやデータベースを直接呼び出すべきではない
  • 状態マネージャーは単一責任を持つ — 無関係な関心事を扱う「神」マネージャーは存在しない
  • クロスコンポーネントの依存関係は、そのソリューションの規約に従っている:
    • Riverpodの場合:ref.watch を介したプロバイダーの依存は想定内 — 循環的または過度に絡み合ったチェーンのみフラグを立てる
    • BLoCの場合:blocは他のblocに直接依存すべきではない — 共有リポジトリまたはプレゼンテーション層の調整を優先する
    • その他のソリューションの場合:コンポーネント間通信のための文書化された規約に従う

イミュータビリティと値の等価性(不変状態ソリューションの場合:BLoC、Riverpod、Redux):

  • 状態オブジェクトは不変である — copyWith() またはコンストラクターを介して新しいインスタンスが作成され、インプレースで変更されることはない
  • 状態クラスは == と hashCode を適切に実装している(比較にすべてのフィールドが含まれている)
  • メカニズムはプロジェクト全体で一貫している — 手動オーバーライド、Equatable、freezed、Dartレコード、またはその他
  • 状態オブジェクト内のコレクションは、生の可変 List/Map として公開されていない

リアクティビティの規律(リアクティブミューテーションソリューションの場合:MobX、GetX、Signals):

  • 状態はそのソリューションのリアクティブAPI(MobXの @action、Signalsの .value、GetXの .obs)を介してのみ変更される — 直接のフィールド変更は変更追跡をバイパスする
  • 派生値は、冗長に保存するのではなく、そのソリューションの計算メカニズムを使用する
  • リアクションとディスポーザーは適切にクリーンアップされている(MobXの ReactionDisposer、Signalsのエフェクトクリーンアップ)

状態形状の設計:

  • 相互に排他的な状態は、シールド型、共用体バリアント、またはそのソリューションの組み込みの非同期状態型(例:Riverpodの AsyncValue)を使用する — ブールフラグ(isLoading、isError、hasData)は使用しない
  • すべての非同期操作は、読み込み、成功、エラーを明確な状態としてモデル化する
  • すべての状態バリアントはUIで網羅的に処理されている — 黙って無視されるケースはない
  • エラー状態には表示用のエラー情報が含まれている;読み込み状態には古いデータが含まれていない
  • null許容データはローディングインジケーターとして使用されていない — 状態は明示的である
// BAD — ブール値フラグの混在により不可能な状態が表現可能になる
class UserState {
  bool isLoading = false;
  bool hasError = false; // isLoading && hasError が表現可能!
  User? user;
}

// GOOD(不変アプローチ) — シールド型により不可能な状態が表現不可能になる
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);
}

// GOOD(リアクティブアプローチ) — 観測可能なenum + データ、リアクティビティAPIを介したミューテーション
// enum UserStatus { initial, loading, loaded, error }
// ソリューションのobservable/signalを使用して、ステータスとデータを個別にラップする

リビルドの最適化:

  • 状態コンシューマーウィジェット(Builder、Consumer、Observer、Obx、Watchなど)は可能な限り狭いスコープにする
  • すべての状態出力ではなく、特定のフィールドが変更された場合にのみリビルドするためにセレクターを使用する
  • const ウィジェットを使用して、ツリーを介したリビルドの伝播を停止する
  • 計算/派生状態はリアクティブに計算され、冗長に保存されない

サブスクリプションと破棄:

  • すべての手動サブスクリプション(.listen())は dispose() / close() でキャンセルされている
  • ストリームコントローラーは不要になったらクローズされている
  • タイマーは破棄ライフサイクルでキャンセルされている
  • フレームワーク管理のライフサイクルが手動サブスクリプションよりも優先されている(.listen() よりも宣言的ビルダー)
  • 非同期コールバック内の setState の前に mounted チェックが行われている
  • context.mounted(Flutter 3.7+)をチェックせずに await の後に BuildContext を使用していない — 古いコンテキストはクラッシュを引き起こす
  • 非同期ギャップの後、ウィジェットがまだマウントされていることを確認せずに、ナビゲーション、ダイアログ、またはスキャフォールドメッセージがない
  • BuildContext はシングルトン、状態マネージャー、または静的フィールドに保存されない

ローカル状態とグローバル状態:

  • 一時的なUI状態(チェックボックス、スライダー、アニメーション)はローカル状態(setState、ValueNotifier)を使用する
  • 共有状態は必要な範囲でのみ持ち上げられる — 過度にグローバル化されない
  • 機能スコープの状態は、その機能がアクティブでなくなったら適切に破棄される

5. パフォーマンス

不必要なリビルド:

  • setState() がルートウィジェットレベルで呼び出されていない — 状態変更をローカライズする
  • const ウィジェットを使用してリビルドの伝播を停止する
  • 独立して再描画する複雑なサブツリーの周りで RepaintBoundary が使用されている
  • アニメーションから独立したサブツリーに AnimatedBuilder の child パラメーターが使用されている

build() 内の高価な操作:

  • build() 内で大きなコレクションのソート、フィルタリング、マッピングがない — 状態管理層で計算する
  • build() 内での正規表現のコンパイルがない
  • MediaQuery.of(context) の使用は具体的である(例:MediaQuery.sizeOf(context))

画像最適化:

  • ネットワーク画像はキャッシュを使用している(プロジェクトに適した任意のキャッシュソリューション)
  • ターゲットデバイスに適切な画像解像度(サムネイルに4K画像をロードしない)
  • Image.asset を cacheWidth/cacheHeight と共に使用して表示サイズでデコードする
  • ネットワーク画像にプレースホルダーとエラーウィジェットが提供されている

遅延読み込み:

  • 大きなリストや動的なリストには ListView(children: [...]) の代わりに ListView.builder / GridView.builder が使用されている(具体的なコンストラクターは小さく静的なリストには問題ない)
  • 大規模なデータセットに対してページネーションが実装されている
  • Webビルドで重いライブラリに対して遅延読み込み(deferred as)が使用されている

その他:

  • アニメーションでの Opacity ウィジェットの使用は避ける — AnimatedOpacity または FadeTransition を使用する
  • アニメーションでのクリッピングは避ける — 画像を事前にクリップする
  • ウィジェットでの operator == のオーバーライドは行わない — 代わりに const コンストラクターを使用する
  • 内在的な寸法ウィジェット(IntrinsicHeight、IntrinsicWidth)は控えめに使用する(余分なレイアウトパス)

6. テスト

テストの種類と期待事項:

  • 単体テスト:すべてのビジネスロジック(状態マネージャー、リポジトリ、ユーティリティ関数)をカバーする
  • ウィジェットテスト:個々のウィジェットの動作、インタラクション、および視覚的出力をカバーする
  • 統合テスト:重要なユーザーフローをエンドツーエンドでカバーする
  • ゴールデンテスト:デザイン上重要なUIコンポーネントのピクセルパーフェクトな比較

カバレッジ目標:

  • ビジネスロジックの行カバレッジ80%以上を目指す
  • すべての状態遷移に対応するテストがある(読み込み → 成功、読み込み → エラー、リトライなど)
  • エッジケースのテスト:空の状態、エラー状態、読み込み状態、境界値

テストの分離:

  • 外部依存関係(APIクライアント、データベース、サービス)はモックまたはフェイクされている
  • 各テストファイルは正確に1つのクラス/ユニットをテストする
  • テストは実装の詳細ではなく、動作を検証する
  • スタブは各テストに必要な動作のみを定義する(最小限のスタブ)
  • テストケース間で可変な共有状態がない

ウィジェットテストの品質:

  • pumpWidget と pump が非同期操作に正しく使用されている
  • find.byType、find.text、find.byKey が適切に使用されている
  • タイミングに依存する不安定なテストがない — pumpAndSettle または明示的な pump(Duration) を使用する
  • テストはCIで実行され、失敗はマージをブロックする

7. アクセシビリティ

セマンティックウィジェット:

  • 自動ラベルが不十分な場合にスクリーンリーダーラベルを提供するために Semantics ウィジェットが使用されている
  • 純粋に装飾的な要素には ExcludeSemantics が使用されている
  • 関連するウィジェットを単一のアクセス可能な要素に結合するために MergeSemantics が使用されている
  • 画像には semanticLabel プロパティが設定されている

スクリーンリーダーサポート:

  • すべてのインタラクティブ要素はフォーカス可能で、意味のある説明がある
  • フォーカス順序は論理的である(視覚的な読み取り順序に従う)

視覚的なアクセシビリティ:

  • テキストと背景のコントラスト比が4.5:1以上である
  • タップ可能なターゲットは最低48x48ピクセルである
  • 色は状態の唯一のインジケーターではない(アイコン/テキストも併用する)
  • テキストはシステムのフォントサイズ設定に応じて拡大縮小する

インタラクションのアクセシビリティ:

  • 何もしない onPressed コールバックがない — すべてのボタンは何かを行うか、無効化されている
  • エラーフィールドは修正を提案する
  • ユーザーがデータを入力中にコンテキストが予期せず変わらない

8. プラットフォーム固有の懸念事項

iOS/Androidの違い:

  • 必要に応じてプラットフォームアダプティブウィジェットが使用されている
  • 戻るナビゲーションが正しく処理されている(Androidの戻るボタン、iOSのスワイプで戻る)
  • ステータスバーとセーフエリアは SafeArea ウィジェットを介して処理されている
  • プラットフォーム固有のパーミッションが AndroidManifest.xml と Info.plist で宣言されている

レスポンシブデザイン:

  • レスポンシブレイアウトに LayoutBuilder または MediaQuery が使用されている
  • ブレークポイントが一貫して定義されている(電話、タブレット、デスクトップ)
  • 小さな画面でテキストがオーバーフローしない — Flexible、Expanded、FittedBox を使用する
  • 横向きがテストされているか、明示的にロックされている
  • Web固有:マウス/キーボードのインタラクションがサポートされ、ホバー状態が存在する

9. セキュリティ

セキュアストレージ:

  • センシティブなデータは flutter_secure_storage またはキーチェーンに保存され、共有プリファレンスには保存されない
  • 認証トークンはメモリ内にのみ保持され、ディスクに永続化されるのは適切に暗号化されている場合のみ
  • ディープリンクは検証され、認証チェックをバイパスするために使用されない

API通信:

  • すべてのAPI呼び出しはHTTPSを使用する
  • 証明書のピン留めが高セキュリティ環境で検討されている
  • ログに機密データ(パスワード、トークン)が含まれていない

入力検証:

  • すべてのユーザー入力はバックエンドに送信される前にクライアント側で検証される
  • 出力はXSS脆弱性を防ぐためにエスケープされる

10. クリーンアーキテクチャと依存関係

レイヤーと依存関係のルール:

  • データ層はドメイン層にのみ依存し、プレゼンテーション層には依存しない
  • ドメイン層は外部ライブラリやフレームワークに依存しない — 純粋なDart
  • 依存関係の逆転:上位レベルのモジュールは下位レベルのモジュールに依存せず、抽象化に依存する
  • 具象実装はコンパイル時または実行時の依存性注入を介して提供される

依存性注入:

  • サービスロケーターパターン(静的ゲッター)の代わりに、適切な依存性注入(get_it、Riverpod、Provider、手動コンストラクターDI)が使用されている
  • DIコンテナの登録はアプリケーションの起動時に1回行われる
  • テストでは、実装の代わりにモックでDIコンテナをオーバーライドする機能が確認されている

このチェックリストをFlutter/Dartコードレビューのプロセスに統合してください。常にコンテキストと判断を適用し、これらのチェック項目はすべての状況に適用されるわけではありません。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

関連スキル