跳到主要内容

高效 Dart:风格

良好代码的一个出人意料的重要部分是良好的风格。一致的命名、排序和格式化有助于使 *相同* 的代码 *看起来* 相同。它利用了我们大多数人眼中强大的模式匹配硬件。如果我们在整个 Dart 生态系统中使用一致的风格,那么我们所有人学习和贡献彼此的代码都会变得更容易。

标识符

#

Dart 中的标识符有三种类型。

  • UpperCamelCase 命名法将每个单词的首字母大写,包括第一个单词。

  • lowerCamelCase 命名法将每个单词的首字母大写,*除了* 第一个单词始终小写,即使它是缩写词。

  • lowercase_with_underscores 命名法只使用小写字母,即使是缩写词,并用 _ 分隔单词。

务必使用 UpperCamelCase 命名类型

#

Linter 规则: camel_case_types

类、枚举类型、类型别名和类型参数应将每个单词的首字母大写(包括第一个单词),且不使用分隔符。

正确dart
class SliderMenu {
   ...
}

class HttpRequest {
   ...
}

typedef Predicate<T> = bool Function(T value);

这甚至包括旨在用于元数据注解的类。

正确dart
class Foo {
  const Foo([Object? arg]);
}

@Foo(anArg)
class A {
   ...
}

@Foo()
class B {
   ...
}

如果注解类的构造函数不带参数,你可能需要为其创建一个单独的 lowerCamelCase 常量。

正确dart
const foo = Foo();

@foo
class C {
   ...
}

务必使用 UpperCamelCase 命名扩展

#

Linter 规则: camel_case_extensions

与类型一样,扩展应将每个单词的首字母大写(包括第一个单词),且不使用分隔符。

正确dart
extension MyFancyList<T> on List<T> {
   ...
}

extension SmartIterable<T> on Iterable<T> {
   ...
}

务必使用 lowercase_with_underscores 命名包、目录和源文件

#

Linter 规则: file_names, package_names

某些文件系统不区分大小写,因此许多项目要求文件名全部小写。使用分隔符可以使名称在这种形式下仍然可读。使用下划线作为分隔符可确保名称仍然是有效的 Dart 标识符,如果语言将来支持符号导入,这可能会有所帮助。

正确
my_package
└─ lib
   └─ file_system.dart
   └─ slider_menu.dart
错误
mypackage
└─ lib
   └─ file-system.dart
   └─ SliderMenu.dart

务必使用 lowercase_with_underscores 命名导入前缀

#

Linter 规则: library_prefixes

正确dart
import 'dart:math' as math;
import 'package:angular_components/angular_components.dart' as angular_components;
import 'package:js/js.dart' as js;
错误dart
import 'dart:math' as Math;
import 'package:angular_components/angular_components.dart' as angularComponents;
import 'package:js/js.dart' as JS;

务必使用 lowerCamelCase 命名其他标识符

#

Linter 规则: non_constant_identifier_names

类成员、顶层定义、变量、参数和命名参数应将每个单词的首字母大写,*除了* 第一个单词,并且不使用分隔符。

正确dart
var count = 3;

HttpRequest httpRequest;

void align(bool clearItems) {
  // ...
}

建议为常量名使用 lowerCamelCase

#

Linter 规则: constant_identifier_names

在新代码中,常量变量(包括枚举值)使用 lowerCamelCase 命名。

正确dart
const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');

class Dice {
  static final numberGenerator = Random();
}
错误dart
const PI = 3.14;
const DefaultTimeout = 1000;
final URL_SCHEME = RegExp('^([a-z]+):');

class Dice {
  static final NUMBER_GENERATOR = Random();
}

为了与现有代码保持一致,你可以在以下情况下使用 SCREAMING_CAPS

  • 当向已使用 SCREAMING_CAPS 的文件或库添加代码时。
  • 当生成与 Java 代码并行的 Dart 代码时,例如,从 protobufs 生成的枚举类型。

务必像单词一样,将两个字母以上的缩写词和缩写首字母大写

#

大写缩写词可能难以阅读,多个相邻的缩写词可能导致名称歧义。例如,给定标识符 HTTPSFTP,读者无法判断它指的是 HTTPS FTP 还是 HTTP SFTP。为了避免这种情况,大多数缩写词和缩写应像普通单词一样首字母大写。如果指前者,此标识符将是 HttpsFtp,如果指后者,则为 HttpSftp

两个字母的缩写和缩写词是例外。如果两个字母在英语中都大写,那么当它们在标识符中使用时,它们都应该保持大写。否则,像单词一样首字母大写。

正确dart
// Longer than two letters, so always like a word:
Http // "hypertext transfer protocol"
Nasa // "national aeronautics and space administration"
Uri // "uniform resource identifier"
Esq // "esquire"
Ave // "avenue"

// Two letters, capitalized in English, so capitalized in an identifier:
ID // "identifier"
TV // "television"
UI // "user interface"

// Two letters, not capitalized in English, so like a word in an identifier:
Mr // "mister"
St // "street"
Rd // "road"
错误dart
HTTP // "hypertext transfer protocol"
NASA // "national aeronautics and space administration"
URI // "uniform resource identifier"
esq // "esquire"
ave // "avenue"

Id // "identifier"
Tv // "television"
Ui // "user interface"

MR // "mister"
ST // "street"
RD // "road"

当任何形式的缩写出现在 lowerCamelCase 标识符的开头时,该缩写应全部小写

dart
var httpConnection = connect();
var tvSet = Television();
var mrRogers = 'hello, neighbor';

建议对未使用的回调参数使用通配符

#

有时回调函数的类型签名需要一个参数,但回调实现并 *没有使用* 该参数。在这种情况下,惯用的做法是将未使用的参数命名为 _,这声明了一个非绑定的通配符变量

正确dart
futureOfVoid.then((_) {
  print('Operation complete.');
});

由于通配符变量是非绑定的,你可以将多个未使用的参数命名为 _

正确dart
.onError((_, _) {
  print('Operation failed.');
});

此准则仅适用于 *匿名且局部* 的函数。这些函数通常立即在一个明确未使用参数表示什么的上下文中被使用。相比之下,顶层函数和方法声明没有这种上下文,因此它们的参数必须命名,以便清楚每个参数的用途,即使它没有被使用。

不要对非私有标识符使用前导下划线

#

Dart 在标识符中使用前导下划线来标记成员和顶层声明为私有。这训练用户将前导下划线与这些声明中的一种关联起来。他们看到“_”就认为是“私有”。

对于局部变量、参数、局部函数或库前缀,没有“私有”的概念。当其中任何一个的名称以下划线开头时,它会向读者发送一个令人困惑的信号。为了避免这种情况,不要在这些名称中使用前导下划线。

不要使用前缀字母

#

匈牙利命名法和其他命名方案出现在 BCPL 时代,那时编译器对帮助你理解代码的作用不大。由于 Dart 可以告诉你声明的类型、作用域、可变性以及其他属性,因此没有理由在标识符名称中编码这些属性。

正确dart
defaultTimeout
错误dart
kDefaultTimeout

不要显式命名库

#

library 指令后附加名称在技术上是可行的,但它是一个遗留特性,不推荐使用。

Dart 根据每个库的路径和文件名为其生成一个唯一的标签。命名库会覆盖这个生成的 URI。如果没有 URI,工具可能更难找到相关的主库文件。

错误dart
library my_library;
正确dart
/// A really great test library.
@TestOn('browser')
library;

排序

#

为了保持文件开头的整洁,我们规定了指令的出现顺序。每个“部分”都应该用空行分隔。

一个 linter 规则处理所有排序指南:directives_ordering。

务必将 dart: 导入放在其他导入之前

#

Linter 规则: directives_ordering

正确dart
import 'dart:async';
import 'dart:collection';

import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

务必将 package: 导入放在相对导入之前

#

Linter 规则: directives_ordering

正确dart
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

import 'util.dart';

务必在所有导入之后,在单独的部分中指定导出

#

Linter 规则: directives_ordering

正确dart
import 'src/error.dart';
import 'src/foo_bar.dart';

export 'src/error.dart';
错误dart
import 'src/error.dart';
export 'src/error.dart';
import 'src/foo_bar.dart';

务必按字母顺序对各部分进行排序

#

Linter 规则: directives_ordering

正确dart
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';

import 'foo.dart';
import 'foo/foo.dart';
错误dart
import 'package:foo/foo.dart';
import 'package:bar/bar.dart';

import 'foo/foo.dart';
import 'foo.dart';

格式化

#

像许多语言一样,Dart 忽略空白符。然而,*人类* 不会。拥有一致的空白符风格有助于确保人类读者以与编译器相同的方式看待代码。

务必使用 dart format 格式化你的代码

#

格式化是项繁琐的工作,在重构期间尤其耗时。幸运的是,你不必为此烦恼。我们提供了一个复杂的自动化代码格式化工具,名为 dart format,它会帮你完成这项工作。Dart 官方的空白符处理规则是 *dart format 生成的任何内容*。格式化工具的常见问题解答可以提供更多关于其强制执行的风格选择的见解。

其余的格式化指南是针对 dart format 无法为你修复的少数情况。

考虑更改你的代码以使其更适合格式化工具

#

格式化工具会尽力处理你提供的任何代码,但它无法创造奇迹。如果你的代码有特别长的标识符、深度嵌套的表达式、混合不同类型的运算符等,格式化后的输出可能仍然难以阅读。

发生这种情况时,请重组或简化你的代码。考虑缩短局部变量名或将表达式提升到新的局部变量中。换句话说,进行与你手动格式化代码并尝试使其更具可读性时相同的修改。将 dart format 视为一种伙伴关系,你们有时会迭代地协同工作,以生成漂亮的代码。

建议每行 80 个字符或更少

#

Linter 规则: lines_longer_than_80_chars

可读性研究表明,长文本行更难阅读,因为当眼睛移到下一行的开头时,需要移动更远的距离。这就是报纸和杂志使用多栏文本的原因。

如果你真的发现自己想要超过 80 个字符的行,我们的经验是你的代码可能过于冗长,可以更紧凑一些。主要的问题通常是 VeryLongCamelCaseClassNames。问问自己:“该类型名称中的每个单词是否提供了关键信息或避免了名称冲突?” 如果不是,请考虑省略它。

请注意,dart format 默认为 80 个字符或更少,但你可以配置默认值。它不会将长字符串字面量拆分为 80 列以内,因此你需要手动完成此操作。

例外: 当 URI 或文件路径出现在注释或字符串中(通常在导入或导出中)时,即使导致行超过 80 个字符,它也可以保持完整。这使得在源文件中搜索路径更容易。

例外: 多行字符串可以包含超过 80 个字符的行,因为换行符在字符串内部具有重要意义,将行拆分为更短的行可能会改变程序。

务必为所有控制流语句使用花括号

#

Linter 规则: curly_braces_in_flow_control_structures

这样做可以避免悬挂 else 问题。

正确dart
if (isWeekDay) {
  print('Bike to work!');
} else {
  print('Go dancing or read a book!');
}

例外: 当你有一个没有 else 子句的 if 语句,并且整个 if 语句都在一行上时,如果你愿意,可以省略花括号

正确dart
if (arg == null) return defaultValue;

但是,如果主体换行到下一行,则使用花括号

正确dart
if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}
错误dart
if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;