目录

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

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

不要使用前缀字母

#

在 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:html';

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 会为你完成 99% 的工作,但最后的 1% 需要你来完成。它不会拆分长字符串字面量以使其适合 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;