naming-conventions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNaming Conventions
命名约定
Consistent, role-carrying names make code searchable and self-explaining, and the suffix on a type declares which layer it lives in — so a reviewer, a , and a banned-import gate can all read the layer off the name alone. This skill is the how; the normative what is Effective Dart. Never invent a house style that contradicts the language's own.
grep一致且能够承载角色含义的命名可让代码易于搜索和自我解释;类型上的后缀会声明其所属层级——因此评审者、 和导入限制检查门禁都能仅凭名称识别层级。该 skill 讲解“如何做”;规范性的“做什么”参见 Effective Dart。绝不要发明与语言自身规范相冲突的内部风格。
grepNon-negotiable rules
不可协商的规则
- Types are . Classes, enums, mixins, extensions, typedefs, type parameters:
UpperCamelCase,TaskScreen,OrderStatus. Consistent shape makes types visually distinct from values.Predicate<T> - Members, variables, functions, and parameters are .
lowerCamelCase,dueDate,loadTasks(). It is the language default; deviating costs readers a double-take.itemCount - Constants are , never
lowerCamelCase.SCREAMING_CAPS— notconst maxItemsPerPage = 50;. Dart dropped the C convention; the analyzer expectsconst MAX_ITEMS = 50.constant_identifier_names - Files, folders, libraries, and import prefixes are .
lowercase_with_underscores,task_detail_screen.dart,features/task_detail/. Cross-platform filesystems and pub demand it.import 'package:app_core/app_core.dart'; - File name = its primary declaration, snake_cased, one primary public type per file. lives in
TaskNotifier. Notask_notifier.dart/utils.dart/helpers.dartgrab-bags and nomodels.dart/utils//common//helpers/junk-drawer folders — a reader who greps a symbol must land in the file that owns it.misc/is the sanctioned pure-foundation layer (value objects,core//Result, theFailureseam, pure calculators), not a junk-drawer — seeClock, which owns the layout.project-structure-and-packages - Acronyms longer than two letters are cased like a word. ,
Json,Http,Url→Api,JsonOrder,HttpClient,fromJson— notimageUrl,JSONOrder. Two-letter caps-in-English acronyms may stay caps as types (HTTPClient,ID). Mixed-case acronyms are unsearchable and inconsistent.UI - A leading underscore means library-private — use it only when you mean private. Never prefix a public symbol with to "namespace" it; that makes it unusable from another file. Public (no
_) is a documented contract — see_.dartdoc-conventions - No Hungarian / type-encoding in names. Not ,
strName,iCount,lstItems,userMap,nameString. The type system already knows the type; writeitemsList,name,usersById.items - Full dictionary words; units and semantics live in the name. ,
maxItemsPerPage,retryDelaySeconds— never bareorderTotalMinorUnits,max,delay. Abbreviations (total,opt,qty) are confined to the inside of one short pure function with a comment mapping them. A name that omits its unit invites a unit bug.amt - Booleans read as assertions. ,
isLoading,hasError,canSubmit— notshouldRetry,loading,error. Boolean getters and methods startretry/is/has/canso a condition reads like prose.should - No -prefixed accessors. Expose
get, notdueTasks. Dart has real getters. Functions are verb phrases (getDueTasks(),loadTasks()); non-boolean getters are noun phrases (scheduleReminder(),itemCount).nextDueDate - Imports grouped and sorted: first, then
dart:, then relative — each group alphabetized,package:s in their own section after imports. Letexportplus thedart formatlint enforce it; never hand-fight the formatter.directives_ordering
- 类型使用 。 Class、enum、mixin、extension、typedef、类型参数:
UpperCamelCase、TaskScreen、OrderStatus。统一的形状可以让类型在视觉上与值区分开来。Predicate<T> - 成员、变量、函数和参数使用 。
lowerCamelCase、dueDate、loadTasks()。这是语言默认规则;偏离它会让读者额外停顿确认。itemCount - 常量使用 ,绝不使用
lowerCamelCase。SCREAMING_CAPS——不要写成const maxItemsPerPage = 50;。Dart 不再采用 C 语言惯例;analyzer 会报告const MAX_ITEMS = 50。constant_identifier_names - 文件、文件夹、library 和 import prefix 使用 。
lowercase_with_underscores、task_detail_screen.dart、features/task_detail/。这是跨平台文件系统和 pub 的要求。import 'package:app_core/app_core.dart'; - 文件名等于其主要声明,使用 snake_cased,每个文件仅包含一个主要 public type。 位于
TaskNotifier中。不允许使用task_notifier.dart/utils.dart/helpers.dart这样的“大杂烩”文件,也不允许使用models.dart/utils//common//helpers/这样的杂物抽屉式文件夹——读者 grep 某个符号时,必须能够直接定位到拥有该符号的文件。misc/是经过认可的纯基础层(值对象、core//Result、Failure接缝、纯计算器),而不是杂物抽屉——具体布局由Clock负责。project-structure-and-packages - 超过两个字母的缩写按普通单词处理大小写。 、
Json、Http、Url→Api、JsonOrder、HttpClient、fromJson,而不是imageUrl、JSONOrder。作为类型时,两个字母的英文首字母缩写可以保留大写(HTTPClient、ID)。混合大小写的缩写难以搜索且缺乏一致性。UI - 前导下划线表示 library 私有——仅在确实需要私有时使用。 绝不要用 作为 public 符号的前缀来“划分命名空间”;这会使符号无法从其他文件使用。公开符号(不带
_)本身就是一份有文档的契约——参见_。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。布尔 getter 和方法以retry/is/has/can开头,使条件表达式像自然语言一样清晰。should - 禁止使用带 前缀的访问器。 应暴露
get,而不是dueTasks。Dart 支持真正的 getter。函数使用动词短语(getDueTasks()、loadTasks());非布尔 getter 使用名词短语(scheduleReminder()、itemCount)。nextDueDate - **导入需分组并排序:**先放 ,然后是
dart:,最后是相对导入;每组按字母顺序排列,package:单独成组并位于导入之后。让export和dart formatlint 负责强制执行;绝不要手工对抗 formatter。directives_ordering
The suffix declares the layer
后缀声明所属层级
A role suffix turns a name into a layer contract: a or a path-scoped import gate can tell a repository from a view from a platform boundary without opening the file. Use neutral domain nouns (Task, Order, Account, Item) for the entity token.
grep| Role | Suffix / pattern | Layer | File → symbol |
|---|---|---|---|
| Screen / route target | | UI (View) | |
| Reusable widget | | UI | |
| ViewModel (Riverpod, default) | | UI (ViewModel) | |
| ViewModel (Provider appendix only) | | UI (ViewModel) | |
| Repository (interface) | | data | |
| Repository (impl) | | data | |
| Drift DAO | | data | |
| Capability interface you define | | boundary | |
| Service impl (per flavor) | | app adapter | |
| Wrapper over a specific plugin/SDK/native channel | | boundary | |
| Gateway impl | | app adapter | |
| Pure-Dart domain logic | | domain | |
| Immutable model | domain noun, no suffix | any | |
| Failure type | | any | |
- is the ViewModel role — never
NotifierorViewModelin the type name. On Riverpod 3.x the ViewModel is aChangeNotifier/Notifier/AsyncNotifier, one perStreamNotifier, 1:1, namedScreenin[Feature]Notifier(file = primary declaration).[feature]_notifier.dartin[Feature]ViewModelis sanctioned ONLY in the Provider/[feature]_view_model.dartappendix — never on the Riverpod path, and neverChangeNotifier. See[Feature]Controller.state-management-riverpod - vs
Service— both name provider-free boundary ports, distinguished by who owns the contract. AGatewayis a capability interface YOU define ([Concern]Service,ShareService); aAnalyticsServiceis the thin wrapper over a SPECIFIC external plugin/SDK or native[Concern]Gateway(MethodChanneloverNotificationGateway,flutter_local_notificationsoverSecureStorageGateway). Domain and UI code names the interface; the concrete impl (flutter_secure_storage,FirebaseAnalyticsService) lives only in the composition root. A concrete impl name leaking into shared code is a grep-catchable smell. SeeFlnNotificationGateway.service-boundary-and-native - Pure-Dart types carry a /
Calculator/Validator/Formatterverb suffix and touch no Flutter. The suffix advertises that the type is framework-free and unit-testable without a widget.Parser - Match the domain's ubiquitous language and keep it consistent everywhere — one word per concept across models, repositories, and UI, so search finds every reference.
角色后缀可将名称转换为层级契约:无需打开文件, 或限定路径的导入门禁就能区分 repository、view 和平台边界。实体部分使用中立的领域名词(Task、Order、Account、Item)。
grep| 角色 | 后缀/模式 | 层级 | 文件 → 符号 |
|---|---|---|---|
| Screen/路由目标 | | UI(View) | |
| 可复用 widget | | UI | |
| ViewModel(Riverpod,默认) | | UI(ViewModel) | |
| ViewModel(仅限 Provider 附录) | | UI(ViewModel) | |
| Repository(接口) | | data | |
| Repository(实现) | | data | |
| Drift DAO | | data | |
| 由你定义的能力接口 | | boundary | |
| Service 实现(按 flavor 划分) | | app adapter | |
| 特定 plugin/SDK/native channel 的封装 | | boundary | |
| Gateway 实现 | | app adapter | |
| 纯 Dart 领域逻辑 | | domain | |
| 不可变模型 | 领域名词,无后缀 | any | |
| Failure 类型 | | any | |
- 是 ViewModel 角色——类型名称中绝不要使用
Notifier或ViewModel。 在 Riverpod 3.x 中,ViewModel 是ChangeNotifier/Notifier/AsyncNotifier,每个StreamNotifier对应一个,比例为 1:1,名称为Screen,文件为[Feature]Notifier(文件名等于主要声明)。[feature]_notifier.dart和[Feature]ViewModel仅允许用于 Provider/[feature]_view_model.dart附录;在 Riverpod 路径中绝不能使用,也绝不能使用ChangeNotifier。参见[Feature]Controller。state-management-riverpod - 与
Service——二者都表示不绑定 provider 的边界端口,区别在于谁拥有契约。Gateway是由你定义的能力接口([Concern]Service、ShareService);AnalyticsService是对特定外部 plugin/SDK 或 native[Concern]Gateway的轻量封装(基于MethodChannel的flutter_local_notifications、基于NotificationGateway的flutter_secure_storage)。领域代码和 UI 代码引用接口;具体实现(SecureStorageGateway、FirebaseAnalyticsService)只能存在于 composition root(组合根)。共享代码中出现具体实现名称,是可以通过 grep 发现的代码异味。参见FlnNotificationGateway。service-boundary-and-native - 纯 Dart 类型应使用 /
Calculator/Validator/Formatter动词后缀,并且不依赖 Flutter。 这样的后缀表明该类型不依赖框架,可以在没有 widget 的情况下进行单元测试。Parser - 匹配领域的通用语言,并在所有位置保持一致——模型、repository 和 UI 中每个概念只使用一个词,使搜索能够找到全部引用。
Value vs. instant, and other name-carried distinctions
值与瞬时点,以及名称承载的其他区别
Encode a semantic distinction the type system can't in the name. A raw instant () used where a stable calendar value is expected is a common correctness bug; naming makes the boundary visible.
DateTime.now()dart
// 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(),See for canonical storage and for the (from ) injected via — never a bespoke .
value-objects-money-and-unitsservice-boundary-and-nativeClockpackage:clockclockProviderClockService在名称中编码类型系统无法表达的语义区别。在需要稳定日历值的位置使用原始瞬时点()是一种常见正确性错误;通过命名可以让这一边界清晰可见。
DateTime.now()dart
// 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-unitsservice-boundary-and-nativeclockProviderClockpackage:clockClockServiceWorked example
实践示例
dart
// 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>> {
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
// 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>> {
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
}Anti-patterns
反模式
- constants in new Dart (
SCREAMING_CAPS) — useconst MAX_ITEMS = 50; the analyzer flags it.lowerCamelCase - Class name ≠ file name (living in
TaskScreen), or a grab-baghome.dart/utils.dartholding unrelated things — breaks grep-to-file.models.dart - Naming a Flutter-touching type /
…Calculator(a pure-Dart suffix), or a Riverpod ViewModel…Validator/…ViewModel/…VM— the suffix then lies about the layer (…Controlleron the Riverpod path;…Notifieronly in the Provider appendix).…ViewModel - A concrete impl name (,
FlnNotificationGateway) referenced from shared/domain code — name theFirebaseAnalyticsService/Gatewayinterface; the impl belongs in the composition root.Service - captured into a field meant to be stable, or an unnamed instant crossing into domain logic — inject a
DateTime.now()and name the instant (Clock).capturedAt - /
getDueTasks()/getprefixes — Dart has real getters.set - Type baked into a name: ,
userMap,nameString,itemsList,strName.iCount - Booleans without /
is/has/can(should,loading); unit-silent quantities (valid,total).delay - Leading to "namespace" a public symbol (it makes it private and unreachable);
_/l/Osingle letters orI/data/tempin committed code.foo - Abbreviations (,
opt,qty) outside a single short function with a mapping comment.msg - Hand-sorted or mixed import groups — run ; obey
dart format.directives_ordering
- 在新的 Dart 代码中使用 常量(
SCREAMING_CAPS)——应使用const MAX_ITEMS = 50;analyzer 会报告此问题。lowerCamelCase - 类名与文件名不一致(位于
TaskScreen中),或者使用home.dart/utils.dart这样的“大杂烩”文件存放无关内容——这会破坏从 grep 定位到文件的能力。models.dart - 为依赖 Flutter 的类型命名为 /
…Calculator(纯 Dart 后缀),或者为 Riverpod ViewModel 命名为…Validator/…ViewModel/…VM——此时后缀会错误地声明其所属层级(Riverpod 路径应使用…Controller;…Notifier仅限 Provider 附录)。…ViewModel - 在共享代码或领域代码中引用具体实现(、
FlnNotificationGateway)——应引用FirebaseAnalyticsService/Gateway接口;具体实现应仅存在于 composition root。Service - 将 捕获到本应保持稳定的字段中,或者让未命名的瞬时点进入领域逻辑——应注入
DateTime.now()并为瞬时点命名(Clock)。capturedAt - 使用 或
getDueTasks()/get前缀——Dart 支持真正的 getter。set - 在名称中编码类型:、
userMap、nameString、itemsList、strName。iCount - 布尔值不使用 /
is/has/can(should、loading);数量不标明单位(valid、total)。delay - 使用前导 为 public 符号“划分命名空间”(这会使其私有且无法访问);在已提交代码中使用
_/l/O等单字母名称或I/data/temp。foo - 在单个简短函数及其映射注释之外使用缩写(、
opt、qty)。msg - 手工排序或混合导入分组——应运行 并遵守
dart format。directives_ordering
Definition of done
完成定义
- Types ; members/vars/constants
UpperCamelCase; files/folders/prefixeslowerCamelCase.lower_snake_case - File name matches its primary declaration; one primary public type per file; no junk-drawer file or folder.
- Architectural suffix applied and correct for the layer (/
Screen/Notifier/Repository/Dao/Service/Gateway).Failure - Riverpod ViewModels are (not
…Notifier/…Controller/…ViewModel); pure-Dart types carry a…VM/Calculator/Validatorsuffix and no Flutter import;Formatter/Serviceinterfaces are provider-free with impls confined to the composition root.Gateway - Acronyms >2 letters word-cased; full dictionary words; units + semantics in the name; instants named at the boundary.
Clock - Booleans read as assertions; functions are verb phrases; getters are noun phrases with no prefix.
get - Imports grouped /
dart:/relative and alphabetized;package:+dart formatclean.dart analyze --fatal-infos
- 类型使用 ;成员、变量和常量使用
UpperCamelCase;文件、文件夹和 prefix 使用lowerCamelCase。lower_snake_case - 文件名与主要声明一致;每个文件仅包含一个主要 public type;不存在“大杂烩”文件或文件夹。
- 已应用与层级匹配的正确架构后缀(/
Screen/Notifier/Repository/Dao/Service/Gateway)。Failure - Riverpod ViewModel 使用 (而不是
…Notifier/…Controller/…ViewModel);纯 Dart 类型使用…VM/Calculator/Validator后缀且不导入 Flutter;Formatter/Service接口不绑定 provider,具体实现仅存在于 composition root。Gateway - 超过两个字母的缩写按单词处理大小写;使用完整字典单词;名称中包含单位和语义;瞬时点在 边界处明确命名。
Clock - 布尔值读作断言;函数使用动词短语;getter 使用名词短语且不带 前缀。
get - 导入按 /
dart:/相对导入分组并按字母顺序排列;package:和dart format均无问题。dart analyze --fatal-infos
Related skills
相关 skill
- — which construct (sealed/record/enum) each declaration earns, and complexity limits.
dart3-idioms-and-coding-standards - — why the ViewModel is a
state-management-riverpod, and the single write path.Notifier - — provider-free
service-boundary-and-nativeinterfaces and their composition-root impls.Service - — canonical-unit storage behind the unit-in-name rule.
value-objects-money-and-units - neighbors:
namingfor thedartdoc-conventionscontract on public symbols,///for thelint-and-style-config/directives_orderingenforcement, andconstant_identifier_namesfor the layer DAG the suffixes map to.flutter-architecture
- ——每种声明适合采用哪种构造(sealed/record/enum),以及复杂度限制。
dart3-idioms-and-coding-standards - ——为什么 ViewModel 是
state-management-riverpod,以及单一写入路径。Notifier - ——不绑定 provider 的
service-boundary-and-native接口及其 composition root 实现。Service - ——单位写入名称规则背后的规范单位存储。
value-objects-money-and-units - 的相邻 skill:使用
naming规范 public 符号的dartdoc-conventions契约;使用///强制执行lint-and-style-config/directives_ordering;使用constant_identifier_names管理后缀所映射的层级 DAG。flutter-architecture
References
参考
- Effective Dart: Style — casing, file names, import ordering (normative).
- Effective Dart: Design — naming — booleans, getters vs. methods, verb/noun phrasing.
- Dart linter rules — ,
constant_identifier_names,camel_case_types,file_names,directives_ordering.non_constant_identifier_names
- Effective Dart: Style——大小写、文件名、导入顺序(规范性要求)。
- Effective Dart: Design — naming——布尔值、getter 与方法、动词/名词短语。
- Dart linter rules——、
constant_identifier_names、camel_case_types、file_names、directives_ordering。non_constant_identifier_names