关于 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。 类、枚举、混入、扩展、类型别名、类型参数: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 都要求这样命名。 - 文件名应与其主要声明一致,使用蛇形命名法;每个文件只放一个主要公共类型。
TaskNotifier应放在task_notifier.dart中。不要使用utils.dart/helpers.dart/models.dart这种杂物文件,也不要使用utils//common//helpers//misc/这种杂物文件夹——读者搜索某个符号时,必须能找到拥有该符号的文件。core/是认可的纯基础层(值对象、Result/Failure、Clock接口、纯计算器),不是杂物抽屉——布局由project-structure-and-packages说明。 - 超过两个字母的缩写按普通单词的方式使用大小写。
Json、Http、Url、Api→JsonOrder、HttpClient、fromJson、imageUrl——而不是JSONOrder、HTTPClient。英语中的双字母大写缩写可以在类型中保留大写,如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。布尔值 getter 和方法以is/has/can/should开头,使条件读起来像一句话。 - 访问器名称不要以
get开头。 暴露dueTasks,而不是getDueTasks()。Dart 有真正的 getter。函数使用动词短语(loadTasks()、scheduleReminder());非布尔 getter 使用名词短语(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 |
| 服务实现(按 flavor 区分) | [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 |
| 不可变模型 | 领域名词,不带后缀 | 任意 | task.dart → Task |
| 故障类型 | [Domain]Failure(sealed) | 任意 | order_failure.dart → OrderFailure |
Notifier才是 ViewModel 角色——类型名称中绝不使用ViewModel或ChangeNotifier。 在 Riverpod 3.x 中,ViewModel 是Notifier/AsyncNotifier/StreamNotifier,每个Screen对应一个,严格保持 1:1,并在[feature]_notifier.dart中命名为[Feature]Notifier(文件名对应主要声明)。[Feature]ViewModel和[feature]_view_model.dart仅在 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。 该后缀表明此类型不依赖框架,可在不使用 Widget 的情况下进行单元测试。 - 符合领域的通用语言,并在各处保持一致——模型、仓库和 UI 中每个概念都使用同一个词,确保搜索能找到所有引用。
值与时刻,以及其他通过名称表达的区别
将类型系统无法表达的语义区别编码到名称中。在预期使用稳定日历值的场景中使用原始时刻(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;分析器会对此发出警告。 - 类名与文件名不一致(
TaskScreen定义在home.dart中),或在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 有真正的 getter。 - 在名称中写入类型:
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。 - 文件名与其主要声明一致;每个文件只有一个主要公共类型;没有杂物文件或文件夹。
- 架构后缀与所属层匹配且使用正确(
Screen/Notifier/Repository/Dao/Service/Gateway/Failure)。 - Riverpod ViewModel 命名为
…Notifier(而非…Controller/…ViewModel/…VM);纯 Dart 类型带有Calculator/Validator/Formatter后缀且不导入 Flutter;Service/Gateway接口不依赖 provider,实现仅限于组合根中。 - 超过 2 个字母的首字母缩写按单词形式大小写;使用完整的词典单词;名称中包含单位和语义;在
Clock边界命名时间点。 - 布尔值名称读起来像断言;函数名称使用动词短语;getter 使用名词短语且不带
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— 不依赖 provider 的Service接口及其组合根实现。value-objects-money-and-units— 单位名称规则背后的规范单位存储。naming相关技能:dartdoc-conventions介绍公共符号的///契约,lint-and-style-config介绍directives_ordering/constant_identifier_names的强制检查,flutter-architecture介绍这些后缀所映射的层级 DAG。
参考资料
- Effective Dart:风格 — 大小写、文件名、导入排序(规范性要求)。
- Effective Dart:设计——命名 — 布尔值、getter 与方法的区别、动词/名词短语。
- Dart linter 规则 —
constant_identifier_names、camel_case_types、file_names、directives_ordering、non_constant_identifier_names。
评论
支持 Markdown