跳到主要内容

Effective Dart:风格

好的代码中一个非常重要的部分是良好的风格。一致的命名、排序和格式化有助于使相同的代码看起来相同。它利用了我们大多数人在视觉系统中拥有的强大的模式匹配硬件。如果我们在整个 Dart 生态系统中使用一致的风格,这将使我们所有人更容易学习和贡献彼此的代码。

标识符

#

标识符在 Dart 中有三种风格。

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

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

  • lowercase_with_underscores 名称仅使用小写字母,即使对于首字母缩略词也是如此,并用 _ 分隔单词。

DO 使用 UpperCamelCase 命名类型

#

Linter 规则:camel_case_types

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

gooddart
class SliderMenu {
   ...
}

class HttpRequest {
   ...
}

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

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

gooddart
class Foo {
  const Foo([Object? arg]);
}

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

@Foo()
class B {
   ...
}

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

gooddart
const foo = Foo();

@foo
class C {
   ...
}

DO 使用 UpperCamelCase 命名扩展

#

Linter 规则:camel_case_extensions

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

gooddart
extension MyFancyList<T> on List<T> {
   ...
}

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

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

#

Linter 规则:file_namespackage_names

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

good
my_package
└─ lib
   └─ file_system.dart
   └─ slider_menu.dart
bad
mypackage
└─ lib
   └─ file-system.dart
   └─ SliderMenu.dart

DO 使用 lowercase_with_underscores 命名导入前缀

#

Linter 规则:library_prefixes

gooddart
import 'dart:math' as math;
import 'package:angular_components/angular_components.dart' as angular_components;
import 'package:js/js.dart' as js;
baddart
import 'dart:math' as Math;
import 'package:angular_components/angular_components.dart' as angularComponents;
import 'package:js/js.dart' as JS;

DO 使用 lowerCamelCase 命名其他标识符

#

Linter 规则:non_constant_identifier_names

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

gooddart
var count = 3;

HttpRequest httpRequest;

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

PREFER 对常量名使用 lowerCamelCase

#

Linter 规则:constant_identifier_names

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

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

class Dice {
  static final numberGenerator = Random();
}
baddart
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 生成的枚举类型中。

DO 像单词一样大写超过两个字母的首字母缩略词和缩写词

#

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

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

gooddart
// 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"
baddart
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';

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

#

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

gooddart
futureOfVoid.then((_) {
  print('Operation complete.');
});

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

gooddart
.onError((_, _) {
  print('Operation failed.');
});

此准则仅适用于匿名和本地的函数。这些函数通常在上下文中立即使用,在上下文中,很清楚未使用的参数代表什么。相反,顶层函数和方法声明没有该上下文,因此必须命名它们的参数,以便清楚每个参数的用途,即使它没有被使用。

DON'T 对非私有标识符使用前导下划线

#

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

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

DON'T 使用前缀字母

#

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

gooddart
defaultTimeout
baddart
kDefaultTimeout

DON'T 显式命名库

#

技术上可以在 library 指令后附加名称,但这是一种遗留功能,不建议使用。

Dart 根据每个库的路径和文件名生成唯一的标记。命名库会覆盖此生成的 URI。没有 URI,工具可能更难找到有疑问的主库文件。

baddart
library my_library;
gooddart
/// A really great test library.
@TestOn('browser')
library;

排序

#

为了使文件的序言保持整洁,我们规定了指令应出现的顺序。每个“部分”应以空行分隔。

单个 Linter 规则处理所有排序准则:directives_ordering

DO 将 dart: 导入放在其他导入之前

#

Linter 规则:directives_ordering

gooddart
import 'dart:async';
import 'dart:collection';

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

DO 将 package: 导入放在相对导入之前

#

Linter 规则:directives_ordering

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

import 'util.dart';

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

#

Linter 规则:directives_ordering

gooddart
import 'src/error.dart';
import 'src/foo_bar.dart';

export 'src/error.dart';
baddart
import 'src/error.dart';
export 'src/error.dart';
import 'src/foo_bar.dart';

DO 按字母顺序排序各部分

#

Linter 规则:directives_ordering

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

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

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

格式化

#

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

DO 使用 dart format 格式化您的代码

#

格式化是乏味的工作,并且在重构期间特别耗时。幸运的是,您不必为此担心。我们提供了一个复杂的自动化代码格式化程序,称为 dart format,它可以为您完成这项工作。Dart 的官方空格处理规则是dart format 生成的任何内容格式化程序常见问题解答可以提供有关其强制执行的样式选择的更多见解。

其余的格式化准则适用于 dart format 无法为您修复的少数事项。

CONSIDER 更改您的代码以使其更易于格式化

#

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

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

PREFER 每行不超过 80 个字符

#

Linter 规则:lines_longer_than_80_chars

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

如果您真的发现自己想要超过 80 个字符的行,我们的经验是您的代码可能过于冗长,并且可以更简洁一些。主要的罪魁祸首通常是 VeryLongCamelCaseClassNames。问问自己,“该类型名称中的每个单词是否告诉我一些关键信息或防止名称冲突?” 如果不是,请考虑省略它。

请注意,dart format 默认设置为 80 个或更少的字符,尽管您可以配置默认值。它不会拆分长字符串文字以适应 80 列,因此您必须手动执行此操作。

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

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

DO 对所有流程控制语句使用花括号

#

Linter 规则:curly_braces_in_flow_control_structures

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

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

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

gooddart
if (arg == null) return defaultValue;

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

gooddart
if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}
baddart
if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;