Dart高效編程

文章目錄

轉載註明出處:https://blog.csdn.net/skysukai

本文摘抄翻譯自Dart官方網站“Effective Dart”部分,選取比較常用的部分翻譯出來,供大家參考。
原文將Dart高效編程分成了四個部分來給出建議:
1、編程風格指南(Style Guide)
2、文檔指南(Documentation Guide)
3、使用指南(Usage Guide)
4、設計指南(Design Guide)
我也將按照這個順序來摘抄翻譯。

1、編程風格指南

1.1 標識符

標識符在Dart中有三種風格:
UpperCamelCase大寫每個單詞的第一個字母,包括第一個字母
lowerCamelCase名稱大寫每個單詞的第一個字母,除了第一個單詞總是小寫,即使它是一個首字母縮寫詞
lowercase_with_underscores只使用小寫字母,即使是首字母縮略詞,每個單詞用_分隔

1.1.1 使用UpperCamelCase來命名類型

1.1.2 使用lowercase_with_underscores命名庫,包,目錄和源文件

1.1.3 使用lowercase_with_underscores命名導入前綴

1.1.4 使用lowerCamelCase命名其他標識符

1.1.5 常量名稱優先使用lowerCamelCase

1.1.6 首字母縮略詞和縮寫詞比兩個字母更長

大寫的首字母縮略詞可能難以閱讀,而多個相鄰的首字母縮略詞可能會導致模糊的名稱。 例如,如果名稱以HTTPSFTP開頭,則無法判斷它是指HTTPS FTP還是HTTP SFTP。
爲了避免這種情況,首字母縮略詞和縮寫詞像普通詞一樣大寫,除了兩個字母的首字母縮略詞。 (像ID和Mr.這樣的兩個字母縮寫仍然像文字一樣大寫。)
比如:
HttpConnectionInfo
uiHandler
IOStream
HttpRequest
Id
DB

而不是:
HTTPConnection
UiHandler
IoStream
HTTPRequest
ID
Db

1.1.7 請勿對非私有標識符使用前置下劃線

Dart在標識符中使用前導下劃線將成員和頂級聲明標記爲私有。 這會訓練開發者將前置下劃線與其中一種聲明相關聯。 他們看到“  _  ”就會想到“private”。
局部變量,參數或庫前綴沒有“private”的概念。 當其中一個名稱以下劃線開頭時,它會向讀者發送一個令人困惑的信號。 爲避免這種情況,請勿在這些名稱中使用前置下劃線。

1.1.8 不要使用前綴字母

匈牙利符號和其他方案出現在BCPL時,當編譯器沒有做太多工作幫助您理解代碼。 因爲Dart可以告訴您聲明的類型,範圍,可變性和其他屬性,所以沒有理由在標識符名稱中對這些屬性進行編碼。
寫成:
defaultTimeout
而不是:
kDefaultTimeout

1.2 順序

1.2.1 帶"dart"的導入放在所有導入之前

比如:
import ‘dart:async’;
import ‘dart:html’;
import ‘package:bar/bar.dart’;
import ‘package:foo/foo.dart’;

1.2.2 "external package"在其他導入之前

1.2.3 在所有導入後,請在單獨的部分中指定導出

比如:
import ‘src/error.dart’;
import ‘src/foo_bar.dart’;
export ‘src/error.dart’;

1.2.4 按字母順序分開排序導入的庫

1.3 格式

像許多語言一樣,Dart忽略了空格。 但是,人類沒有。 具有一致的空格樣式有助於確保人類讀者以與編譯器相同的方式查看代碼。

1.3.1 使用dartfmt格式化代碼

格式化是一項繁瑣的工作,在重構過程中特別耗時。 幸運的是,你不必擔心它。 我們提供了一個名爲dartfmt的複雜自動代碼格式化程序,它可以爲您完成。 我們有一些關於它適用的規則的文檔,但是Dart的官方空白處理規則是dartfmt產生的。
其餘格式指南適用於dartfmt無法爲您修復的一些內容。

1.3.2 考慮更改代碼以使其更易於格式化

1.3.2 避免行超過80個字符

1.3.3 請對所有流控制語句使用花括號

2、文檔指南

2.1 註釋

2.1.1 註釋應是一個句子

比如:
// Not if there is nothing before it.
if (_chunks.isEmpty) return false;

2.1.2 不要將塊註釋用於文檔

寫成:
greet(name) {
  // Assume we have a valid name.
  print(‘Hi, $name!’);
}

而不是:
greet(name) {
  /* Assume we have a valid name. */
  print(‘Hi, $name!’);
}

您可以使用塊註釋(/ * … * /)暫時註釋掉一段代碼,但所有其他註釋應該使用//。

2.2 文檔註釋

Doc評論特別方便,因爲dartdoc會解析它們並從中生成漂亮的doc頁面。 doc註釋是在聲明之前出現的任何註釋,並使用dartdoc查找///的特殊語法。

2.2.1 使用/// 註釋來記錄成員和類型

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

而不是:
// The number of characters in this chunk when unsplit.
int get length => …

2.2.2 爲公共API編寫文檔註釋

2.2.3 考慮編寫庫級文檔註釋

2.2.4 考慮爲私有API編寫文檔註釋

2.2.5 請用單句摘要開始文檔註釋

使用以句點結尾的簡短,以用戶爲中心的描述開始您的doc註釋。 句子片段通常就足夠了。 爲讀者提供足夠的上下文來定位這些註釋,並決定是否應該繼續閱讀或尋找辦法。
比如:
/// Deletes the file at [path] from the file system.
void delete(String path) {
  …
}

而不是:
/// Depending on the state of the file system and the user’s permissions,
/// certain operations may or may not be possible. If there is no file at
/// [path] or it can’t be accessed, this function throws either [IOError]
/// or [PermissionError], respectively. Otherwise, this deletes the file.
void delete(String path) {
  …
}

2.2.6 將文檔註釋的第一句分開到自己的段落中

在第一句之後添加一個空行,將其拆分爲單獨的段落。 如果多於一句解釋是有用的,請將其餘部分放在後面的段落中。
這有助於您編寫一個簡短的第一句話來總結文檔。 此外,像Dartdoc這樣的工具使用第一段作爲類和列表列表等地方的簡短摘要。
比如:
/// Deletes the file at [path].
///
/// Throws an [IOError] if the file could not be found. Throws a
/// [PermissionError] if the file is present but could not be deleted.
void delete(String path) {
   …
}

而不是:
/// Deletes the file at [path]. Throws an [IOError] if the file could not
/// be found. Throws a [PermissionError] if the file is present but could
/// not be deleted.
void delete(String path) {
   …
}

2.2.7 避免與周圍context的冗餘

類的文檔註釋的讀者可以清楚地看到類的名稱,它實現的接口等。當讀取成員的文檔時,簽名就在那裏,而封閉的類是顯而易見的。 這些都不需要在文檔註釋中拼寫出來。 相反,專注於解釋讀者不知道的內容。
比如:
class RadioButtonWidget extends Widget {
  /// Sets the tooltip to [lines], which should have been word wrapped using
  /// the current font.
  void tooltip(List lines) {
    …
  }
}

而不是:
class RadioButtonWidget extends Widget {
  /// Sets the tooltip for this radio button widget to the list of strings in
  /// [lines].
  void tooltip(List lines) {
    …
  }
}

2.2.8 用第三人稱動詞啓動函數或方法註釋

2.2.9 用帶有名詞短語的變量、getter或setter開始註釋

2.2.10 在起始庫或用名詞短語鍵入註釋

2.2.11 考慮在文檔註釋中添加代碼示例

比如:
/// Returns the lesser of two numbers.
///
/// dart
/// min(5, 3) == 3
///
num min(num a, num b) => …

2.2.12 請在文檔註釋中使用方括號來引用範圍內標識符

2.2.13 使用方括號來解釋參數,返回值和異常

其他語言使用詳細標記和部分來描述方法的參數和返回值
/// Defines a flag with the given name and abbreviation.
///
/// @param name The name of the flag.
/// @param abbr The abbreviation for the flag.
/// @returns The new flag.
/// @throws ArgumentError If there is already an option with
/// the given name or abbreviation.
Flag addFlag(String name, String abbr) => …

Dart中的約定是將其集成到方法的描述中,並使用方括號突出顯示參數。
/// Defines a flag.
///
/// Throws an [ArgumentError] if there is already an option named [name] or
/// there is already an option using abbreviation [abbr]. Returns the new flag.
Flag addFlag(String name, String abbr) => …

2.2.14 請在元數據註釋之前放置文檔註釋

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

而不是:
@Component(selector: ‘toggle’)
/// A button that can be flipped on and off.
class ToggleComponent {}

2.3 Markdown

您可以在文檔註釋中使用大多數markdown格式,dartdoc將使用markdown包相應地處理它。
有很多指南已經向您介紹Markdown。 它普遍受歡迎是我們選擇它的原因。 這裏只是一個簡單的例子,讓您瞭解所支持的內容:
/// This is a paragraph of regular text.
///
/// This sentence has two emphasized words (italics) and two
/// strong ones (bold).
///
/// A blank line creates a separate paragraph. It has some inline code
/// delimited using backticks.
///
/// * Unordered lists.
/// * Look like ASCII bullet lists.
/// * You can also use - or +.
///
/// 1. Numbered lists.
/// 2. Are, well, numbered.
/// 1. But the values don’t matter.
///
/// * You can nest lists too.
/// * They must be indented at least 4 spaces.
/// * (Well, 5 including the space after ///.)
///
/// Code blocks are fenced in triple backticks:
///
/// /// this.code /// .will /// .retain(its, formatting); ///
///
/// The code language (for syntax highlighting) defaults to Dart. You can
/// specify it by putting the name of the language after the opening backticks:
///
/// html /// <h1>HTML is magical!</h1> ///
///
/// Links can be:
///
/// * http://www.just-a-bare-url.com
/// * with the URL inline
/// * [or separated out][ref link]
///
/// [ref link]: http://google.com
///
/// # A Header
///
/// ## A subheader
///
/// ### A subsubheader
///
/// #### If you need this many levels of headers, you’re doing it wrong

2.3.1 避免過度使用markdown

2.3.2 避免使用HTML進行格式化

2.3.3 首選代碼塊的反引號圍欄

2.4 寫代碼

2.4.1 儘量簡潔

2.4.2 避免縮寫和首字母縮略詞,除非它們是顯而易見的

2.4.3 首先使用“this”而不是“the”來引用成員的實例

記錄類的成員時,通常需要返回調用該成員的對象。 使用“the”可能含糊不清。
class Box {
  /// The value this wraps.
  var _value;
  /// True if this box contains a value.
  bool get hasValue => _value != null;
}

3、使用指南

3.1 庫

3.1.1 使用part of指令

許多Dart開發人員完全避免使用part。 當每個庫是單個文件時,他們發現更容易推理出他們的代碼。 如果您確實選擇使用part將庫的一部分拆分到另一個文件中,Dart要求另一個文件依次指示它是哪個庫的一部分。由於遺留原因,Dart允許指令的這一部分使用它所屬的庫的名稱。 這使得工具更難以物理地查找主庫文件,並且可能使部件實際上屬於哪個庫的模糊不清。
首選的現代語法是使用直接指向庫文件的URI字符串,就像在其他指令中使用一樣。 如果您有一些庫,my_library.dart,其中包含:
library my_library;
part “some/other/file.dart”;
然後這部分文件應如下所示:
part of “…/…/my_library.dart”;
而不是:
part of my_library;

3.1.2 不要導入另一個包的src目錄中的庫

3.1.3 在您自己的包的lib目錄中導入庫時,首選相對路徑

當從同一個包中的另一個庫引用包的lib目錄中的庫時,相對URI或顯式包:將起作用。
例如,假設你的目錄結構如下:
my_package
└─ lib
├─ src
│ └─ utils.dart
└─ api.dart
如果api.dart想要導入utils.dart,它應該使用:
import ‘src/utils.dart’;
而不是:
import ‘package:my_package/src/utils.dart’;
沒有深刻的理由偏愛前者 - 它只是更短,我們希望保持一致性。
“在您自己的包的lib目錄中”部分很重要。 lib中的庫可以導入lib(或其子目錄)中的其他庫。 lib之外的庫可以使用相對導入來到lib之外的其他庫。 遵循以下兩條規則:
導入路徑永遠不應包含/ lib /。
lib下的庫永遠不應該使用…/來轉義lib目錄。

3.2 布爾型

3.2.1 請用??將null轉換爲布爾值

當表達式可以計算true,false或null時,此規則適用,並且您需要將結果傳遞給不接受null的內容。 常見的情況是將null-aware方法調用用作條件的結果:
if (optionalThing?.isEnabled) {
  print(“Have enabled thing.”);
}

如果optionalThing爲null,則此代碼拋出異常。 要解決此問題,您需要將null值“轉換”爲true或false。 雖然您可以使用==執行此操作,但我們建議使用??:
// If you want null to be false:
optionalThing?.isEnabled ?? false;
// If you want null to be true:
optionalThing?.isEnabled ?? true;。

而不是:
// If you want null to be false:
optionalThing?.isEnabled == true;
// If you want null to be true:
optionalThing?.isEnabled != false;

這兩個操作產生相同的結果並做正確的事情,但?? 是有三個主要原因的首選:
?? 運算符清楚地表明代碼與空值有關。
= = true看起來像一個常見的新程序員錯誤,其中等式運算符是冗餘的並且可以被刪除。 當左邊的布爾表達式不會產生null時是真的,但是當它不能產生時卻不。
如果表達式爲null,則?? 返回false和??返回true清楚地顯示將使用的值。 使用= = true,您必須通過布爾邏輯來實現這意味着將null轉換爲false。

3.3 String

以下是在Dart中編寫字符串時要記住的一些最佳實踐

3.3.1 使用相鄰的字符串來連接字符串文字

如果你有兩個字符串文字 - 不是值,而是實際引用的文字形式 - 你不需要使用+來連接它們。 就像在C和C ++中一樣,只需將它們放在一起就可以了。 這是製作多行的單個長字符串的好辦法。
比如:
raiseAlarm(
'ERROR: Parts of the spaceship are on fire. Other ’
  ‘parts are overrun by martians. Unclear which are which.’);

而不是:
raiseAlarm('ERROR: Parts of the spaceship are on fire. Other ’ +
  ‘parts are overrun by martians. Unclear which are which.’);

3.3.2 首選使用插值來組合字符串和值

如果您習慣其他語言,那麼您習慣使用+的長鏈來構建文字和其他值的字符串。 這在Dart中有效,但使用插值幾乎總是更清晰,更短:
比如:
‘Hello, $name! You are ${year - birth} years old.’;
而不是:
'Hello, ’ + name + ‘! You are ’ + (year - birth).toString() + ’ y…’;

3.3.3 避免在不需要時使用花括號進行插值

如果您要插入一個簡單的標識符,而不是緊跟更多的字母數字文本,則應省略{}。
比如:
‘Hi, $name!’
“Wear your wildest $decade’s outfit.”
‘Wear your wildest ${decade}s outfit.’

而不是:
‘Hi, ${name}!’
“Wear your wildest ${decade}'s outfit.”

3.4 集合

開箱即用,Dart支持四種集合類型:列表,地圖,隊列和集合。 以下最佳實踐適用於集合。

3.4.1 儘可能使用集合

有兩種方法可以創建一個空的可擴展list:[]List()。 同樣,有三種方法可以創建一個空的linked hash map:{}Map()LinkedHashMap()
如果要創建不可擴展的列表或其他一些自定義集合類型,那麼請務必使用構造函數。 核心庫公開了那些構造函數以便於採用,但慣用的Dart代碼不使用它們。
比如:
var points = [];
var addresses = {};

而不是:
var points = List();
var addresses = Map();

如果重要的話,你甚至可以爲它們提供一個類型參數。
比如:
var points = <Point>[];
var addresses = <String, Address>{};

而不是:
var points = List();
var addresses = Map<String, Address>();

請注意,這條建議不適用於這些類的命名構造函數。 List.from(),Map.fromIterable()和相似的方法有他們自己的用法。 同樣,如果您將大小傳遞給List()以創建不可增長的集合,那麼使用它是有意義的。

3.4.2 不要使用.length來查看集合是否爲空

Iterable規則不要求集合知道其長度或能夠在恆定時間內提供它。 調用.length只是爲了查看集合是否包含任何內容可能會非常緩慢。
相反,有更快,更可讀的getter:.isEmpty和.isNotEmpty。 使用不需要綁定結果。
比如:
if (lunchBox.isEmpty) return ‘so hungry…’;
if (words.isNotEmpty) return words.join(’ ‘);

而不是:
if (lunchBox.length == 0) return ‘so hungry…’;
if (!words.isEmpty) return words.join(’ ');

3.4.3 考慮使用高階方法轉換序列

如果你有一個集合並想從中生成一個新的修改過的集合,那麼使用.map(),. where()以及Iterable上的其他方便方法通常更短,更具說明性。
使用這些而不是命令循環表明你的意圖是產生一個新序列而不產生副作用。
比如:
var aquaticNames = animals
  .where((animal) => animal.isAquatic)
  .map((animal) => animal.name);

與此同時,這可能會走得太遠。 如果您正在鏈接或嵌套許多高階方法,那麼編寫一大塊命令性代碼可能會更加清晰。

3.4.4 使用帶有函數文字的Iterable.forEach()

forEach()函數在JavaScript中被廣泛使用,因爲內置的for-in循環不能達到你通常想要的效果。 在Dart中,如果要迭代序列,那麼慣用的方法就是使用循環。
比如:
for (var person in people) {
  …
}

而不是:
people.forEach((person) {
  …
});

請注意,這條建議特別指出“函數文字”。 如果要在每個元素上調用一些已存在的函數,forEach()就可以了。
people.forEach(print);
另請注意,使用Map.forEach()始終可以使用。 Maps不可iterable,因此本條建議不適用。

3.4.5 除非您打算更改結果的類型,否則請勿使用List.from()

給定Iterable,有兩種顯而易見的方法可以生成包含相同元素的新List:
var copy1 = iterable.toList();
var copy2 = List.from(iterable);
明顯的區別是第一個更短。 重要的區別是第一個保留了原始對象的類型參數:
比如:
// Creates a List:
var iterable = [1, 2, 3];
// Prints “List”:
print(iterable.toList().runtimeType);

而不是:
// Creates a List:
var iterable = [1, 2, 3];
// Prints “List”:
print(List.from(iterable).runtimeType);

如果要更改類型,則調用List.from()非常有用:
var numbers = [1, 2.3, 4]; // List.
numbers.removeAt(1); // Now it only contains integers.
var ints = List.from(numbers);

但是如果你的目標只是複製iterable並保留其原始類型,或者你不關心類型,那麼使用toList()。

3.4.6 請使用whereType()按類型過濾集合

假設您有一個包含對象混合的列表,並且您希望只獲取整數。 你可以像這樣使用where():
var objects = [1, “a”, 2, “b”, 3];
var ints = objects.where((e) => e is int);

這是冗長的,但更糟糕的是,它返回一個可能不是你想要的類型的iterable。 在這裏的示例中,它返回一個Iterable <Object>,即使您可能想要一個Iterable <int>,因爲那是您要過濾它的類型。
有時您會看到通過添加cast()來“糾正”上述錯誤的代碼:
var objects = [1, “a”, 2, “b”, 3];
var ints = objects.where((e) => e is int).cast<int>();

這是冗長的,並導致創建兩個包裝器,具有兩層間接和冗餘運行時檢查。 幸運的是,核心庫具有針對此確切用例的whereType()方法:
var objects = [1, “a”, 2, “b”, 3];
var ints = objects.whereType<int>();

使用whereType()是簡潔的,產生所需類型的Iterable,並且沒有不必要的包裝。

3.4.7 當附近的操作有cast轉換時,請勿使用cast()

通常,當您處理可迭代或流時,您可以對其執行多次轉換。 最後,您希望生成具有特定類型參數的對象。 而不是調用cast(),看看是否有一個現有的轉換可以改變類型。
如果您已經調用了toList(),請將其替換爲對List <T> .from()的調用,其中T是您想要的結果列表的類型。
比如:
var stuff = <dynamic>[1, 2];
var ints = List.from(stuff);

而不是:
var stuff = <dynamic>[1, 2];
var ints = stuff.toList().cast<int>();

如果要調用map(),請爲其指定一個顯式類型參數,以便生成所需類型的可迭代。 類型推斷通常會根據您傳遞給map()的函數爲您選擇正確的類型,但有時您需要明確。
比如:
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map<double>((n) => 1 / n);

而不是:
var stuff = <dynamic>[1, 2];
var reciprocals = stuff.map((n) => 1 / n).cast<double>();;

3.4.8 避免使用cast()

這是對先前規則的更軟件的概括。 有時候沒有附近的操作可以用來修復某些對象的類型。 即便如此,儘可能避免使用cast()來“改變”集合的類型。
請改爲選擇以下任何選項:
使用正確的類型創建集合。 更改首次創建集合的代碼,以使其具有正確的類型。
在訪問時強制轉換元素。 如果您立即迭代集合,則在迭代內部轉換每個元素。
強烈推薦使用List.from()。 如果您最終將訪問集合中的大多數元素,並且您不需要該對象轉換到原始對象,請使用List.from()進行轉換。
cast()方法返回一個惰性集合,用於檢查每個操作的元素類型。 如果只對少數元素執行少量操作,那麼懶惰就會很好。 但在許多情況下,延遲驗證和包裝的開銷超過了好處。
以下是使用正確類型創建它的示例:
List<int> singletonList(int value) {
  var list = <int>[];
  list.add(value);
  return list;
}

而不是:
List<int> singletonList(int value) {
  var list = []; // List<dynamic>.
  list.add(value);
  return list.cast<int>();
}

訪問時轉換每個元素:
void printEvens(List<Object> objects) {
  // We happen to know the list only contains ints.
  for (var n in objects) {
    if ((n as int).isEven) print(n);
  }
}

而不是:
void printEvens(List<Object> objects) {
  // We happen to know the list only contains ints.
  for (var n in objects.cast<int>()) {
    if (n.isEven) print(n);
  }
}

使用List.from():
int median(List<Object> objects) {
  // We happen to know the list only contains ints.
  var ints = List<int>.from(objects);
  ints.sort();
  return ints[ints.length ~/ 2];
}

而不是:
int median(List<Object> objects) {
  // We happen to know the list only contains ints.
  var ints = objects.cast<int>();
  ints.sort();
  return ints[ints.length ~/ 2];
}

當然,這些替代方案並不總是有效,有時cast()是正確的答案。 但是考慮到這種方法有點危險和不可取 - 它可能很慢,如果你不小心,可能會在運行時失敗。

3.5 函數

在Dart中,甚至函數都是對象。 以下是一些涉及函數的最佳實踐。

3.5.1 使用函數聲明將函數綁定到名稱

現代語言已經意識到本地嵌套函數和閉包是多麼有用。 在另一個函數中定義一個函數是很常見的。 在許多情況下,此函數立即用作回調,不需要名稱。 函數表達式非常適合。
但是,如果您確實需要爲其命名,請使用函數聲明語句,而不是將lambda綁定到變量。
比如:
void main() {
  localFunction() {
    …
  }
}

而不是:
void main() {
  var localFunction = () {
    …
  };
}

3.5.2 tear-off時不要創建一個lambda

如果你引用一個對象上的方法但省略了括號,Dart會給你一個“tear-off” - 一個閉包,它接受與方法相同的參數,並在你調用它時調用它。
如果您有一個調用方法的函數,該函數具有與傳遞給它的參數相同的參數,則無需手動將調用包裝在lambda中。
比如:
names.forEach(print);
而不是:
names.forEach((name) {
  print(name);
});

3.6 參數

3.6.1 請使用=將命名參數與其默認值分開

由於遺留原因,Dart允許:和=作爲命名參數的默認值分隔符。 爲了與可選的位置參數保持一致,請使用=。
比如:
void insert(Object item, {int at = 0}) { … }
而不是:
void insert(Object item, {int at: 0}) { … }

3.6.2 請勿使用顯式默認值null

如果您將參數設置爲可選但不提供默認值,則該語言隱式使用null作爲默認值,因此無需編寫它。

3.7 變量

以下最佳實踐描述瞭如何在Dart中最好地使用變量。

3.7.1 不要將變量顯式初始化爲null

在Dart中,未自動顯式初始化的變量或字段將初始化爲null。 這是由語言可靠地指定的。 Dart中沒有“未初始化記憶”的概念。 添加= null是多餘的,不需要。

3.7.2 避免存儲您可以計算的內容

在設計類時,您經常希望將多個視圖暴露給相同的基礎狀態。 通常,您會看到在構造函數中計算所有這些視圖的代碼,然後存儲它們:
class Circle {
  num radius;
  num area;
  num circumference;
  Circle(num radius)
    : radius = radius,
    area = pi * radius * radius,
    circumference = pi * 2.0 * radius;
}

這段代碼有兩個錯誤。 首先,它可能會浪費內存。 嚴格來說,area和circumference是高速緩存。 它們是存儲的計算結果,我們可以從我們已有的其他數據重新計算。這兩個參數正在增加內存同時降低CPU使用率。 這是否是一個值得權衡的性能問題?
更糟糕的是,代碼是錯誤的。 緩存的問題是無效 - 您如何知道緩存何時過期並需要重新計算? 在這裏,我們永遠不會這樣做,即使radius是可變的。 您可以指定不同的值,area和circumference將保留其先前的、現在不正確的值。
要正確處理緩存失效,我們需要這樣做:
class Circle {
  num _radius;
  num get radius => _radius;
  set radius(num value) {
     _radius = value;
    _recalculate();
}

  num _area;
  num get area => _area;

  num _circumference;
  num get circumference => _circumference;

  Circle(this._radius) {
    _recalculate();
  }

  void _recalculate() {
    _area = pi * _radius * _radius;
    _circumference = pi * 2.0 * _radius;
   }
}

這是編寫,維護,調試和讀取的大量代碼。 相反,您的第一個實現應該是:
class Circle {
  num radius;

  Circle(this.radius);

  num get area => pi * radius * radius;
  num get circumference => pi * 2.0 * radius;
}

此代碼更短,使用更少的內存,並且更不容易出錯。 它存儲表示圓所需的最少量數據。 沒有字段可以不同步,因爲只有一個來源。
在某些情況下,您可能需要緩存慢速計算的結果,但只有在您知道性能問題後才能執行此操作,請仔細執行,並留下解釋優化的註釋。

3.8 成員

在Dart中,對象具有可以是函數(方法)或數據(實例變量)的成員。 以下最佳實踐適用於對象的成員。

3.8.1 不要在不必要的情況下將字段包裹在getter和setter中。

在Java和C#中,通常把字段隱藏getter和setter(或C#中)後面,即使實現需要用到字段。 這樣,即使這些字段不會被用到,就要產生更多的代碼。 在java中使用getter和直接訪問字段是不同的,甚至在C#訪問屬性與訪問原始字段不是二進制兼容的。
Dart沒有這個限制。 字段和getter / setter是完全區分開的。 您可以在類中公開一個字段,然後將其包裝在getter和setter中,而不必有其他任何使用該字段的代碼。
比如:
class Box {
  var contents;
}

而不是:
class Box {
  var _contents;
  get contents => _contents;
  set contents(value) {
    _contents = value;
  }
}

3.8.2 首選使用final字段創建只讀屬性

如果您有一個外部代碼應該能夠看到但不能分配的字段,那麼在許多情況下有效的簡單解決方案就是將其標記爲final。

3.8.3 考慮使用=>表示簡單成員

除了使用=>作爲函數表達式之外,Dart還允許您使用它來定義成員。 該樣式非常適合僅計算和返回值的簡單成員。
double get area => (right - left) * (bottom - top);

bool isReady(num time) => minTime == null || minTime <= time;

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

編寫代碼的人似乎喜歡=>,但是很容易濫用它並最終得到難以閱讀的代碼。 如果您的聲明超過幾行或包含深層嵌套的表達式 - 級聯和條件運算符是常見的錯誤 - 請大家使用塊體和一些表達式。
您還可以對不返回值的成員使用=>。 通常,當setter很小並且具有使用=>的相應getter時。
num get x => center.x;
set x(num value) => center = Point(value, center.y);

3.8.4 不要使用this. 除了重定向到命名構造函數或避免陰影

JavaScript需要明確這一點。 引用當前正在執行其方法的對象上的成員,但類似Dart的C ++,Java和C#沒有這個限制。

3.8.5 儘可能在聲明中初始化字段

如果某個字段不依賴於任何構造函數參數,則可以並且應該在其聲明中對其進行初始化。 它需要更少的代碼,並確保如果類有多個構造函數,不要忘記初始化字段。

3.9 構造函數

3.9.1 儘可能使用初始化形式

許多字段直接從構造函數參數初始化,如:
class Point {
  num x, y;
  Point(num x, num y) {
    this.x = x;
    this.y = y;
  }
}

我們必須在這裏輸入x四次定義一個字段。 我們可以做得更好:
class Point {
  num x, y;
  Point(this.x, this.y);
}

這裏this. 構造函數參數之前的語法稱爲“初始化形式”。 你不能總是利用它。 有時你希望有一個命名參數,其名稱與您正在初始化的字段的名稱不匹配。 但是當你可以使用初始化形式時,你應該使用它。

3.9.2 初始化不需要參數類型

如果構造函數參數正在使用this.初始化字段,那麼參數的類型應理解爲與字段相同的類型。
比如:
class Point {
  int x, y;
  Point(this.x, this.y);
}

而不是:
class Point {
  int x, y;
  Point(int this.x, int this.y);
}

3.9.3 對於空構造函數體請使用; 而不是{}

3.9.4 別使用new

Dart 2使new關鍵字可選。 即使在Dart 1中,它的含義也從未被清楚,因爲工廠構造函數意味着新的調用可能仍然不會實際返回一個新對象。
但請考慮將其棄用並從代碼中刪除它。
比如:
Widget build(BuildContext context) {
  return Row(
    children: [
      RaisedButton(
        child: Text(‘Increment’),
      ),
      Text(‘Click!’),
    ],
  );
}

而不是:
Widget build(BuildContext context) {
  return new Row(
    children: [
      new RaisedButton(
        child: new Text(‘Increment’),
      ),
      new Text(‘Click!’),
    ],
  );
}

3.9.5 不要冗餘地使用const

在表達式必須是常量的上下文中,const關鍵字是隱式的,不需要寫,也不應該。 這些背景是內在的任何表達:
  const集合文字。
  一個const構造函數調用 元數據註釋。
  const變量聲明的初始化器。
  一個switch case表達式 - 緊接在case之後的部分:而不是case的主體。

基本上,任何寫入new而不是const的錯誤的地方,Dart 2允許你省略const.
比如:
const primaryColors = [
  Color(“red”, [255, 0, 0]),
  Color(“green”, [0, 255, 0]),
  Color(“blue”, [0, 0, 255]),
];

而不是:
const primaryColors = const [
  const Color(“red”, const [255, 0, 0]),
  const Color(“green”, const [0, 255, 0]),
  const Color(“blue”, const [0, 0, 255]),
];

3.10 出錯處理

3.10.1 避免沒有on語句catches

3.10.2 如果沒有on子句,請勿丟棄來自catches捕獲的錯誤

3.10.3 拋出僅針對程序錯誤實現Error的對象

3.10.4 不要顯式地捕獲Error或它的實現類型

3.10.5 請使用rethrow重新拋出捕獲的異常

3.11 異步

3.11.1 在futures上使用async/await

衆所周知,異步代碼很難讀取和調試。 async / await語法提高了可讀性,並允許您使用異步代碼中的所有Dart控制流結構。

3.11.2 如果沒有有用的效果,請不要使用異步

很容易養成在任何與異步相關的函數上使用異步的習慣。 但在某些情況下,它是無關緊要的。 如果可以在不更改函數行爲的情況下省略異步,請執行此操作。
比如:
Future afterTwoThings(Future first, Future second) {
  return Future.wait([first, second]);
}

而不是:
Future afterTwoThings(Future first, Future second) async {
  return Future.wait([first, second]);
}

異步有用的情況包括:

 你正在使用await。 (這是顯而易見的。)

 您將異步返回錯誤。 async然後throw比返回Future.error(...)更短。

 您正在返回一個值,並且您希望將來隱式包裝它。 async比Future.value(...)短。

Future usesAwait(Future later) async {
  print(await later);
}

Future asyncError() async {
  throw ‘Error!’;
}

Future asyncValue() async => ‘value’;

3.11.3 考慮使用高階方法轉換流

這與上述關於迭代的建議相似。 Streams支持許多相同的方法,並且還可以正確處理傳輸錯誤,關閉等操作。

3.11.4 避免直接使用Completer

許多剛接觸異步編程的人想要編寫可以產生future的代碼。 Future中的構造函數似乎不符合他們的需要,因此他們最終找到了Completer類並使用它。
Future<bool> fileContainsBear(String path) {
  var completer = Completer<bool>();
  File(path).readAsString().then((contents) {
    completer.complete(contents.contains(‘bear’));
  });
  return completer.future;
}

兩種低級代碼需要Completer:新的異步原語,以及與不使用future的異步代碼的接口。 大多數其他代碼應該使用async / await或Future.then(),因爲它們更清晰並且使錯誤處理更容易。
Future<bool> fileContainsBear(String path) {
  return File(path).readAsString().then((contents) {
    return contents.contains(‘bear’);
  });
}

Future<bool> fileContainsBear(String path) async {
  var contents = await File(path).readAsString();
  return contents.contains(‘bear’);
}

3.11.5 當消除其類型參數可能是Object的FutureOr <T>時,測試Future <T>

在使用FutureOr <T>執行任何有用的操作之前,通常需要檢查是否有Future <T>或裸T.如果type參數是某個特定類型,如FutureOr <int> ,使用哪個測試無關緊要,是int還是Future <int>。 兩者都有效,因爲這兩種類型是不相交的。
但是,如果值類型是Object或可能使用Object實例化的類型參數,則兩個分支重疊。 Future <Object>本身實現了Object,因此是Object或者是T,其中T是一個可以用Object實例化的類型參數,即使對象是未來,它也會返回true。 相反,明確測試Future案例:
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 as T;
  }
}

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

4、設計指南

這是最軟件,但確範圍最廣的指南。包括了設計一致性、dart庫中可使用的API,同時也包括了簽名和申明的指南。

4.1 名稱

// If you want null to be false:
optionalThing?.isEnabled == true;
// If you want null to be true:
optionalThing?.isEnabled != false;

4.1.1 命名形式上的一致性

4.1.2 避免使用縮寫。如果使用,請正確地使用言簡意賅的縮寫。

4.1.3 建議把最具描述性的名詞放在最後

4.1.4 考慮把代碼寫得像句子,而不帶有歧義

4.1.5 首選非布爾屬性或變量的名詞短語

4.1.5 首選布爾屬性或變量的非命令性動詞短語

4.1.6 考慮使用省略命名布爾參數的動詞

4.1.7 首選布爾屬性或變量的“肯定的”名稱

4.1.8 爲有數據交互和改變的函數和方法選擇一個命令性的動詞短語

4.1.9 如果返回值是其主要目的,則爲函數或方法首選名詞短語或非命令性動詞短語。

4.1.10 如果你在意函數或方法所執行的返回結果,請考慮命令性動詞短語。

4.1.11 避免方法名稱以get開頭

在多數情況下,get應該從getter函數中移除。例如:以breakfastOrder來取代getBreakfastOrder()
即使成員變量有提煉成getter方法的必要,你也應該避免使用getter

4.1.12 如果方法是將對象複製到新對象,則將方法命名爲to___()

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

4.1.13 如果方法返回的對象和原對象代表不同的含義,則將方法命名爲as___()

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

4.1.14 避免使用函數或方法名稱中的名稱來描述一個形參。

如:
list.add(element);
map.remove(key);
而不是:
list.addElement(element);
map.removeKey(key);
也有例外,當用以消除有相似名稱但不同類型參數的歧義時:
map.containsKey(key);
map.containsValue(value);

4.1.15 在命名類型參數時,請遵循現有的助記符約定。

E表示集合中的元素類型:
class IterableBase<E> {}
class List<E> {}
class HashSet<E> {}
K和V表示關聯集合中的鍵和值類型:
class Map<K, V> {}
class Multimap<K, V> {}
class MapEntry<K, V> {}
R表示用作函數的返回類型或類的方法的類型。這種情況並不常見,但有時會出現在typedef中,也會出現在實現訪問者模式的類中。

4.2 庫

4.2.1 首選將聲明設爲私有

4.2.2 考慮將多個類放在一個庫裏

4.3 類和mixins

4.3.1 *避免簡單函數可以完成功能時定義只有一個成員的抽象類

(這條建議特別重要)
與Java不同,Dart具有一流的函數,閉包以及使用它們的輕鬆語法。如果您只需要回調,只需使用一個函數即可。如果您正在定義一個類,並且它只有一個具有無意義名稱的抽象成員,如call或invoke,那麼您很有可能只需要一個函數。
如:
typedef Predicate<E> = bool Function(E element);
而不是:
abstract class Predicate<E> {
   bool test(E element);
}

4.3.2 避免定義只有靜態成員變量的類

在Java和C#中,每個定義都必須在一個類中,因此通常會看到只存在靜態成員變量的“類”。其他類用作命名空間 - 一種爲一堆成員提供共享前綴以使它們彼此關聯或避免名稱衝突的方法。
Dart具有頂級函數,變量和常量,因此您不需要一個類來定義某些東西。如果你只需要一個命名空間,庫可能更爲合適。庫支持importshow/hide的組合使用。這些是強大的工具,讓代碼的使用者以最適合他們的方式處理名稱衝突。
如果函數或變量在邏輯上與類無關,請將其置於頂層。如果您擔心名稱衝突,請爲其指定更精確的名稱,或將其移動到可以使用import前綴導入的單獨庫中。
如:
DateTime mostRecent(List<DateTime> dates) {
  return dates.reduce((a, b) => a.isAfter(b) ? a : b);
}

const _favoriteMammal = ‘weasel’;
而不是:
class DateUtils {
  static DateTime mostRecent(List<DateTime> dates) {
    return dates.reduce((a, b) => a.isAfter(b) ? a : b);
  }
}

class _Favorites {
  static const mammal = ‘weasel’;
}

在慣用的Dart中,類常被定義成各種對象。 從未實例化的類型是壞代碼的味道。
但是,這不是一個嚴格的規定。 對於常量和類似枚舉的類型,將它們分組在類中是很自然的。
如:
class Color {
  static const red = ‘#f00’;
  static const green = ‘#0f0’;
  static const blue = ‘#00f’;
  static const black = ‘#000’;
  static const white = ‘#fff’;
}

4.3.3 避免繼承不打算進行子類化的類

4.3.4 如果你的類可以被繼承增加一些文檔

4.3.5 避免實現一個不打算作爲接口的類

4.3.6 增加文檔,如果您的類可以用作接口

4.3.7 請使用mixin來定義mixin類型

Dart最初沒有單獨的語法來聲明一個類要混合到其他類中。相反,任何滿足某些限制的類(沒有非默認構造函數,沒有超類等)都可以用mixin類型。因爲類的作者可能並不打算將它混合到其他類。
Dart 2.1.0添加了一個mixin關鍵字,用於明確聲明mixin。使用它創建的類型只能用作mixins,並且該語言還可以確保您的mixin保持在限制範圍內。 在定義要用作mixin的新類型時,請使用此語法。
mixin ClickableMixin implements Control {
  bool _isDown = false;
  void click();
  void mouseDown() {
    _isDown = true;
  }
  void mouseUp() {
     if (_isDown) click();
     _isDown = false;
   }
}

您可能仍會遇到使用類來定義mixins的舊代碼,但首選新語法。

4.3.8 避免mixin一個不打算作爲mixin的類型

4.4 構造函數

4.4.1 如果類支持請考慮使用const修飾構造函數

4.5 成員

4.5.1 首選將字段和頂級變量設爲final

4.5.2 請使用getter進行概念上訪問屬性的操作

4.5.3 請使用setter進行概念上改變屬性的操作

4.5.4 不要在沒有相應getter的情況下定義setter

4.5.5 避免從返回類型爲bool,double,int或num的成員返回null

4.5.6 避免從方法中返回this只是爲了啓用流暢的界面

方法級聯是鏈式方法調用的更好解決方案
如:
var buffer = StringBuffer()
  . .write(‘one’)
  . .write(‘two’)
  . .write(‘three’);

而不是:
var buffer = StringBuffer()
  .write(‘one’)
  .write(‘two’)
  .write(‘three’);

4.6 類型

4.6.1 如果類型不明顯,則首選給公共字段和頂級變量添加類型註釋

比如不是:
install(id, destination) => …
而是:
Future install(PackageId id, String destination) => …
但在某些情況下,類型是如此明顯,以至於編寫類型註釋是毫無意義的:
const screenWidth = 640; // Inferred as int.

4.6.2 如果類型不明顯,則首選給私有字段和頂級變量添加類型註釋

4.6.3 避免類型註釋初始化局部變量

局部變量,特別是在函數往往很小的現代代碼中,範圍很小。省略該類型會將讀者的注意力集中在變量更重要的名稱及其初始化值上。
比如:
List<List<Ingredient>> possibleDesserts(Set<Ingredient> pantry) {
  var desserts = <List<Ingredient>>[];
   for (var recipe in cookbook) {
     if (pantry.containsAll(recipe)) {
      desserts.add(recipe);
     }
   }
   return desserts;
}

而不是:
List<List<Ingredient>> possibleDesserts(Set<Ingredient> pantry) {
   List<List<Ingredient>> desserts = <List<Ingredient>>[];
  for (List<Ingredient> recipe in cookbook) {
     if (pantry.containsAll(recipe)) {
         desserts.add(recipe);
     }
  }
  return desserts;
}

如果局部變量沒有初始值設定項,則無法推斷其類型。 在這種情況下,註釋是個好主意。 否則,您將還有泛型可用並失去靜態類型檢查的好處。
List<AstNode> parameters;
if (node is Constructor) {
  parameters = node.signature;
} else if (node is Method) {
  parameters = node.parameters;
}

4.6.4 避免在函數表達式上添加可推斷的參數類型

匿名函數幾乎總是立即參數給傳遞採用某種類型回調的方法。(如果函數沒有立即使用,通常得將它作爲命名聲明。)在類型化context中創建函數表達式時,Dart會嘗試根據預期類型推斷函數的參數類型。
例如,當您將函數表達式傳遞給Iterable.map()時,將根據map()期望的回調類型推斷函數的參數類型:
var names = people.map((person) => person .name);
而不是:
var names = people.map((Person person) => person .name);
在極少數情況下,context環境不夠精確,無法爲一個或多個函數的參數提供類型。 在這些情況下,您可能需要註釋。

4.6.5 避免泛型調用的冗餘類型參數

如果上下文推理將填充類型參數,則手動寫入類型參數是多餘的。 如果調用的是註釋變量的初始化程序,或者是函數的參數,那麼上下文推理通常會爲您填寫類型:
比如:
Set<String> things = Set();
而不是:
Set<String> things = Set<String>();
這裏,變量的類型註釋用於在初始化程序中推斷構造函數調用的類型參數。
在其他情況下,沒有足夠的信息來推斷類型,然後你應該寫類型參數:
var things = Set<String>();
而不是:
var things = Set();
這裏,由於變量沒有類型註釋,因此沒有足夠的上下文來確定要創建的Set類型,因此應該顯式提供type參數。

4.6.6 當Dart推斷錯誤的類型時,請註釋

有時,Dart推斷出一種類型,但不是你想要的類型。 例如,您可能希望變量的類型是初始化程序類型的超類型,以便稍後可以爲變量分配一些其他同級類型:
比如:
num highScore(List<num> scores) {
  num highest = 0;
  for (var score in scores) {
    if (score > highest) highest = score;
  }
   return highest;
}

而不是:
num highScore(List<num> scores) {
  var highest = 0;
  for (var score in scores) {
     if (score > highest) highest = score;
  }
  return highest;
}

這裏,如果分數包含雙精度,如[1.2],那麼最高分配將失敗,因爲它的推斷類型是int,而不是num。 在這些情況下,顯式註釋是有意義的。

4.6.7 首選使用泛型註釋而不是讓編譯器推理失敗

Dart允許您在許多地方省略類型註釋,並嘗試爲您推斷類型。 在某些情況下,如果推理失敗,它會默默地爲您提供dynamic類型。 如果dynamic類型是你想要的類型,這在技術上是最簡潔的方式。
然而,這不是最簡潔的方式。 如果讀者看到註釋缺失的代碼無法知道您是否希望它是dynamic類型的以期編譯器推斷以填充其類型,或者只是忘記編寫註釋。
當dynamic是您想要的類型時,明確地編寫它會使您的意圖清晰。
比如:
dynamic mergeJson(dynamic original, dynamic changes) => …
而不是:
mergeJson(original, changes) => …

4.6.8 在函數類型註釋中首選簽名

標識符函數本身沒有任何返回類型或參數簽名涉及到固定的函數類型。 這種類型僅比使用dynamic類型更有用。 如果要進行註釋,請選擇包含函數參數和返回類型的完整函數類型。
比如:
bool isValid(String value, bool Function(String) test) => …
而不是:
bool isValid(String value, Function test) => …
例外:有時,您需要一個表示多個不同函數類型多個類型。 例如,您可以接受帶有一個參數的函數或帶有兩個參數的函數。 由於我們沒有union類型,因此沒有辦法精確地鍵入它,您通常必須使用dynamic類型。函數至少比這更有用:
比如:
void handleError(void Function() operation, Function errorHandler) {
  try {
     operation();
   } catch (err, stack) {
      if (errorHandler is Function(Object)) {
       errorHandler(err);
    } else if (errorHandler is Function(Object, StackTrace)) {
      errorHandler(err, stack);
     } else {
       throw ArgumentError(“errorHandler has wrong signature.”);
      }
  }
}

4.6.9 請勿指定setter函數的返回類型

Setter總是在Dart中返回void。 寫這個詞毫無意義。
比如:
set foo(Foo value) { … }
而不是:
void set foo(Foo value) { … }

4.6.10 請勿使用舊式typedef語法

Dart有兩個用於爲函數類型定義命名typedef的符號。 原始語法如下:
typedef int Comparisont<T>(T a, T b);
這個語法有許多問題:
無法爲泛型函數類型指定名稱。 在上面的示例中,typedef本身是泛型。 如果在代碼中引用Comparison而沒有類型參數,則隱式獲取函數類型int Function(dynamic,dynamic),而不是int Function<T>(T,T)。 這在實踐中並不常見,但在某些極端情況下很重要。
參數中的單個標識符被解釋爲參數的名稱,而不是其類型。比如:
typedef bool TestNumber(num);
大多數用戶預期這是一個函數類型,它接受一個num並返回bool。 它實際上這是一個函數類型,它接受任何對象(dynamic)並返回bool。 參數的名稱(除了typedef中的文檔之外的任何內容都不使用)是“num”。 這是Dart長期存在的錯誤根源。
新的語法如下:
typedef Comparison<T> = int Function(T, T);
如果要包含參數的名稱,也可以這樣做:
typedef Comparison<T> = int Function(T a, T b);
新語法可以表達舊語法可以表達的任何內容,並且缺少容易出錯的錯誤,其中單個標識符被視爲參數的名稱而不是其類型。在typedef中=之後的相同函數類型語法也允許在類型註釋可能出現的任何地方,這爲我們提供了一種在程序中的任何位置編寫函數類型的一致方法。
Dart仍然支持舊的typedef語法以避免破壞現有代碼,但它已被棄用。

4.6.11 在typedef上首選內聯函數類型

在Dart 1中,如果要爲字段,變量或泛型類型參數使用函數類型,則必須首先爲其定義typedef。 Dart 2支持函數類型語法,可以在允許類型註釋的任何地方使用它:
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;
  }
}

如果函數類型特別長或經常使用,可能仍然值得定義typedef。 但在大多數情況下,用戶希望看到函數類型實際上在哪裏使用,並且函數類型語法使它們清晰。

4.6.12 考慮使用參數的函數類型語法

在定義類型爲函數的參數時,Dart具有特殊語法。 與C類似,您可以使用函數的返回類型和參數簽名來包圍參數的名稱:
Iterable <T> where(bool predicate(T element)) => …
在Dart 2添加函數類型語法之前,這是在不定義typedef的情況下爲參數提供函數類型的唯一方法。 既然Dart有函數類型的通用符號,你也可以將它用於函數類型的參數:
Iterable<T> where(bool Function(T) predicate) => …
新語法稍微冗長一點,但這樣和其他使用新語法的地方保持了一致性。

4.6.13 使用Object註釋而不是dynamic類型來指示允許任何對象

某些操作適用於任何可能的對象。 例如,log()方法可以接受任何對象並在其上調用toString()。 Dart中的兩種類型允許所有值:Object和dynamic類型。 但是,他們傳達了不同的東西。 如果您只想聲明允許所有對象,請使用Object,就像在Java或C#中一樣。
使用dynamic發送更復雜的信號。 這可能意味着Dart的類型系統不夠複雜,無法表示允許的類型集,或者值來自互操作或者在靜態類型系統範圍之外,或者您明確希望運行時dynamic在 程序中的那一點。
void log(Object object) {
  print(object.toString());
}
/// Returns a Boolean representation for [arg], which must
/// be a String or bool.
bool convertToBool(dynamic arg) {
  if (arg is bool) return arg;
  if (arg is String) return arg == ‘true’;
  throw ArgumentError(‘Cannot convert $arg to a bool.’);
}

4.6.14 請使用Future &lt;void&gt;作爲不生成值的異步成員的返回類型

如果您有一個不返回值的同步函數,則使用void作爲返回類型。 對於不生成值但是調用者可能需要等待的方法的異步等價物是Future <void>。
您可能會看到使用Future或Future 的代碼,因爲舊版本的Dart不允許void作爲類型參數。 既然如此,你應該使用它。 這樣做更直接地匹配您鍵入類似同步函數的方式,併爲調用者和函數體提供更好的錯誤檢查。
對於不返回有用值的異步函數以及沒有調用者需要等待異步工作或處理異步失敗的異步函數,請使用返回類型的void。

4.6.15 避免使用FutureOr <T>作爲返回類型

如果一個方法接受一個FutureOr <int>,那麼它接受的是慷慨的。 用戶可以使用int或Future <int>調用該方法,因此無論如何他們都不需要在Future中包裝一個int。
如果返回FutureOr <int>,用戶需要檢查是否可以返回int或Future <int>,然後才能執行任何有用的操作。 (或者他們只是等待價值,實際上總是將其視爲Future。)只需返回Future <int>,它就更清晰了。 用戶更容易理解函數總是異步的或總是同步的,但是一個函數可能很難正確使用。
比如:
Future<int> triple(FutureOr<int> value) async => (await value) * 3;
而不是:
FutureOr<int> triple(FutureOr<int> value) {
  if (value is int) return value * 3;
  return (value as Future<int>).then((v) => v * 3);
}

這條建議指南更精確的表述是僅在逆變位置使用FutureOr <T>。 參數是逆變的,返回類型是協變的。 在嵌套函數類型中,這會被翻轉 - 如果你有一個類型本身就是函數的參數,那麼回調的返回類型現在處於逆變位置,並且回調的參數是協變的。 這意味着回調的類型可以返回FutureOr <T>:
Stream<S> asyncMap<T, S>(
  Iterable<T> iterable, FutureOr<S> Function(T) callback) async* {
  for (var element in iterable) {
    yield await callback(element);
  }
}

4.7 參數

4.7.1 避免布爾類型的可選參數

與其他類型不同,布爾值通常以字面形式使用。 數字之類的東西通常包含在命名常量中,但我們通常只是直接傳遞true和false。 如果不清楚boolean表示的是什麼,這可能使調用點不可讀:
new Task(true);
new Task(false);
new ListBox(false, true, true);
new Button(false);

相反,請考慮使用命名參數,命名構造函數或命名常量來闡明調用正在執行的操作:
Task.oneShot();
Task.repeating();
ListBox(scroll: true, showScrollbars: true);
Button(ButtonState.enabled);

請注意,這不適用於setter,其中名稱清楚表示值代表什麼:
listBox.canScroll = true;
button.isEnabled = false;

4.7.2 如果用戶可能想要省略先前的參數,則避免可選的位置參數

可選的位置參數應該具有邏輯進展,使得較早的參數比後面的參數更頻繁地傳遞。 用戶幾乎不需要顯式傳遞“漏洞”來省略先前的位置參數以便稍後傳遞。 你最好使用命名參數。
String.fromCharCodes(Iterable charCodes, [int start = 0, int end]);

DateTime(int year,
  [int month = 1,
  int day = 1,
  int hour = 0,
  int minute = 0,
  int second = 0,
  int millisecond = 0,
  int microsecond = 0]);

Duration(
  {int days = 0,
  int hours = 0,
  int minutes = 0,
  int seconds = 0,
  int milliseconds = 0,
  int microseconds = 0});

4.7.3 避免接受特殊“無參數”值的強制參數

如果用戶在邏輯上省略了參數,則實際上應該省略它,辦法是使參數可選而不是強制它們傳遞null,空字符串或其他一些意味着“沒有通過”的特殊值。
省略參數更簡潔,有助於防止在用戶認爲提供實際值時偶然傳遞像null這樣的標記值的錯誤。
比如:
var rest = string.substring(start);
而不是:
var rest = string.substring(start, null);

4.7.4 請使用包含開始和獨佔結束參數來接受有範圍的參數

如果要定義允許用戶從某個整數索引序列中選擇一系列元素或項的方法或函數,請使用一個起始索引,該索引引用第一個項和一個(可能是可選的)結束索引,該索引比一個大於 最後一項的索引。

這與執行相同操作的核心庫一致。
比如:
  [0, 1, 2, 3].sublist(1, 3) // [1, 2]
‘abcd’.substring(1, 3) // ‘bc’

在這裏保持一致尤爲重要,因爲這些參數通常是未命名的。 如果您的API需要一個長度而不是一個終點,那麼在調用點上根本不會顯示差異。

4.8 相等

爲類實現自定義相等行爲可能很棘手。 用戶對於對象需要匹配的相等如何工作有着深刻的直覺,而哈希表之類的集合類型具有他們期望元素遵循的微妙約定。

4.8.1 如果複寫==,請複寫hashCode

4.8.2 確保你的==運算符遵守平等的數學規則

4.8.3 避免定義可變類的自定義相等

4.8.4 不要在傳統的 ==運算符中檢查null

Dart指定此檢查是自動完成的,只有在右側不爲空時才調用==方法:
比如:
class Person {
  final String name;
  // ···
  bool operator ==(other) => other is Person && name == other.name;
  int get hashCode => name.hashCode;
}

而不是:
class Person {
  final String name;
  // ···
  bool operator ==(other) => other != null && …
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章