内容

有效 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_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

好的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;