Effective Dart 學(xué)習(xí)筆記

Effective Dart

閱讀 Effective Dart 時(shí)做的一些筆記。

Style Guide

好代碼一定是遵循良好代碼風(fēng)格的代碼。前后一致的命名、排序和格式化使得代碼具有更高的可讀性。在整個(gè) Dart 生態(tài)中維持一個(gè)規(guī)范統(tǒng)一的代碼風(fēng)格,可以使得程序員之間分享彼此的代碼時(shí),更容易閱讀和互相學(xué)習(xí)。

Identifiers

Dart 中標(biāo)識(shí)符有三種類型:

  • UpperCamelCase:大駝峰命名法,每個(gè)單詞首字母大寫。
  • lowerCamelCase:小駝峰命名法,除了首個(gè)單詞,其余每個(gè)單詞首字母大寫。
  • lowercase_with_underscores:帶下劃線的小寫命名法,每個(gè)單詞小寫,用下劃線分割單詞。

DO name types using UpperCamelCase.

類名,枚舉類,typedef,注解等的命名都應(yīng)該采用大駝峰命名法。

class SliderMenu { ... }

enum Status { ... }

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

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

#### DO name extensions using `UpperCamelCase`.

和類型命名一樣,擴(kuò)展方法也應(yīng)該使用大駝峰命名法。

```dart
extension MyFancyList<T> on List<T> { ... }

DO name libraries, packages, directories, and source files using lowercase_with_underscores.

庫(kù)名,包名,文件和文件夾名,都應(yīng)該使用小寫+下劃分隔符命名。

library my_library;

export 'global_constants.dart';
export 'src/common_util/screen_util.dart';

DO name import prefixes using lowercase_with_underscores.

導(dǎo)入包名使用別名時(shí),用小寫+下劃分隔符。

import 'package:angular_components/angular_components' as angular_components;

DO name other identifiers using lowerCamelCase.

頂層方法,變量,類的成員,變量,參數(shù)等,都應(yīng)該使用小駝峰命名法。

var httpRequest;

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

PREFER using lowerCamelCase for constant names.

推薦使用小駝峰命名常量、枚舉類。

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

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

DO capitalize acronyms and abbreviations longer than two letters like words.

大于兩個(gè)字符的縮寫均當(dāng)做普通單詞使用。

class HttpConnection {} // bad: HTTPConnection
class DBIOPort {} // bad: DbIoPort
class TVVcr {}
class MrRogers {}

var httpRequest = ...
var uiHandler = ...
Id id;

PREFER using _, __, etc. for unused callback parameters.

推薦使用 _ 命名未使用的回調(diào)參數(shù)。

futureOfVoid.then((_, __, ___) {
  print('Operation complete.');
});

DON’T use a leading underscore for identifiers that aren’t private.

不要使用下劃線作為標(biāo)識(shí)符的起始字符,只有私有變量和私有函數(shù)才可以使用下劃線開(kāi)頭。

var _notVisible;

_initContentForCaseA() {
  ...
}

DON’T use prefix letters.

不要使用前綴字符。

var defaultTimeout; // bad: kDefaultTimeout

Ordering

為了保持一個(gè) Dart 文件的整潔性,我們應(yīng)該保證每段代碼都按照特定的順序排列,并且每個(gè)區(qū)域都被空白行分割。

DO place “dart:” imports before other imports.

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

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

DO place “package:” imports before relative imports.

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

import 'util.dart';

DO specify exports in a separate section after all imports.

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

export 'src/error.dart';

DO sort sections alphabetically.

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

Formatting

為了使得代碼具有良好的可讀性,我們需要格式化 Dart 代碼。

DO format your code using dart format.

Dart 為我們提供了 dart format 工具格式化代碼,具體見(jiàn)相關(guān)文檔

CONSIDER changing your code to make it more formatter-friendly.

在某些場(chǎng)景下,格式化可能會(huì)失效,比如很長(zhǎng)的標(biāo)識(shí)符、嵌套很深的表達(dá)式、多種操作符的混合使用等,我們應(yīng)該考慮修改代碼使得代碼更加容易被格式化。

AVOID lines longer than 80 characters.

單行代碼越長(zhǎng)越難讀,考慮換行,或者將一些特別長(zhǎng)的標(biāo)識(shí)符簡(jiǎn)寫。

DO use curly braces for all flow control statements.

哪怕只有流程語(yǔ)句中只有一個(gè)表達(dá)式也應(yīng)該使用花括號(hào)。

Documentation Guide

良好的文檔和注釋可以使得閱讀你的代碼的人(包括未來(lái)的你)更容易理解你的代碼。簡(jiǎn)潔、精確的注釋可以節(jié)約一個(gè)人大量的時(shí)間和精力去理解看懂一段代碼所需要的上下文。雖然好的代碼是自解釋的,但是大多數(shù)時(shí)候我們都應(yīng)該寫更多的注釋而不是更少的注釋。

Comment

DO format comments like sentences.

應(yīng)該像寫普通的句子一樣寫注釋,注釋盡量使用英文,首字符前加空格,首字母大寫,每一句都應(yīng)該使用標(biāo)點(diǎn)。如果使用中文,則應(yīng)該在中英文之間使用空格分割。

// Not if there is nothing before it.
if (_chunks.isEmpty) return false;
// 盡量減少使用中文注釋,即使使用也要 use space 分割中英文字符。
if (_chunks.specialCases) return true;

DON’T use block comments for documentation.

Dart 中不推薦使用塊注釋。

greet(name) {
  // Assume we have a valid name.
  print('Hi, $name!');
  // bad:
  /* Assume we have a valid name. */
  print('Hi, $name!');
}

Doc comments

文檔注釋 /// 可以用于方便地生成文檔頁(yè),主要通過(guò) dartdoc 生成。

DO use /// doc comments to document members and types.

對(duì)于類和成員變量變量,使用 /// 注釋,這樣 dartdoc 才能找到它們并解析成文檔。

/// The number of characters in this chunk when unsplit.
int get length => ...

PREFER writing doc comments for public APIs.

不用給每個(gè)類,成員變量寫注釋,但是至少關(guān)鍵部分應(yīng)該寫文檔注釋。

CONSIDER writing a library-level doc comment.

最好在 Dart library 的入口提供文檔注釋,比如 library 的主要功能、術(shù)語(yǔ)解釋、樣本代碼、常用類和方法的鏈接、外部引用資源等。

CONSIDER writing doc comments for private APIs.

一些重要的私有方法也應(yīng)該提供文檔注釋,方便庫(kù)的使用者理解你的代碼。

DO start doc comments with a single-sentence summary.

每個(gè)文檔注釋的開(kāi)頭應(yīng)該使用簡(jiǎn)單、準(zhǔn)確的一句話作為總結(jié)概括。

DO separate the first sentence of a doc comment into its own paragraph.

第一句總結(jié)之后使用空白行分割,可以使得文檔注釋更易讀。

AVOID redundancy with the surrounding context.

為類的成員和方法寫注釋時(shí),避免寫得太過(guò)啰嗦,因?yàn)樽x者對(duì)類的基本用途和上下文已經(jīng)有了解了。

PREFER starting function or method comments with third-person verbs.

使用第三人稱動(dòng)詞作為方法注釋的開(kāi)頭,因?yàn)榉椒ㄒ话愣际菆?zhí)行一項(xiàng)任務(wù),這樣可以讓讀者更快了解方法的用途。

PREFER starting variable, getter, or setter comments with noun phrases.

使用名詞作為變量注釋的開(kāi)頭,因?yàn)樽兞恳话愦硪粭l數(shù)據(jù)。

PREFER starting library or type comments with noun phrases.

使用名詞作為庫(kù)和類型注釋的開(kāi)頭,同樣庫(kù)和類型是一種對(duì)象火種功能的抽象。

CONSIDER including code samples in doc comments.

復(fù)雜的代碼中如果包含示例代碼有助于讀者理解你的代碼。

DO use square brackets in doc comments to refer to in-scope identifiers.

文檔注釋中使用中括號(hào)引用當(dāng)前上下文中代碼的鏈接。

/// Throws a [StateError] if ...
/// similar to [anotherMethod()], but ...
/// Similar to [Duration.inDays], but handles fractional days.
/// To create a point from polar coordinates, use [Point.polar()].

DO use prose to explain parameters, return values, and exceptions.

Java 中一般使用 @param, @returns, @throws 等注解來(lái)注釋參數(shù)和返回值等,在 Dart 中不要這么做,推薦使用直白文字的敘述方法的功能,參數(shù)以及特殊的地方。

DO put doc comments before metadata annotations.

文檔注釋應(yīng)該在注解之前。

/// A button that can be flipped on and off.
@Component(selector: 'toggle')
class ToggleComponent {}

Markdown

Dart 中支持大多數(shù)常見(jiàn)的 markdown 語(yǔ)法。

AVOID using markdown excessively.

When in doubt, format less. Formatting exists to illuminate your content, not replace it. Words are what matter. (說(shuō)得太經(jīng)典了,拿小本本記下來(lái)(??????))

AVOID using HTML for formatting.

大多數(shù)情況下,應(yīng)該只使用 markdown 語(yǔ)法就夠了。

PREFER backtick fences for code blocks.

我們可以使用 inline code 或者 ``` code blocks,如果是小段的代碼可以使用前者,如果是大段的代碼塊,推薦使用后者。

Writing

作為程序員,雖然我們每天主要和計(jì)算機(jī)打交道,但是我們寫得絕大多數(shù)代碼都是給人讀的,寫代碼需要練習(xí),寫作也同樣需要練習(xí)。

這里介紹一些寫作技巧,更多關(guān)于技術(shù)寫作的技巧,推薦閱讀:Technical writing style.

PREFER brevity.

保證你的文字是清晰和精準(zhǔn)的,同時(shí)保持簡(jiǎn)潔。

AVOID abbreviations and acronyms unless they are obvious.

盡可能少使用縮寫,除非是那種人人都知道的,比如 "i.e.", "e.g.", "etc"。

PREFER using “this” instead of “the” to refer to a member’s instance.

當(dāng)需要代指當(dāng)前類的實(shí)例是時(shí)候,使用 this 代替 the。

class Box {
  /// True if this box contains a value.
  bool get hasValue => _value != null;
}

Usage Guide

Libraries

DO use strings in part of directives.

在 Dart 中,我們可以使用 part of 將代碼分離到另一個(gè)文件中,然后使用 part 引用這部分分離出去的代碼,推薦的做法是,直接使用 URI 鏈接到指定的文件,而不是只指定庫(kù)的名字。

part of '../../my_library.dart';

// bad:
part of my_library;

DON’T import libraries that are inside the src directory of another package.

我們應(yīng)該直接導(dǎo)入庫(kù),而不是導(dǎo)入庫(kù)中的某個(gè)文件或者目錄,因?yàn)閹?kù)作者可能對(duì)目錄結(jié)構(gòu)做修改。

DON’T allow an import path to reach into or out of lib.

比如使用相對(duì)路徑引用 lib 之外的某個(gè)文件,或者 lib 之外的某個(gè)文件(比如 test 文件夾)導(dǎo)入 lib 內(nèi)的文件,這兩種情況都應(yīng)該避免,應(yīng)該只使用包導(dǎo)入的方式導(dǎo)入依賴。

import 'package:my_package/api.dart';

// bad:
import '../lib/api.dart';

PREFER relative import paths.

如果無(wú)法使用包導(dǎo)入,才使用相對(duì)路徑的方式導(dǎo)入。

test/api_test.dart:

import 'test_utils.dart'; // 在當(dāng)前同一個(gè)目錄結(jié)構(gòu)下,可以使用相對(duì)路徑

Null

DON’T explicitly initialize variables to null.

Dart 會(huì)自動(dòng)為可為空的變量賦值為 null,而不可為空的變量在初始化之前被使用的話會(huì)報(bào)錯(cuò)。所以沒(méi)必要為變量賦空值。

DON’T use an explicit default value of null.

與上一條類似,如果你為一個(gè)可選位置參數(shù)的值可為空,那么 Dart 會(huì)為它自動(dòng)賦值為空值,沒(méi)必要手動(dòng)賦值為空。

PREFER using ?? to convert null to a boolean value.

使用 ?? 將空值轉(zhuǎn)換為布爾值,好處更簡(jiǎn)潔易懂。看例子:

// If you want null to be false:
if (optionalThing?.isEnabled ?? false) {
  print("Have enabled thing.");
}

// If you want null to be true:
if (optionalThing?.isEnabled ?? true) {
  print("Have enabled thing or nothing.");
}

// 第二種情況等同于下面這種寫法,所以使用 ?? 明顯可以簡(jiǎn)化寫法
if (optionalThing?.isEnabled == null || optionalThing!.isEnabled!) {
    print("Have enabled thing or nothing.");
}

AVOID late variables if you need to check whether they are initialized.

使用 late 關(guān)鍵字可以讓我們延遲初始化某些變量,但是這樣我們就沒(méi)法判斷某個(gè)變量是否初始化了,當(dāng)需要做這樣的判斷時(shí)候,最好的做法是不要使用 late 關(guān)鍵字,而是使用 nullable 變量。

// 使用 late 關(guān)鍵字
late Type a;
bool initialized = false;

initialize() {
  a = Type('A');
  initialized = true;
}

doSomeWorkEnsureInitialized() {
  if (a == null) {
    if (!initialized) {
      intialize();
    }
  }
  doWork();
}

// 不使用 late 關(guān)鍵字
Type? a;

doSomeWorkEnsureInitialized() {
  if (a == null) {
    initialize();
  }
  doWork();
}

CONSIDER copying a nullable field to a local variable to enable type promotion.

Dart 中有類型提升的機(jī)制,但是只適用于本地變量,因此,對(duì)于可為空的成員變量我們應(yīng)該先將其拷貝成本地變量,然后再使用。

final Response? response;

@override
String toString() {
  var response = this.response;
  if (response != null) {
    return "Could not complete upload to ${response.url} "
        "(error code ${response.errorCode}): ${response.reason}.";
  }
  return "Could not upload (no response).";
}

但是要注意如果本地變量發(fā)生變化,要將其重新賦值到成員變量上。而且成員變量有可能在被拷貝之后發(fā)生了變化,則拷貝的本地變量已經(jīng)「過(guò)時(shí)」了。

Strings

DO use adjacent strings to concatenate string literals.

Dart 中不需要使用 + 來(lái)連接兩個(gè)字符,像 C 和 C++ 中一樣,相鄰的字符串會(huì)自動(dòng)被拼接成同一個(gè)字符串。

print('content1,''content2,');
print('Some very very long string text which cannot be put into one line, '
      'but can be put into adjacent line to be concatenated together without +');

PREFER using interpolation to compose strings and values.

'Hello, $name! You are ${year - birth} years old.'; // good
'Hello, ' + name + '! You are ' + (year - birth).toString(); // bad!

Collections

DO use collection literals when possible.

創(chuàng)建集合的時(shí)候使用字面量表達(dá)式,而不是默認(rèn)構(gòu)造器,因?yàn)樽置媪勘磉_(dá)式還支持?jǐn)U展表達(dá)式和集合 if 和集合 for 的操作。

// good:
var points = <Point>[];
var addresses = <String, Address>{};
var counts = <int>{};

// bad:
var points = List<Point>.empty(growable: true);
var addresses = Map<String, Address>();
var counts = Set<int>();

DON’T use .length to see if a collection is empty.

使用 isEmptyisNotEmpty 代替 .length。

if (lunchBox.isEmpty) return 'so hungry...';
if (words.isNotEmpty) return words.join(' ');

AVOID using Iterable.forEach() with a function literal.

使用 for..in 代替 forEach()。

// good:
for (var person in people) {
  ...
}

// bad:
people.forEach((person) {
  ...
});

DON’T use List.from() unless you intend to change the type of the result.

使用 iterable.toList() 代替 List.from(iterable)。

var copy1 = iterable.toList();
var copy2 = List.from(iterable);

只有在需要修改集合類型的時(shí)候才使用 List.from()

var numbers = [1, 2.33, 4]; // List<num>.
numbers.removeAt(1); // Now it only contains integers.
var ints = List<int>.from(numbers);

DO use whereType() to filter a collection by type.

使用 whereType() 根據(jù)類型篩選集合。

var objects = [1, "a", 2, "b", 3];
var ints = objects.whereType<int>();

DON’T use cast() when a nearby operation will do.

集合方法很多都支持泛型,所以除非必要,不要使用 cast() 進(jìn)行類型轉(zhuǎn)換。

var stuff = <dynamic>[1, 2];
var ints = List<int>.from(stuff);
// var ints = stuff.toList().cast<int>();

var reciprocals = stuff.map<double>((n) => 1 / n);
// var reciprocals = stuff.map((n) => 1 / n).cast<double>();

AVOID using cast().

如非必要,盡量減少使用 cast() 轉(zhuǎn)換集合類型,考慮使用以下方法代替:

  • 直接用目標(biāo)類型創(chuàng)建集合。

  • 遍歷元素,對(duì)單個(gè)元素使用 cast()(使用時(shí)才轉(zhuǎn)換)。

  • 盡量使用 List.from() 代替 cast()。

cast() 方法會(huì)返回一個(gè)集合并且在每次操作時(shí)檢查元素類型,如果你只在集合的少量元素上做操作則適合使用 cast() 方法,其它情況下,這種轉(zhuǎn)換的性能開(kāi)銷都比較大。

Functions

DO use a function declaration to bind a function to a name.

當(dāng)使用局部方法的時(shí)候,如果需要有方法名,盡量直接使用方法聲明,避免使用 lambd 表達(dá)式+變量給方法命名。

void main() {
  // good:
  localFunction() {
    ...
  }
  
  // bad:
  var localFunction = () {
    ...
  };
}

DON’T create a lambda when a tear-off will do.

當(dāng)使用匿名函數(shù)的時(shí)候,如果函數(shù)調(diào)用的方法所需的參數(shù)和函數(shù)的參數(shù)一致,則可以使用 tear-off 的語(yǔ)法,類似于 Java 中的方法引用。

// good:
names.forEach(print);

// bad:
names.forEach((name) {
  print(name);
});

DO use = to separate a named parameter from its default value.

由于歷史遺留問(wèn)題,Dart 中允許使用 : 為參數(shù)設(shè)置默認(rèn)值,最好的做法是用 =。

// good:
void insert(Object item, {int at = 0}) { ... }

// bad:
void insert(Object item, {int at: 0}) { ... }

Variables

DO follow a consistent rule for var and final on local variables.

絕大多數(shù)局部變量都不需要有類型標(biāo)注,而是直接使用 var 或者 final 關(guān)鍵字聲明。我們可以選擇以下兩個(gè)原則的其中一個(gè),不要同時(shí)使用兩種方式:

  • 原則 A:如果是不會(huì)被重新賦值的變量,則使用 final 關(guān)鍵字,會(huì)被重新賦值的則使用 var 關(guān)鍵字。
  • 原則 B:所有的局部變量一律都使用 var 關(guān)鍵字,只有頂層變量和成員變量才使用 final 關(guān)鍵字。

推薦原則 B,更簡(jiǎn)單,且容易遵循。

AVOID storing what you can calculate.

原因是浪費(fèi)內(nèi)存,推薦使用 getters 代替。

class Circle {
  double radius;

  Circle(this.radius);

  // 不要使用成員變量來(lái)保存需要計(jì)算得到的值,使用 getters
  double get area => pi * radius * radius;
  double get circumference => pi * 2.0 * radius;
}

Members

Dart 中,對(duì)象的成員可以是方法或者變量。

DON’T wrap a field in a getter and setter unnecessarily.

Dart 中訪問(wèn)屬性和訪問(wèn) getters/setters 的效果是一樣的,每個(gè)屬性默認(rèn)會(huì)自動(dòng)生成 getters/setters,所以沒(méi)必要手動(dòng)去寫 getters/setters,如果是為了使屬性不可訪問(wèn),則應(yīng)該使用私有屬性。

PREFER using a final field to make a read-only property.

// good:
class Box {
  final contents = [];
}

// bad:
class Box {
  var _contents;
  get contents => _contents;
}

CONSIDER using => for simple members.

使用箭頭表達(dá)式定義成員變量,最常見(jiàn)的用法是用于創(chuàng)建 getters。

double get right => left + width;
set right(double value) => left = value - width;

String capitalize(String name) =>
    '${name[0].toUpperCase()}${name.substring(1)}';

DON’T use this. except to redirect to a named constructor or to avoid shadowing.

只有以下幾種情況下才使用 this 關(guān)鍵字:

  • 構(gòu)造器中引用成員變量;
  • 構(gòu)造器重定向到某個(gè)具名構(gòu)造器時(shí);
  • 成員變量與局部變量或者參數(shù)同名時(shí);

有趣的是,在構(gòu)造器初始化列表中,可以區(qū)分出同名參數(shù),所以不需要使用 this

class Box extends BaseBox {
  var value;

  Box(value)
      : value = value,
        super(value);
}

DO initialize fields at their declaration when possible.

如果成員屬性不依賴構(gòu)造器初始化,則應(yīng)該盡量在聲明處進(jìn)行初始化。

class ProfileMark {
  final String name;
  final DateTime start = DateTime.now();

  ProfileMark(this.name);
  ProfileMark.unnamed() : name = '';
}

Constructors

DO use initializing formals when possible.

在構(gòu)造器參數(shù)前使用 this. 叫做 initializing formal

class Point {
  double x, y;
  Point(this.x, this.y);
}

DON’T use late when a constructor initializer list will do.

如果成員變量會(huì)在構(gòu)造器中進(jìn)行初始化,就應(yīng)該避免將其標(biāo)記為 late。

DO use ; instead of {} for empty constructor bodies.

構(gòu)造器方法體為空時(shí)使用 ; 結(jié)束構(gòu)造器,避免使用空方法體。

DON’T use new.

避免使用 new 關(guān)鍵字,同樣是 Dart 的歷史遺留問(wèn)題。

DON’T use const redundantly.

以下幾種情況下,不需要使用 const 關(guān)鍵字:

  • 在一個(gè) const 的集合中;
  • 在一個(gè) const 構(gòu)造器中,其中每個(gè)參數(shù)都是 const 的;
  • 在元數(shù)據(jù)注解中;
  • const 常量的初始化器;
  • 在 switch..case 表達(dá)式中,case 之后 : 之前的值默認(rèn)也是 const 的;

Error handling

AVOID catches without on clauses.

如果 catch 子句沒(méi)有使用 on 關(guān)鍵字限定捕捉的異常類型,則會(huì)捕捉所有類型的異常,這樣我們就沒(méi)法得到程序出錯(cuò)的具體原因了,到底是 stackoverflow 還是參數(shù)異常,又或者是斷言條件未滿足?所以,哪怕是只捕捉 Exception 也比捕捉全部異常好,Exception 代表程序運(yùn)行時(shí)異常,排除了編碼錯(cuò)誤造成的異常。

DON’T discard errors from catches without on clauses.

如果你執(zhí)意捕捉所有異常,請(qǐng)不要丟棄異常內(nèi)容,至少打印一下好嗎?

DO throw objects that implement Error only for programmatic errors.

所有的編碼異常都繼承自 Error 類,如果是運(yùn)行時(shí)異常造成的錯(cuò)誤,則應(yīng)該拋出運(yùn)行時(shí)異常,盡量在編碼異常中排除掉運(yùn)行時(shí)異常。

DON’T explicitly catch Error or types that implement it.

實(shí)現(xiàn)了 Error 類的大多是編碼異常,這類異??梢愿嬖V我們程序出錯(cuò)的信息,通常不應(yīng)該捕捉這類異常。

DO use rethrow to rethrow a caught exception.

throw 會(huì)重置錯(cuò)誤棧信息,而 rethrow 則不會(huì)。

try {
  somethingRisky();
} catch (e) {
  if (!canHandle(e)) rethrow;
  handle(e);
}

Asynchrony

PREFER async/await over using raw futures.

async / await 語(yǔ)法可以提升代碼的可讀性,并且是的程序更容易 debug。

DON’T use async when it has no useful effect.

雖然異步場(chǎng)景下優(yōu)先推薦使用 async,但是注意不要濫用 async。滿足以下條件時(shí)才推薦使用 async

  • 使用了 await.(廢話)
  • 需要異步返回異常,使用 async 比使用 Future.error() 更簡(jiǎn)潔。
  • 需要返回一個(gè) Future 對(duì)象,使用 async 比使用 Future.value() 更簡(jiǎn)潔。

CONSIDER using higher-order methods to transform a stream.

Stream 和集合類似,包含一些諸如 every, any, distinct 等便捷的轉(zhuǎn)換操作,避免手動(dòng)轉(zhuǎn)換。

AVOID using Completer directly.

Completer 是比較底層的類,應(yīng)該避免使用,大多數(shù)場(chǎng)景下用 async/await 就足夠了。

DO test for Future<T> when disambiguating a FutureOr<T> whose type argument could be Object.

在使用 FutureOr<T> 之前,你通常需要先用 is 關(guān)鍵字判斷其類型。因?yàn)?T 有可能是 Object,而 FutureOr<> 本身也是 Object,所以要用 Future<T> 作為判斷依據(jù)。

Future<T> logValue<T>(FutureOr<T> value) async {
  if (value is Future<T>) {
    var result = await value;
    print(result);
    return result;
  } else {
    print(value);
    return value;
  }
}

Design Guide

Names

DO use terms consistently.

在整個(gè)代碼庫(kù)中保持用相同的名稱命名相同的物體,盡量使用被大眾接受或者共同認(rèn)可的方式命名。

pageCount         // A field.
updatePageCount() // Consistent with pageCount.
toSomething()     // Consistent with Iterable's toList().
asSomething()     // Consistent with List's asMap().
Point             // A familiar concept.

AVOID abbreviations.

盡量避免使用縮寫,除非是非常常見(jiàn)的縮寫。

PREFER putting the most descriptive noun last.

最后一個(gè)單詞一定是最具描述性、最能總結(jié)該類用途的名詞。

StatelessWidget
DownloadPage
AnimationController
OutlineInputBorder

CONSIDER making the code read like a sentence.

盡量使你的代碼讀起來(lái)可以像讀文章一樣易懂。

if (serviceTable.isEmpty) {
  serviceTable.addAll(waitingList.where(
          (customer) => customer.waitingMinutes > 30));
}

PREFER a noun phrase for a non-boolean property or variable.

盡量使用名詞命名非布爾類型的屬性或變量。

PREFER a non-imperative verb phrase for a boolean property or variable.

使用非祈使動(dòng)詞命名布爾類型的屬性或變量。布爾類型的變量一般代表某種狀態(tài)或者能力。

isEmpty
hasElements
canClose
closesWindow
canShowPopup
hasShownPopup

CONSIDER omitting the verb for a named boolean parameter.

具名參數(shù)一般可以省略動(dòng)詞,使用形容詞。

var copy = List.from(elements, growable: true);
var copy = List.from(elements, canGrow: true); // 哪一種更好?

PREFER the “positive” name for a boolean property or variable.

對(duì)于布爾值,盡量使用有價(jià)值、有意義、有用的值作為 true 值等。

if (socket.isConnected && database.hasData) {
  socket.write(database.read());
}

PREFER an imperative verb phrase for a function or method whose main purpose is a side effect.

使用祈使動(dòng)詞短語(yǔ)命名那些具有 "side effect" 的方法名,side effect 即會(huì)改變對(duì)象的內(nèi)部狀態(tài),比如屬性等,或者產(chǎn)生一些新數(shù)據(jù),或者會(huì)引起外部其它對(duì)象發(fā)生變化等等。

queue.removeFirst();
window.refresh();

PREFER a noun phrase or non-imperative verb phrase for a function or method if returning a value is its primary purpose.

如果方法的主要用途是返回值,那么應(yīng)該使用名詞短語(yǔ)或者非祈使動(dòng)詞短語(yǔ)作為方法名。

var element = list.elementAt(3);
var first = list.firstWhere(condition);
var char = string.codeUnitAt(4);

CONSIDER an imperative verb phrase for a function or method if you want to draw attention to the work it performs.

當(dāng)方法不產(chǎn)生任何 side effect 但是會(huì)比較消耗資源或者做一些有可能出錯(cuò)的操作時(shí),也要使用祈使動(dòng)詞短語(yǔ)命名。

var table = database.downloadData();
var packageVersions = packageGraph.solveConstraints();

AVOID starting a method name with get.

大多數(shù)方法都應(yīng)該直接省略 get 直接描述方法作用,比如使用 packageSortOrder() 而不是 getPackageSortOrder()。

PREFER naming a method toXxx() if it copies the object’s state to a new object.

如果方法的主要用途是將一個(gè)對(duì)象復(fù)制并轉(zhuǎn)換為另一個(gè)對(duì)象時(shí),盡量使用 toXxx() 命名。

list.toSet();
stackTrace.toString();
dateTime.toLocal();

PREFER naming a method asXxx() if it returns a different representation backed by the original object.

如果方法的主要用途是基于一個(gè)對(duì)象包裝轉(zhuǎn)換成另一個(gè)對(duì)象時(shí),盡量使用 asXxx() 命名。

var map = table.asMap();
var list = bytes.asFloat32List();
var future = subscription.asFuture();

AVOID describing the parameters in the function’s or method’s name.

方法名中不要帶有參數(shù)相關(guān)的信息。

// good:
list.add(element);
map.remove(key);

// bad:
list.addElement(element);
map.removeKey(key);

只有在為了區(qū)分多種功能類似的方法時(shí)才可以忽略該原則:

map.containsKey(key);
map.containsValue(value);

DO follow existing mnemonic conventions when naming type parameters.

使用現(xiàn)有的助記規(guī)則命名類型參數(shù),比如 E 代表集合中的 Element,K/V 代表 Map 的 key/value,R 代表類或方法的 return 值,其它情況使用 T/S/U/N/E 等。

Libraries

Dart 中使用下劃線 __ 代表成員是庫(kù)私有的,這不僅僅是約定俗成的,更是在語(yǔ)法層面做出的規(guī)定。

PREFER making declarations private.

對(duì)于庫(kù)的作者而言,這點(diǎn)尤為重要,要盡可能減少暴露接口給庫(kù)的使用者,只暴露必須使用到的接口。

CONSIDER declaring multiple classes in the same library.

Dart 中每個(gè)文件都是一個(gè) library,但是不像 Java 等語(yǔ)言一個(gè)文件通常只能代表一個(gè)類,Dart 中可以在相同的 library 中包含多個(gè)類、頂層變量和方法,只要這些類、變量和方法的確相互聯(lián)系并且構(gòu)成一個(gè)功能模塊就可以了。

將多個(gè)類放在一起有諸多好處。因?yàn)樗接性L問(wèn)權(quán)限僅限于庫(kù)層級(jí),而不是類層級(jí),所以在同一個(gè)庫(kù)中是可以訪問(wèn)其它類中的私有屬性和方法的。

Classes and mixins

Dart 是一個(gè)純面向?qū)ο蟮恼Z(yǔ)言,這意味這所有對(duì)象都是類的實(shí)例。但是另一方面,Dart 有可以像面向過(guò)程或者函數(shù)式編程一樣擁有頂層方法和變量。

AVOID defining a one-member abstract class when a simple function will do.

當(dāng)類中只有一個(gè)方法或者變量的時(shí)候,考慮使用頂層變量或者方法代替。

// good:
typedef Predicate<E> = bool Function(E element);

// bad:
abstract class Predicate<E> {
  bool test(E element);
}

AVOID defining a class that contains only static members.

Java C# 等語(yǔ)言必須將方法、變量和常量定義在類之中,比如 Java 中,我們常常會(huì)用一個(gè) Constants 類來(lái)保存全局使用到的一些靜態(tài)常量。而且為了避免命名沖突,我們常常會(huì)使用類名區(qū)分相同名字的方法。Dart 中沒(méi)有這樣的限制,相反,Dart 使用 library 作為命名空間,并且在導(dǎo)入包的時(shí)候可以使用 as/show/hide 等關(guān)鍵字避免沖突的命名。

我們可以將靜態(tài)方法或靜態(tài)成員變量轉(zhuǎn)換成頂層方法或常量,然后以合適的方式命名或者整理進(jìn)同一個(gè)庫(kù)中。當(dāng)然,這個(gè)規(guī)則不一定必須要遵循,一些場(chǎng)合下可能還是使用類和靜態(tài)成員變量更合適:

class Color {
  static const red = '#f00';
  static const green = '#0f0';
  static const blue = '#00f';
  static const black = '#000';
  static const white = '#fff';
}

AVOID extending a class that isn’t intended to be subclassed.

有些類可能本意就不是為了被繼承而設(shè)計(jì)的,所以盡量用合適的命名告訴庫(kù)的使用者哪些類可以被繼承而哪些類不行。

DO document if your class supports being extended.

同上,如果一個(gè)類可以被繼承,至少用注釋說(shuō)明一些需要注意的地方。

AVOID implementing a class that isn’t intended to be an interface.

同樣的,如果一個(gè)接口不應(yīng)該被實(shí)現(xiàn),而使用者卻實(shí)現(xiàn)了這個(gè)接口,那么當(dāng)未來(lái)庫(kù)的作者對(duì)這個(gè)接口做任何改動(dòng),都會(huì)影響到使用者的原有的實(shí)現(xiàn)。

DO document if your class supports being used as an interface.

如果一個(gè)接口可以被實(shí)現(xiàn),需要在文檔注釋中說(shuō)明。

DO use mixin to define a mixin type.

Dart 從版本 2.1.0 之后才添加了 mixin 關(guān)鍵字,在這之前,任何沒(méi)有默認(rèn)構(gòu)造器、沒(méi)有父類的類都可以被用做 mixin,這帶來(lái)了一個(gè)問(wèn)題,有些類可能并不適合用做 mixin,誤用它們可能會(huì)帶來(lái)一些問(wèn)題。而使用了 mixin 關(guān)鍵字之后,mixin 類只能被用做 mixins,而不能用做其它用途。

AVOID mixing in a type that isn’t intended to be a mixin.

同上。

Constructors

Dart 中構(gòu)造器是一類特殊的方法,用于創(chuàng)建類的實(shí)例,它的方法名和類名相同,沒(méi)有返回值,并且除此之外還可以用 . 分割,后面加標(biāo)識(shí)符,這類構(gòu)造器叫具名構(gòu)造器。

CONSIDER making your constructor const if the class supports it.

如果類中的成員變量都是 final 的,而且構(gòu)造器只是初始化了這些成員變量,那么可以用 const 修飾構(gòu)造器。這樣,使用該類的開(kāi)發(fā)者就可以在需要使用常量的地方使用該類的對(duì)象了,比如在其它常量容器中,switch..case 中,默認(rèn)參數(shù)值等等。

class Pet {
  final String name;
  
  const Pet(this.name);
}

class PetShop {
  Pet pet;

  // Error: The default value of an optional parameter must be constant.
  // PetShop({this.pet = Pet('Cat')}); 
  
  PetShop([this.pet = const Pet('Cat')]);
}

Members

PREFER making fields and top-level variables final.

盡量將成員變量和頂層變量設(shè)為 final 的。

DO use getters for operations that conceptually access properties.

在 API 設(shè)計(jì)中,使用 getters 還是使用方法作為訪問(wèn)某個(gè)數(shù)據(jù)的方式是個(gè)微妙但重要的部分。Dart 中成員變量會(huì)自動(dòng)生成 getters,所以訪問(wèn)成員變量和訪問(wèn) getters,其本質(zhì)是一樣的。所以,通常來(lái)說(shuō),訪問(wèn) getters 給人的感覺(jué)就像是訪問(wèn)成員變量,它意味著:

  • 這種操作不需要接受參數(shù),但是有返回值;
  • 調(diào)用者只關(guān)心返回值;
  • 這種操作并不會(huì)造成任何用戶可見(jiàn)的 side effects;
  • 這種操作具有冪等性,即無(wú)論調(diào)用多少次,結(jié)果相同;
  • 返回的結(jié)果對(duì)象不會(huì)暴露原始對(duì)象的所有狀態(tài);

如果你的目標(biāo)操作符合以上所有特點(diǎn),則可以將這種操作定義成一個(gè) getter 而不是方法。

DO use setters for operations that conceptually change properties.

與 getters 類似,使用 setters 也會(huì)遇到類似的困境,不同的是 setters 需要滿足 filed-like 特質(zhì):

  • 操作只接受一個(gè)參數(shù),且不會(huì)產(chǎn)生返回值;
  • 操作只會(huì)改變對(duì)象的某些狀態(tài);
  • 操作具有等冪性;

DON’T define a setter without a corresponding getter.

一個(gè)可被修改的 setter 往往對(duì)應(yīng)著一個(gè)供訪問(wèn)的 getter。

AVOID using runtime type tests to fake overloading.

Dart 中沒(méi)有重載機(jī)制,有的人會(huì)定義一個(gè)方法,在方法中用 is 判斷類型然后根據(jù)具體的類型做一些操作。這種操作雖然能達(dá)到目的,但是最好的做法還是使用一系列獨(dú)立的方法,讓用戶根據(jù)不同的類型調(diào)用不同的方法。只有當(dāng)一個(gè)對(duì)象具體類型不確定,需要在運(yùn)行時(shí)根據(jù)不同的類型來(lái)調(diào)用特定的子類方法的時(shí)候,才可以把它們定義在一個(gè)方法內(nèi)。

AVOID public late final fields without initializers.

不像其它 final 成員變量,late final 的成員變量如果沒(méi)有初始化器的話,Dart 會(huì)為它們生成 setters 函數(shù),如果成員變量是 public 的,則意味著 setters 也是 public 的。我們將成員變量設(shè)置為 late 通常是希望稍后再去初始化它,比如在構(gòu)造器中,而 late final 使得成員變量可能在被初始化之前就在外部被初始化了一遍。所以,最好的做法是:

  • 不要使用 late
  • 使用 late 但是在聲明時(shí)為其初始化;
  • 使用 late 但是將它標(biāo)記為 private 的,同時(shí)為其提供一個(gè) getter;

AVOID returning nullable Future, Stream, and collection types.

如果返回的是容器類型,盡量避免返回空的容器類型,一般使用返回空的容器或者直接返回 null。

AVOID returning this from methods just to enable a fluent interface.

使用級(jí)聯(lián)表達(dá)式而不是在方法中返回 this 實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。

Types

我們使用類型標(biāo)注限制某部分代碼能夠使用什么樣的值。類型通常出現(xiàn)在兩個(gè)地方:變量聲明處的類型標(biāo)注 (type annotations) 和使用泛型時(shí)的類型參數(shù) (generic invocations)。

類型標(biāo)注通常就是我們所認(rèn)為的靜態(tài)類型,我們可以對(duì)變量、參數(shù)、屬性、返回值使用類型標(biāo)注,就像下面這個(gè)例子:

bool isEmpty(String parameter) { // 返回值和參數(shù)的類型標(biāo)注
  bool result = parameter.isEmpty; // 變量的類型標(biāo)注
  return result;
}

泛型調(diào)用時(shí)的類型參數(shù)可以是創(chuàng)建集合字面量,或者是調(diào)用泛型類的構(gòu)造方法,或者是調(diào)用泛型方法。比如:

var lists = <num>[1, 2]; // 創(chuàng)建集合時(shí)指定類型
lists.addAll(List<num>.filled(3, 4)); // 調(diào)用泛型類的構(gòu)造器并指定類型
lists.cast<int>(); // 調(diào)用泛型方法

Type inference

類型標(biāo)注是可選的,因?yàn)?Dart 會(huì)根據(jù)當(dāng)前上下文推斷出具體類型。當(dāng)缺乏足夠的信息推斷出類型的時(shí)候,Dart 默認(rèn)會(huì)使用 dynamic 類型。這種機(jī)制讓類型推斷看起來(lái)是安全的,但實(shí)際上使得類型檢查完全失效了。

同時(shí)擁有類型推斷和 dynamic 類型,使得我們?cè)谡f(shuō)代碼是無(wú)類型的 (untyped) 時(shí)產(chǎn)生歧義,一個(gè)變量到底是動(dòng)態(tài)類型還是沒(méi)有寫類型參數(shù)?所以,我們一般不說(shuō)代碼是無(wú)類型的,而是使用以下術(shù)語(yǔ)代替:

  • 如果代碼擁有類型標(biāo)注,則它的類型即所標(biāo)注的類型(廢話)。
  • 如果代碼是推斷類型,則說(shuō)明 Dart 已經(jīng)確定其類型。而如果類型推斷失敗,那么我們不把稱它為 inferred
  • 如果代碼是動(dòng)態(tài)類型的,那么它的靜態(tài)類型就是 dynamic。這種情況下,代碼既可以是主動(dòng)被標(biāo)注為 dynamic 也可以是推斷類型(使用 var 關(guān)鍵字)。

換句話說(shuō),代碼是標(biāo)注類型還是推斷類型,與它是否被標(biāo)注為 dynamic 或者其它類型無(wú)關(guān)。

類型推斷是個(gè)強(qiáng)有力的工具,可以幫助我們編碼或閱讀代碼時(shí)跳過(guò)一些顯而易見(jiàn)的部分(代碼類型),讓我們關(guān)注真正重要的代碼邏輯。但是,顯式的代碼類型也同樣重要,它可以幫助我們寫出健壯、可維護(hù)的代碼。

當(dāng)然,類型推斷也不是萬(wàn)能藥,一些情況下還是應(yīng)該使用類型標(biāo)注。有時(shí)候類型推斷提前確定了變量類型,但是該類型不是你想要的,比如變量在初始化后推斷出了類型,但是你實(shí)際卻想要使用另一個(gè)類型,這種情況下就只能使用顯式的類型標(biāo)注了。

理解上面這些概念之后,方便我們?cè)诮忉尳酉聛?lái)的這些原則時(shí),不會(huì)造成歧義。首先,我們可以將大致的原則總結(jié)為以下幾點(diǎn):

  • 當(dāng)上下文不足以推斷出類型的時(shí)候,請(qǐng)使用類型標(biāo)注,即使你想要的是 dynamic 類型;
  • 不要標(biāo)注局部變量或者泛型調(diào)用;
  • 對(duì)于頂層變量和屬性,盡量顯式標(biāo)注其類型,除非初始化器使得它們的類型顯而易見(jiàn);

DO type annotate variables without initializers.

如果沒(méi)有變量沒(méi)有立即被初始化,請(qǐng)使用類型標(biāo)注。

DO type annotate fields and top-level variables if the type isn’t obvious.

如果變量類型不是顯而易見(jiàn)的,也要使用類型標(biāo)注。

顯而易見(jiàn)包括以下這些情況:

  • 字面量,如基本數(shù)據(jù)類型等
  • 構(gòu)造器中的參數(shù)
  • 引用其它變量或者常量
  • 簡(jiǎn)單的表達(dá)式,比如 isEmpty, ==, > 等等
  • 工廠方法,比如 int.parse(), Future.wait() 等

另外,當(dāng)你覺(jué)得類型標(biāo)注可以使你的代碼更清晰時(shí),那就請(qǐng)使用類型標(biāo)注。

When in doubt, add a type annotation.

DON’T redundantly type annotate initialized local variables.

有初始化器的局部變量不要使用類型標(biāo)注。只有當(dāng)你確定推斷類型不是你想要的類型的時(shí)候才使用類型標(biāo)注。

DO annotate return types on function declarations.

給方法返回值添加類型標(biāo)注可以方便方法的調(diào)用者。當(dāng)然,匿名方法就沒(méi)必要了。

DO annotate parameter types on function declarations.

給方法的參數(shù)添加類型標(biāo)注,同樣很有必要,可以幫助方法的調(diào)用者確定參數(shù)的邊界。

需要注意的是,Dart 不會(huì)對(duì)可選的參數(shù)做類型推斷,來(lái)源

void sayRepeatedly(String message, {int count = 2}) {
  for (var i = 0; i < count; i++) {
    print(message);
  }
}

DON’T annotate inferred parameter types on function expressions.

Dart 通??梢愿鶕?jù)上下文確定匿名方法接收的參數(shù)是什么,所以匿名方法一般不需要添加類型標(biāo)注。

var names = people.map((person) => person.name);

DON’T type annotate initializing formals.

之前說(shuō)過(guò)構(gòu)造器中使用 this. 給屬性賦值的形式叫做 initializing formals,這種情況下也不要使用類型標(biāo)注。

class Point {
  double x, y;
  Point(this.x, this.y);
}

DO write type arguments on generic invocations that aren’t inferred.

一些情況下,泛型的類型無(wú)法被確定,比如空的集合,所以我們需要為它們標(biāo)注類型。

var playerScores = <String, int>{};
final events = StreamController<Event>();

// 對(duì)于成員變量來(lái)說(shuō),如果類型同樣無(wú)法推斷出,則需要在聲明處標(biāo)注類型
class Downloader {
  final Completer<String> response = Completer();
}

DON’T write type arguments on generic invocations that are inferred.

如果泛型類的類型已經(jīng)推斷出來(lái),就不要在寫類型了。

class Downloader {
  final Completer<String> response = Completer<String>(); // 錯(cuò)誤示例
}

AVOID writing incomplete generic types.

也就是不要使用 raw 泛型。

// bad:
List numbers = [1, 2, 3];
var completer = Completer<Map>();

// good:
List<num> numbers = [1, 2, 3];
var completer = Completer<Map<String, int>>();

DO annotate with dynamic instead of letting inference fail.

顯式標(biāo)明 dynamic 永遠(yuǎn)要比不寫類型標(biāo)注要好。

// good:
dynamic mergeJson(dynamic original, dynamic changes) => ...
// bad:
mergeJson(original, changes) => ...

當(dāng)然,有些情況下,Dart 也能推斷出 dyanmic 類型的:

Map<String, dynamic> readJson() => ...

void printUsers() {
  var json = readJson();
  var users = json['users'];
}

PREFER signatures in function type annotations.

默認(rèn)的 Function 允許任何類型的返回值和參數(shù),如果不帶簽名使用,在有些情況下會(huì)導(dǎo)致錯(cuò)誤。

// good:
bool isValid(String value, bool Function(String) test) => ...
// bad:
bool isValid(String value, Function test) => ...

DON’T specify a return type for a setter.

Dart 中 setters 只會(huì)返回 void,所以不需要寫返回值。

DON’T use the legacy typedef syntax.

又是一個(gè)歷史遺留問(wèn)題,Dart 中有兩種方式定義 typedef,推薦使用新的寫法。

// bad:
typedef int Comparison<T>(T a, T b);
// good:
typedef Comparison<T> = int Function(T a, T b);

PREFER inline function types over typedefs.

Dart 2 開(kāi)始支持 inline function,我們可以直接定義方法作為類型簽名。

class FilteredObservable {
  final bool Function(Event) _predicate;
  final List<void Function(Event)> _observers;

  FilteredObservable(this._predicate, this._observers);

  void Function(Event)? notify(Event event) {
    if (!_predicate(event)) return null;

    void Function(Event)? last;
    for (var observer in _observers) {
      observer(event);
      last = observer;
    }

    return last;
  }
}

如果方法很復(fù)雜或者多次使用的情況下,推薦使用 typedef 代替。

PREFER using function type syntax for parameters.

就像方法可以作為類型標(biāo)注一樣,方法也可以作為參數(shù),并且有特殊的語(yǔ)法支持:

// 函數(shù)形式的參數(shù):返回值 Function(參數(shù)類型) 參數(shù)名
Iterable<T> where(bool Function(T) predicate) => ...

AVOID using dynamic unless you want to disable static checking.

Dart 中 dynamic 是一個(gè)非常特殊的類型,它的作用和 Object? 類似,都允許任何對(duì)象,包括 null,但是 dynamic 還有額外的功能,那就是默認(rèn)允許任何操作,包括對(duì)任何成員的訪問(wèn),無(wú)論這種訪問(wèn)是否有效或者合法,Dart 不會(huì)在編譯期對(duì)其進(jìn)行檢查,如果有異常只會(huì)在運(yùn)行期才會(huì)被拋出。除非你確認(rèn)想要這種效果,否則還是使用 Obejct? 或者 Object 代替 dynamic,然后用 is 對(duì)類型進(jìn)行進(jìn)行檢查和類型提升。

/// Returns a Boolean representation for [arg], which must
/// be a String or bool.
bool convertToBool(Object arg) {
  if (arg is bool) return arg;
  if (arg is String) return arg.toLowerCase() == 'true';
  throw ArgumentError('Cannot convert $arg to a bool.');
}

DO use Future<void> as the return type of asynchronous members that do not produce values.

如果異步方法沒(méi)有值需要返回,請(qǐng)使用 Future<void> 作為返回值。這樣可以保證后續(xù)的操作,還有支持 await 等。

AVOID using FutureOr<T> as a return type.

如果方法接受 FutureOr<int> 作為參數(shù),那么它可以接收 int 或者 Future<int> 作為參數(shù),這樣可以方便調(diào)用者用 Future 包裝 int 后再調(diào)用你的方法。但是,如果你返回 FutureOr<T>,方法的調(diào)用者就需要檢查返回值到底是 int 還是 Future<int>。推薦的做法是直接返回 Future<T>,這樣調(diào)用者可以直接使用 await 獲取異步結(jié)果值。

Future<int> triple(FutureOr<int> value) async => (await value) * 3;

Parameters

AVOID positional boolean parameters.

可選布爾值不但容易讓調(diào)用著分不清參數(shù)的含義,而且容易出錯(cuò)。

// bad:
new ListBox(false, true, true);
// good:
ListBox(scroll: true, showScrollbars: true);

AVOID optional positional parameters if the user may want to omit earlier parameters.

對(duì)于可選位置參數(shù),調(diào)用者可能省略中間或者后面部分,盡量把關(guān)鍵部分寫在前面,或者使用具名位置參數(shù)。

// 調(diào)用方可能省略一部分可選位置參數(shù),因此,最重要的寫在前面
String.fromCharCodes(Iterable<int> charCodes, [int start = 0, int? end]);

// 使用具名位置參數(shù)就沒(méi)有這個(gè)煩惱了
Duration(
    {int days = 0,
    int hours = 0,
    int minutes = 0,
    int seconds = 0,
    int milliseconds = 0,
    int microseconds = 0});

AVOID mandatory parameters that accept a special “no argument” value.

不要強(qiáng)制用戶傳 null,使用可選參數(shù)代替。

// bad:
var rest = string.substring(start, null);
// good:
var rest = string.substring(start);

DO use inclusive start and exclusive end parameters to accept a range.

當(dāng)方法接收的參數(shù)用數(shù)字下標(biāo)表示范圍時(shí),盡量采用前閉后開(kāi)的習(xí)俗,包括開(kāi)頭下標(biāo)但是不包括結(jié)尾的下標(biāo)。

[0, 1, 2, 3].sublist(1, 3) // [1, 2]
'abcd'.substring(1, 3) // 'bc'

Equality

DO override hashCode if you override ==.

這是約定俗成的。兩個(gè)對(duì)象相同則說(shuō)明它們的哈希值一致,否則類似于 Map 等基于哈希值的集合就無(wú)法使用了。

DO make your == operator obey the mathematical rules of equality.

  • 自反性:a == a 永遠(yuǎn)返回 true;
  • 對(duì)稱性:a == b 為 true 時(shí) b == a 也必定為 true;
  • 傳遞性:a == bb == c 都為 true,則 a == c 也為 true;

AVOID defining custom equality for mutable classes.

如果是可變的對(duì)象,比如擁有可變屬性的對(duì)象,他們的哈希值會(huì)隨著屬性的變化而變化,但是大多數(shù)基于哈希的集合沒(méi)有考慮到這一點(diǎn),因此,最好不要自定義可變對(duì)象的相等性。

DON’T make the parameter to == nullable.

Dart 語(yǔ)言中 null 只能等于 null,因此,使用 == 比較對(duì)象時(shí),右邊的對(duì)象不能是 null。

class Person {
  final String name;

  // bad:
  bool operator ==(Object? other) =>
      other != null && other is Person && name == other.name;
  
  // good:
  bool operator ==(Object other) => other is Person && name == other.name;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 基本數(shù)據(jù)類型 Number intint a = 1;int b = 0xDEFFFF; doubledouble...
    清無(wú)閱讀 486評(píng)論 0 0
  • 語(yǔ)法學(xué)習(xí)筆記。資料參考:官方文檔(英文)(要直接看英文文檔,中文文檔可能是機(jī)器翻譯的,很多地方語(yǔ)句不通順,埋坑無(wú)數(shù)...
    SingleDigit閱讀 232評(píng)論 0 0
  • 如何閱讀指南 DO 應(yīng)始終遵循的準(zhǔn)則 DON'T 不應(yīng)該這么使用的準(zhǔn)則 PREFER 應(yīng)該遵循的準(zhǔn)則,但是在某些情...
    _白羊閱讀 3,213評(píng)論 0 3
  • 彩排完,天已黑
    劉凱書(shū)法閱讀 4,452評(píng)論 1 3
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過(guò)就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,412評(píng)論 2 7

友情鏈接更多精彩內(nèi)容