内容

Effective Dart:样式

优秀的代码中一个非常重要的部分是优秀的样式。一致的命名、排序和格式化有助于使相同的内容看起来相同。它利用了我们大多数人在视觉系统中拥有的强大的模式匹配硬件。如果我们在整个 Dart 生态系统中使用一致的样式,那么我们所有人都可以更轻松地相互学习和贡献代码。

标识符

#

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

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

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

  • lowercase_with_underscores 名称仅使用小写字母(即使是缩写),并用 _ 分隔单词。

请使用 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 { ... }

使用 UpperCamelCase 命名扩展。

#

Linter 规则:camel_case_extensions

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

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

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

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

#

Linter 规则:file_namespackage_names

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

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

使用 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;
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

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

gooddart
var count = 3;

HttpRequest httpRequest;

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

优先为常量名称使用 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();
}
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。

为避免这种情况,缩略词和缩写应像普通单词一样大写。

例外:像 IO(输入/输出)这样的两个字母缩略词完全大写:IO。另一方面,像 ID(标识)这样的两个字母缩写仍然像普通单词一样大写:Id

gooddart
class HttpConnection {}
class DBIOPort {}
class TVVcr {}
class MrRogers {}

var httpRequest = ...
var uiHandler = ...
var userId = ...
Id id;
dart
class HTTPConnection {}
class DbIoPort {}
class TvVcr {}
class MRRogers {}

var hTTPRequest = ...
var uIHandler = ...
var userID = ...
ID iD;

首选使用 ___ 等作为未使用的回调参数

#

有时回调函数的类型签名需要一个参数,但回调实现并不使用该参数。在这种情况下,将未使用的参数命名为 _ 是惯用的。如果函数有多个未使用的参数,请使用其他下划线以避免名称冲突:_____ 等。

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

此准则仅适用于匿名且局部的函数。这些函数通常在明确未使用的参数所代表内容的上下文中立即使用。相比之下,顶级函数和方法声明没有该上下文,因此必须对它们的参数进行命名,以便明确每个参数的用途,即使它没有被使用。

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

#

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

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

不要使用前缀字母

#

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

gooddart
defaultTimeout
dart
kDefaultTimeout

不要明确命名库

#

从技术上来说,可以给 library 指令附加一个名称,但这是一个遗留特性,不建议使用。

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

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

排序

#

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

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

应在其他导入之前放置 dart: 导入

#

Linter 规则:directives_ordering

gooddart
import 'dart:async';
import 'dart:html';

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

应在相对导入之前放置 package: 导入

#

Linter 规则:directives_ordering

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

import 'util.dart';

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

#

Linter 规则:directives_ordering

gooddart
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

gooddart
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问题。

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

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

gooddart
if (arg == null) return defaultValue;

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

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