naming-conventions について
Dart と Flutter 開発向けの命名規約スキルです。Effective Dart に沿って識別子、ファイル、アーキテクチャ上の役割を示す接尾辞を定め、コードの一貫性と検索性を保ちます。
主な機能
- 01Dart の型とメンバーの命名規則
- 02ファイルとディレクトリの命名規則
- 03アーキテクチャ層ごとの接尾辞
- 04インポートのグループ化と順序
- 05命名アンチパターンの確認項目
活用シーン
- 01新しい Dart 機能の名前を統一
- 02型名とアーキテクチャ上の役割を確認
- 03ファイル名とインポートを整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ zakariaf/ flutter-skills --skill naming-conventionsインストール後、Claude Code で/naming-conventionsを入力して読み込まれたか確認します。
詳細
命名規則
一貫性があり、役割のわかる名前を付けると、コードを検索しやすく、説明的にもなります。また、型の接尾辞によって、その型がどのレイヤーに属するかを示せます。これにより、レビュー担当者も、grep も、禁止インポートを検出するゲートも、名前だけでレイヤーを判別できます。このスキルが示すのは「方法」であり、規範となる「内容」は Effective Dart を参照してください。言語自体の規則に反する独自のスタイルを作ってはいけません。
絶対に守るルール
- 型名には
UpperCamelCaseを使います。 クラス、列挙型、ミックスイン、拡張、typedef、型パラメーターには、TaskScreen、OrderStatus、Predicate<T>のように付けます。一貫した形式にすることで、型を値と視覚的に区別できます。 - メンバー、変数、関数、パラメーターには
lowerCamelCaseを使います。dueDate、loadTasks()、itemCountのように付けます。これは言語のデフォルトであり、これから外れると、読み手は一瞬立ち止まることになります。 - 定数には
lowerCamelCaseを使い、SCREAMING_CAPSは使いません。const maxItemsPerPage = 50;のようにします。const MAX_ITEMS = 50ではありません。Dart は C の慣習を採用しておらず、アナライザーはconstant_identifier_namesを想定しています。 - ファイル、フォルダー、ライブラリ、インポート接頭辞には
lowercase_with_underscoresを使います。task_detail_screen.dart、features/task_detail/、import 'package:app_core/app_core.dart';のようにします。クロスプラットフォームのファイルシステムと pub でこの形式が求められます。 - ファイル名は、そのファイルの主要な宣言をスネークケースにしたものにし、1 ファイルにつき主要な公開型は 1 つにします。
TaskNotifierはtask_notifier.dartに置きます。utils.dart/helpers.dart/models.dartのような寄せ集めファイルや、utils//common//helpers//misc/のような雑多なフォルダーは作りません。シンボルを検索する読み手が、そのシンボルを所有するファイルにたどり着けるようにします。core/は、値オブジェクト、Result/Failure、Clockの境界、純粋な計算関数を置くために認められた純粋な基盤レイヤーであり、雑多な置き場ではありません。レイアウトを定義するproject-structure-and-packagesを参照してください。 - 3 文字以上の頭字語は単語と同じ形式で大文字・小文字を使い分けます。
Json、Http、Url、Apiのようにし、JsonOrder、HttpClient、fromJson、imageUrlとします。JSONOrder、HTTPClientではありません。英語で大文字表記される 2 文字の頭字語は、型名ではID、UIのように大文字のままでもかまいません。大文字・小文字が混在する頭字語は検索しづらく、一貫性もありません。 - 先頭のアンダースコアはライブラリ内限定を意味します。非公開にする意図がある場合にのみ使います。 公開シンボルの前に
_を付けて「名前空間」を作ってはいけません。別のファイルから使えなくなります。公開(_なし)であることは、文書化された契約です。dartdoc-conventionsを参照してください。 - ハンガリアン記法や型を示す名前付けは使いません。
strName、iCount、lstItems、userMap、nameString、itemsListのような名前は使いません。型は型システムがすでに把握しているため、name、usersById、itemsと書きます。 - 辞書に載っている完全な単語を使い、単位や意味も名前に含めます。
maxItemsPerPage、retryDelaySeconds、orderTotalMinorUnitsのようにします。max、delay、totalのような単独の名前は使いません。略語(opt、qty、amt)は、対応関係をコメントで示したうえで、短い純粋関数の内部に限って使います。単位を省いた名前は、単位に関するバグを招きます。 - ブール値は断定文として読めるようにします。
isLoading、hasError、canSubmit、shouldRetryのようにし、loading、error、retryは使いません。条件が文章のように読めるよう、ブール値を返すゲッターやメソッドはis/has/can/shouldで始めます。 getで始まるアクセサーは使いません。getDueTasks()ではなくdueTasksを公開します。Dart にはゲッターがあります。関数名には動詞句(loadTasks()、scheduleReminder())を使い、ブール値以外のゲッター名には名詞句(itemCount、nextDueDate)を使います。- インポートはグループ分けして並べ替えます。
dart:を最初にし、次にpackage:、最後に相対パスを配置します。各グループ内はアルファベット順に並べ、exportはインポートの後に独立したセクションとして配置します。dart formatとdirectives_orderinglint に従わせ、フォーマッターに手作業で逆らってはいけません。
接尾辞でレイヤーを示す
役割を示す接尾辞を付けると、名前がレイヤーの契約になります。ファイルを開かなくても、grep やパスを対象としたインポートゲートで、リポジトリ、ビュー、プラットフォーム境界を見分けられます。エンティティを表すトークンには、中立的なドメイン名詞(Task、Order、Account、Item)を使います。
| 役割 | 接尾辞/パターン | レイヤー | ファイル → シンボル |
|---|---|---|---|
| 画面/ルートの対象 | [Feature]Screen | UI(View) | task_list_screen.dart → TaskListScreen |
| 再利用可能なウィジェット | [Thing](曖昧な場合のみ Widget を付加) | UI | task_card.dart → TaskCard |
| ViewModel(Riverpod、デフォルト) | [Feature]Notifier | UI(ViewModel) | task_list_notifier.dart → TaskListNotifier |
| ViewModel(Provider の付録のみ) | [Feature]ViewModel | UI(ViewModel) | task_list_view_model.dart → TaskListViewModel |
| リポジトリ(インターフェース) | [Entity]Repository | data | task_repository.dart → TaskRepository |
| リポジトリ(実装) | Drift[Entity]Repository / Remote[Entity]Repository | data | drift_task_repository.dart |
| Drift DAO | [Entity]Dao | data | tasks_dao.dart → TasksDao |
| 定義する機能インターフェース | [Concern]Service | boundary | share_service.dart → ShareService |
| サービス実装(フレーバーごと) | [Provider][Concern]Service | app adapter | firebase_analytics_service.dart → FirebaseAnalyticsService |
| 特定のプラグイン/SDK/ネイティブチャネルのラッパー | [Concern]Gateway | boundary | notification_gateway.dart → NotificationGateway |
| Gateway の実装 | [Plugin][Concern]Gateway / Live[Concern]Gateway | app adapter | fln_notification_gateway.dart → FlnNotificationGateway |
| 純粋な Dart のドメインロジック | [Domain] + 動作を示す接尾辞(Calculator/Validator/Formatter) | domain | price_calculator.dart → PriceCalculator |
| 不変モデル | ドメイン名詞、接尾辞なし | any | task.dart → Task |
| 失敗型 | [Domain]Failure(sealed) | any | order_failure.dart → OrderFailure |
- ViewModel の役割を表すのは
Notifierです。型名にViewModelやChangeNotifierを決して使わないでください。 Riverpod 3.x では、ViewModel はNotifier/AsyncNotifier/StreamNotifierであり、Screenごとに 1 つ、1 対 1 で対応させ、[feature]_notifier.dart内で[Feature]Notifierと命名します(ファイルには主となる宣言を置きます)。[feature]_view_model.dart内の[Feature]ViewModelが認められるのは Provider/ChangeNotifierの付録に限られます。Riverpod を使う場合には決して使わず、[Feature]Controllerも使わないでください。state-management-riverpodを参照してください。 ServiceとGatewayは、どちらもプロバイダーに依存しない境界ポートの名前ですが、契約の所有者によって使い分けます。[Concern]Serviceは、あなたが定義する機能インターフェース(ShareService、AnalyticsService)です。[Concern]Gatewayは、特定の外部プラグイン/SDK やネイティブのMethodChannel(flutter_local_notificationsを利用するNotificationGateway、flutter_secure_storageを利用するSecureStorageGateway)を薄く包むラッパーです。ドメインコードと UI コードではインターフェース名を使い、具体的な実装(FirebaseAnalyticsService、FlnNotificationGateway)はコンポジションルートにのみ置きます。具体的な実装名が共有コードに漏れている場合は、grepで検出できる設計上の問題です。service-boundary-and-nativeを参照してください。- 純粋な Dart の型には、動作を示す
Calculator/Validator/Formatter/Parserの接尾辞を付け、Flutter に触れないようにします。 この接尾辞により、その型がフレームワークに依存せず、ウィジェットなしで単体テストできることが分かります。 - ドメインのユビキタス言語に合わせ、あらゆる場所で一貫性を保ちます。 モデル、リポジトリ、UI 全体で、概念ごとに使う語を 1 つに統一し、検索ですべての参照を見つけられるようにします。
値と時点、および名前で示すその他の区別
型システムでは表せない意味上の区別を、名前に反映させます。安定した暦上の値が必要な場面で生の時点(DateTime.now())を使うのは、よくある正確性のバグです。命名によって境界を明確にできます。
// A real wall-clock instant is legal only at the Clock boundary and named as one.
final DateTime capturedAt = clock.now(); // instant, from package:clock's Clock (never DateTime.now())
// A stored quantity carries its canonical unit in the name.
final int priceMinorUnits; // not `price` — unit is explicit
final int distanceMeters; // SI base unit, integer
// Unused callback params are `_` (and `__` for a second).
onChanged: (_) => notifier.refresh(),
標準的な保存方法については value-objects-money-and-units を、clockProvider 経由で注入する Clock(package:clock 由来)については service-boundary-and-native を参照してください。独自の ClockService は決して作らないでください。
実例
// task_list_notifier.dart — file name == primary declaration, snake_cased
import 'dart:async'; // dart: group, alphabetized
import 'package:flutter_riverpod/flutter_riverpod.dart'; // package: group, alphabetized
import 'task.dart'; // relative group, alphabetized
/// Frozen paging bound — unit lives in the name (rule 9).
const int maxItemsPerPage = 50; // lowerCamelCase const, not SCREAMING_CAPS
/// ViewModel for [TaskListScreen]. One Notifier per Screen, 1:1 (role suffix).
class TaskListNotifier extends AsyncNotifier<List<Task>> {
@override
Future<List<Task>> build() => ref.watch(taskRepositoryProvider).loadTasks();
/// Verb-phrase command; routes through the repository's single write path.
Future<void> archive(String taskId) async {
await ref.read(taskRepositoryProvider).archive(taskId);
ref.invalidateSelf();
}
bool get hasTasks => state.valueOrNull?.isNotEmpty ?? false; // boolean assertion
int get taskCount => state.valueOrNull?.length ?? 0; // noun getter, no get- prefix
}
アンチパターン
- 新しい Dart コードでの
SCREAMING_CAPS定数(const MAX_ITEMS = 50)—lowerCamelCaseを使用します。アナライザーで警告されます。 - クラス名とファイル名が一致しない(
home.dartにTaskScreenがある)、または無関係なものを寄せ集めたutils.dart/models.dart— 検索結果からファイルを特定しにくくなります。 - Flutter に触れる型に
…Calculator/…Validator(純粋な Dart を表すサフィックス)という名前を付けたり、Riverpod の ViewModel に…ViewModel/…VM/…Controllerという名前を付けたりすること — サフィックスがレイヤーを偽って示すことになります(Riverpod の場合は…Notifier、…ViewModelは Provider の付録内でのみ使用します)。 - 具象実装の名前(
FlnNotificationGateway、FirebaseAnalyticsService)を共有コード/ドメインコードから参照すること —Gateway/Serviceインターフェースを使い、実装はコンポジションルートに置きます。 - 安定させるべきフィールドに
DateTime.now()を取り込んだり、名前のない時点をドメインロジックに渡したりすること —Clockを注入し、時点に名前を付けます(capturedAt)。 getDueTasks()やget/setで始まる名前 — Dart には実際のゲッターがあります。- 型を名前に含めること:
userMap、nameString、itemsList、strName、iCount。 is/has/can/shouldのないブール値の名前(loading、valid)、単位の分からない数量(total、delay)。- 公開シンボルの「名前空間」に先頭の
_を使うこと(プライベートになり、アクセスできなくなります)、l/O/Iのような一文字の名前、またはコミットするコードでのdata/temp/foo。 - 単一の短い関数内で対応関係をコメントに記す場合を除き、省略形(
opt、qty、msg)を使うこと。 - インポートグループを手動で並べ替えたり、混在させたりすること —
dart formatを実行し、directives_orderingに従います。
完了の定義
- 型は
UpperCamelCase、メンバー/変数/定数はlowerCamelCase、ファイル/フォルダー/プレフィックスはlower_snake_caseです。 - ファイル名は主な宣言と一致しています。1 ファイルにつき主な公開型は 1 つです。寄せ集めのファイルやフォルダーはありません。
- アーキテクチャに沿ったサフィックスを、レイヤーに応じて正しく適用しています(
Screen/Notifier/Repository/Dao/Service/Gateway/Failure)。 - Riverpod の ViewModel は
…Notifierです(…Controller/…ViewModel/…VMではありません)。純粋な Dart の型にはCalculator/Validator/Formatterのサフィックスが付き、Flutter のインポートはありません。Service/Gatewayインターフェースにプロバイダーは含まれず、実装はコンポジションルートに限定されています。 - 2 文字を超える頭字語は単語として大文字小文字を使い分けています。辞書に載っている完全な単語を使用しています。単位と意味を名前に含めています。時点には
Clockの境界で名前を付けています。 - ブール値は真偽を表す文として読めます。関数名は動詞句です。ゲッター名は
getの接頭辞がない名詞句です。 - インポートは
dart:/package:/相対パスでグループ化し、アルファベット順に並んでいます。dart formatとdart analyze --fatal-infosで問題がありません。
関連スキル
dart3-idioms-and-coding-standards— 各宣言に適した構文(sealed/record/enum)と、複雑さの制限について。state-management-riverpod— ViewModel がNotifierである理由と、単一の書き込み経路について。service-boundary-and-native— プロバイダーを含まないServiceインターフェースと、コンポジションルートに置くその実装について。value-objects-money-and-units— 名前に単位を含めるルールを支える、正規単位での保存について。naming関連項目:公開シンボルの///コントラクトについてはdartdoc-conventions、directives_ordering/constant_identifier_namesの適用についてはlint-and-style-config、サフィックスが対応するレイヤー DAG についてはflutter-architectureを参照してください。
参考資料
- Effective Dart: Style — 大文字小文字の使い分け、ファイル名、インポート順(規範)。
- Effective Dart: Design — naming — ブール値、ゲッターとメソッドの違い、動詞句/名詞句の表現。
- Dart linter rules —
constant_identifier_names、camel_case_types、file_names、directives_ordering、non_constant_identifier_names。