Dart-最佳實踐

Style?

UpperCamelCase:每個單詞的首字母大寫,包括第一個字母。

lowerCamelCase:每個單詞的首字母大寫,第一個字母總是小寫,即使首字母縮寫也是如此。

lowercase_with_underscores:僅使用小寫字母(即使是首字母縮寫詞),并使用_分隔單詞。

標識符

UpperCamelCase:

Classes, enum types, typedefs, type parameters(泛型中的類型) ,extensions methods

lowercase_with_underscores:

?libraries, packages, directories, source files,import prefixes(import .. as 導入前綴)

lowerCamelCase:

Class members, top-level definitions, variables, parameters, named parameters,constant names

縮寫詞:

只有兩個字母的縮寫詞作整個標識符,首字母大寫? Id

只有兩個字母的縮寫詞作為標識符一部分,字母大寫? IOStream?DBIOPort

多個字母的縮寫詞,首字母大寫?HttpRequest

下劃線:

前導下劃線和私有相關,盡量不使用,例外:未使用的參數可以命名為_,__,___等。這種情況發生在回調中,在這些回調中您可以向其傳遞一個值,但不需要使用它。 給它一個僅包含下劃線的名稱是一種慣用的方式,用于指示未使用該值。

不要使用前綴小寫字母,例如 kDefaultTimeout

排序

//首先是Dart核心庫

import 'dart:async';

import 'dart:html';

//然后是其他包

import 'package:bar/bar.dart';

import 'package:foo/foo.dart';

//相對導入

import 'util.dart';

//其他相對文件

import 'src/error.dart';

import 'src/foo_bar.dart';

//導出

export 'src/error.dart';

各個部分使用空行分割,各個部分之中按照字母排序

一行不要超過80個字符

dartfmt代碼格式化

例外:當URI或文件路徑出現在注釋或字符串中(通常在導入或導出中)時,即使它導致行超過80個字符,也可能保持完整。 這樣可以更輕松地在源文件中搜索路徑。

例外:多行字符串可以包含多于80個字符的行,因為換行符在字符串內很重要,將行拆分為較短的行可以更改程序。

if 單行可以省略花括號

if (arg == null) return defaultValue;

Documentation?

大寫第一個字母,除非它是區分大小寫的標識符。 以句號結尾(我想應該是“!”或“?”)。 所有注釋都適用:文檔注釋,內聯內容,甚至TODO。

使用塊注釋(/ * ... * /)暫時注釋掉一段代碼,但所有其他注釋都應使用//

greet(name) {

? ?// Assume we have a valid name.

? ?print('Hi, $name!');

}

doc注釋

出現在聲明之前并使用dartdoc查找的特殊///語法的任何注釋。

使用/// doc注釋庫,頂級變量,類型和成員。

//注釋內部語句

/// The number of characters in this chunk when unsplit.

int get length => ...

考慮編寫庫級別的文檔注釋。

在Dart中,庫本身就是用戶可以直接使用,導入和思考的實體。這使得庫指令成為文檔的好地方,該文檔向讀者介紹其中提供的主要概念和功能。考慮包括:

庫用途的單句摘要。整個庫中使用的術語說明。使用API??的幾個完整的代碼示例。鏈接到最重要或最常用的類和函數。鏈接到與庫有關的域上的外部引用。

您可以通過在文件開頭的庫指令上方放置doc注釋來對庫進行記錄。如果該庫沒有庫指令,則可以添加一個,僅用于將文檔注釋掛起。

考慮為私有API和公共API編寫文檔注釋。

文檔注釋不僅適用于庫的公共API的外部使用者。它們也有助于理解從庫的其他部分調用的私有成員。

務必以單句摘要開始文檔注釋。

以簡短的,以用戶為中心的描述(以句點結尾)開始您的文檔注釋。句子片段通常就足夠了。為讀者提供足夠的背景信息以使其適應自己的方向,并決定他們是否應該繼續閱讀或尋找其他解決方案

文檔注釋第一句后空行

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) { ...}

類文檔注釋的讀者可以清楚地看到該類的名稱,其實現的接口等。在為成員閱讀文檔時,簽名就在其中,并且包含的類很明顯。 無需在文檔注釋中說明所有內容。 相反,請專注于說明讀者尚不了解的內容。

類的文檔注釋通常是程序中最重要的文檔。 他們描述了類型的結構,建立了所使用的術語,并為成員的其他文檔注釋提供了上下文。 此處的一些額外工作可以使所有其他成員更易于記錄。

首選帶有第三人稱動詞的起始函數或方法注釋

/// Returns `true` if every element satisfies the [predicate].

bool all(bool predicate(T element)) => ...

/// Starts the stopwatch if not already running.

void start() { ...}

優先使用名詞短語注釋變量,getter或setter。

文檔注釋應強調該屬性是什么。 即使對于可能進行計算或其他工作的getter也是如此。 調用者關心的是該工作的結果,而不是工作本身。

/// The current day of the week, where `0` is Sunday.

int weekday;

/// The number of checked buttons on the page.

int get checkedCount => ...

文檔注釋使用例子

/// Returns the lesser of two numbers.

///

/// ```dart

/// min(5, 3) == 3

/// ```

num min(num a, num b) => ...

在文檔注釋中使用方括號來引用范圍內標識符。

/// Throws a [StateError] if ...

/// similar to [anotherMethod()], but ...

類的成員使用.

/// Similar to [Duration.inDays], but handles fractional days.

/// To create a point, call [Point()] or use [Point.polar()] to ...

參數的說明也使用[]

/// 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) => ...

Markdown

將反引號用于代碼塊。

/// You can use [CodeBlockExample] like this:

///

/// ```

/// var example = CodeBlockExample();

/// print(example.isItGreat); // "Yes."

/// ```

更簡潔。要清晰準確,但也要簡潔。

避免縮寫和首字母縮寫,除非它們是顯而易見的。許多人不知道“ i.e。”,“ e.g。”是什么 和“等”。 意思。 您確定該領域的每個人都知道的縮寫可能不會像您想象的那樣廣為人知。

優選使用“ this”而不是“ the”來引用成員的實例。在為類記錄成員時,通常需要參考該成員被調用的對象。 使用“ the”可能是模棱兩可的。

Usage?

庫多個文件:使用URL

library my_library;

part? "some/other/file.dart";

另一個文件

part of "../../my_library.dart";

不要導入另一個軟件包的src目錄中的庫。

src目錄為包私有的庫

在您自己的軟件包的lib目錄中導入庫時,請使用相對路徑。不要使用package:?URI.

lib外部的庫永遠不要使用相對導入來訪問lib之下的庫,反之亦然。

請遵循以下兩個規則:

導入路徑不應包含/ lib /。

lib下的庫絕對不應使用../來轉義lib目錄。

Booleans

使用??轉換值到?boolean

// If you want null to be false:

optionalThing?.isEnabled ?? false

;// If you want null to be true:

optionalThing?.isEnabled ?? true;

Strings

如果您有兩個字符串文字(不是值,而是實際的帶引號的文字形式),則無需使用+來串聯它們。 就像在C和C ++中一樣,只需將它們彼此相鄰放置即可。 這是制作不適合一行的長字符串的好方法。

raiseAlarm( 'ERROR: Parts of the spaceship are on fire. Other '

? ? ? ? ? ? ? ? ? ? ?'parts are overrun by martians. Unclear which are which.');

字符串插值而不是+

'Hello, $name! You are ${year - birth} years old.';

盡量避免在不需要時在插值中使用花括號。

'Hi, $name!'?

?"Wear your wildest $decade's outfit."

?'Wear your wildest ${decade}s outfit.'

Collections

lists, maps, queues, and sets

有兩種創建空的可增長list的方法:[]和List()。 同樣,有三種方法可以制作空的map:{},Map()和LinkedHashMap()。如果要創建不可增長的列表或其他自定義集合類型,則一定要使用構造函數。 否則,請使用漂亮的文字語法。?

var points = [];

var addresses = {};

var points = <Point>[];

var addresses = <String, Address>{};

不要使用.length來查看集合是否為空。使用.isEmpty?and?.isNotEmpty.

使用? .map(),?.where()以及其他可迭代對象的方法代替for,來產生一個新集合

使用 for ...in? 代替 forEach,如果是一個函數則使用forEach

people.forEach(print);

總是使用Map.forEach(),map不是可迭代對象

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

給定一個Iterable,有兩種顯而易見的方法來產生一個包含相同元素的新List:

var copy1 = iterable.toList();

var copy2 = List.from(iterable);

// Creates a List<int>:var iterable = [1, 2, 3];

// Prints "List<dynamic>": 改變了類型

print(List.from(iterable).runtimeType);

var ints = List<int>.from(numbers);? //可以用來改變類型

使用whereType進行類型過濾

var objects = [1, "a", 2, "b", 3];

var ints = objects.whereType<int>();

對List <T> .from()的調用,其中T是您想要的結果列表的類型。進行轉換不使用cast()

var ints = List<int>.from(stuff);

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

cast()方法返回一個惰性集合,該集合檢查每個操作上的元素類型。 如果只對幾個元素執行幾個操作,那么懶惰就可以了。 但是在許多情況下,延遲驗證和包裝的開銷超過了好處。

替代方法并不總是有效,有時,cast()是正確的答案。

Variables

不要將變量顯式初始化為null.

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

class Circle {?

?double radius;?

?Circle(this.radius);

?double get area => pi * radius * radius;?

?double get circumference => pi * 2.0 * radius;

}

在某些情況下,您可能需要緩存慢速計算的結果,但是只有在知道性能問題后才這樣做,請仔細進行處理,并在注釋中說明優化方法。

Members

不要無必要地在getter和setter中包裝字段。

編寫代碼的人似乎很喜歡=>,但是很容易濫用它并最終得到難以閱讀的代碼。如果您的聲明多于兩行或包含深層嵌套的表達式(級聯和條件運算符是常見的違法者),請您自己和每個需要閱讀代碼的人都幫忙,并使用一個塊體和一些語句。

不要使用this。 除了重定向到命名構造函數或避免命名沖突。

class Box {?

?var value;?

?void clear() { update(null); }?

?void update(value) { this.value = value; }

}

class ShadeOfGray {?

?final int brightness;?

?ShadeOfGray(int val) : brightness = val;?

?ShadeOfGray.black() : this(0);?

?// But now it will!?

?ShadeOfGray.alsoBlack() : this.black();

}

請注意,構造函數參數永遠不會遮蓋構造函數初始化列表中的字段:

class Box extends BaseBox {

?var value;?

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

}

盡可能在聲明時初始化字段。

如果字段不依賴于任何構造函數參數,則可以并且應該在其聲明中對其進行初始化。 它花費更少的代碼,并且確保如果類具有多個構造函數,您也不會忘記對其進行初始化。

Constructors

初始化形式,在Dart中,帶有空主體的構造函數可以僅用分號終止。

class Point {

?double x, y;?

?Point(this.x, this.y);

}

Dart 2將new關鍵字設為可選。不要使用

不要冗余使用const。

在表達式必須為常數的情況下,const關鍵字是隱式的,不需要編寫也不需要編寫。

Asynchrony

async?有用的情況包括:

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

您正在異步返回錯誤。 throw?比return Future.error(...)短。

您正在返回一個值,并且希望將其隱式包裝future對象。 async?比Future.value(...)短。

Future<void> usesAwait(Future<String> later) async {?

?print(await later);

}

Future<void> asyncError() async {

?throw 'Error!';

}

Future<void> asyncValue() async => 'value';

//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');

}

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;

? ? }

}

Design

Names

使用一致的術語

避免縮寫。除非縮寫比未縮寫的術語更普遍,否則不要縮寫。 如果您縮寫,請正確將其大寫。

將描述性最強的名詞放在最后。最后一個詞應該是事物的最描述。 您可以在其前面加上其他詞(例如形容詞),以進一步描述事物。

pageCount // A count (of pages).

ConversionSink // A sink for doing conversions.

為非布爾屬性或變量指定名詞短語。如果用戶更關心如何確定屬性,則它可能應該是帶有動詞短語名稱的方法。

list.length

context.lineWidth

quest.rampagingSwampBeast

為布爾屬性或變量指定非命令式動詞短語。好的名字往往以以下幾種動詞之一開頭:

一種形式的“成為”:isEnabled,wasShown,willFire。 到目前為止,這些是最常見的。

輔助動詞:hasElements,canClose,shouldConsume,mustSave。

一個活動動詞:ignoresInput,writeFile。 這些是罕見的,因為它們通常是模棱兩可的。 loggingResult是一個錯誤的名稱,因為它可能表示“是否記錄了結果”或“記錄的結果”。 同樣,closeingConnection可以是“連接是否正在關閉”或“連接正在關閉”。 當名稱只能作為謂詞讀取時,允許使用主動動詞。

將所有這些動詞短語與方法名稱區分開的原因在于它們不是強制性的。 布爾名稱永遠不會聽起來像告訴對象執行某項操作的命令,因為訪問屬性不會更改對象。 (如果該屬性確實以有意義的方式修改了該對象,則它應該是一個方法。)

isEmpty

hasElements

canClose

closesWindow

canShowPopup

hasShownPopup

考慮忽略動詞的命名布爾參數。

?對于布爾型的命名參數,名稱通常很清楚,沒有動詞,并且代碼在調用站點處讀起來更好。

Isolate.spawn(entryPoint, message, paused: false);

var copy = List.from(elements, growable: true);

var regExp = RegExp(pattern, caseSensitive: false);

為布爾屬性或變量指定“正”名稱。

大多數布爾名稱在概念上都具有“正”和“負”的形式,其中前者感覺像是基本概念,而后者則是其否定形式-“開放”和“封閉”,“啟用”和“禁用”等。通常,后一種名稱 從字面上看,其前綴否定了前者:“可見”和“不可見”,“連接”和“斷開”,“零”和“非零”。

在選擇真值代表的兩種情況中的哪一種(以及因此為屬性命名的情況)時,請選擇肯定或更基本的一種。 布爾成員通常嵌套在邏輯表達式內,包括否定運算符。 如果您的屬性本身讀起來像一個否定詞,那么讀者就很難在思維上進行雙重否定并理解代碼的含義。

if (socket.isConnected && database.hasData) {?

?socket.write(database.read());

}

執行操作的函數或方法指定命令式動詞短語

list.add("element");

queue.removeFirst();

window.refresh();

如果返回值是其主要目的,則應為函數或方法提供名詞短語或非命令性動詞短語。

var element = list.elementAt(3);

var first = list.firstWhere(test);

var char = string.codeUnitAt(4);

如果您想引起對函數或方法的注意,請考慮該函數或方法的命令性動詞短語。

var table = database.downloadData();

var packageVersions = packageGraph.solveConstraints();

大多數情況下,請根據其為調用者所做的事情而不是其工作方式來命名您的成員。

避免使用get開頭方法名稱。

在大多數情況下,該方法應該是從名稱中刪除的getter方法。 例如,代替名為getBreakfastOrder()的方法,定義一個名為BreakfastOrder的方法。如果調用者最在乎方法返回的值,則只需刪除get并使用名詞短語名稱(例如breakfastOrder())即可。如果調用者關心完成的工作,則使用動詞短語名稱,但選擇比獲得更準確地描述工作的動詞,例如create,?download,?fetch,?calculate,?request,?aggregate

如果將對象的狀態復制到新對象,則將方法命名為to___()。

list.toSet();

stackTrace.toString();

如果方法返回由原始對象支持的其他表示形式,則將其命名為as___()。

var map = table.asMap();

var list = bytes.asFloat32List();

避免在函數或方法名稱中描述參數。

list.add(element); 代替?list.addElement(element)

但是,提及一個參數以使其與采用不同類型的其他類似名稱的方法進行歧義可能會很有用:

map.containsKey(key);

map.containsValue(value);

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

單個字母名稱并不能完全說明問題,但幾乎所有通用類型都使用它們。 幸運的是,他們大多以一致的助記符方式使用它們。 約定是:

E?for the?element?type in a collection

K?and?V?for the?key?and?value?types in an associative collection

R?for a type used as the?return?type of a function or a class’s methods. This isn’t common, but appears in typedefs sometimes and in classes that implement the visitor pattern:

將T,S和U用于具有單個類型參數且周圍類型使其含義顯而易見的泛型。 這里有多個字母,以允許嵌套而不遮蓋周圍的名稱。

Libraries

下劃線字符(_)表示成員是其庫的私有成員。這不只是約定俗成,而是內置在語言本身中。

考慮在同一個庫中聲明多個類。

如果一個庫包含多個類,頂級變量和函數(如果它們在邏輯上都屬于同一邏輯),完全可以。

將多個類一起放在一個庫中可以啟用一些有用的模式。由于Dart中的隱私權是在庫級別而非類級別起作用的,因此這是一種定義“朋友”類的方法,就像在C ++中一樣。在同一個庫中聲明的每個類都可以訪問彼此的私有成員,但是該庫外部的代碼則不能。

Classes and mixins

避免定義一個成員抽象類,如果簡單函數就可以實現功能

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

避免定義僅包含靜態成員的類。

Dart具有頂級函數,變量和常量,因此您不需要僅用于定義某些內容的類。如果您想要的是名稱空間,則最好使用庫。庫支持導入前綴和顯示/隱藏。這些功能強大的工具可讓您的代碼使用者以最適合他們的方式處理名稱沖突。

如果函數或變量在邏輯上未與類綁定,則將其放在頂層。如果您擔心名稱沖突,請給它一個更精確的名稱,或者將其移到可以使用前綴導入的單獨庫中。

但是,這不是硬性規定。 對于常量和類似枚舉的類型,將它們分組在一個類中是很自然的。

class Color {

?static const red = '#f00';?

?static const green = '#0f0';

?static const blue = '#00f';?

?static const black = '#000';

?static const white = '#fff';

}

如果您的類支持擴展,請記錄文檔。

如果要允許類的子類,請說明。在類名稱后加上Base,或在類文檔注釋中提及。

如果您的類支持用作接口,請記錄文檔。

如果該類沒有文檔注釋或類似IterableMixin的明顯名稱,則假設您未使用mixin聲明該類,則無法混入該類。

Constructors

如果類支持,請考慮使構造函數為const。

如果您有一個所有字段均為final的類,并且構造函數只對它們進行了初始化,則可以使該構造函數為const。 這樣,用戶可以在需要常量的地方(其他較大的常量,切換用例,默認參數值等)中創建類的實例。

但是請注意,const構造函數是您的公共API中的承諾。 如果以后將構造函數更改為非常量,則它將破壞用常量表達式調用它的用戶。 如果您不想這樣做,請不要將其設為const。 實際上,const構造函數對于簡單,不可變的數據記錄類最有用。

Members

in Dart; fields are “particularly fast getters”.

對于在概念上訪問屬性的操作,請使用getter:

操作不接受任何參數,并返回結果。

調用者最關心結果。如果要讓調用者擔心操作如何產生其結果而不是產生結果,請給該操作一個動詞名稱,該動詞名稱描述該工作并使其成為方法。這并不意味著操作必須特別快才能成為getter。 IterableBase.length為O(n),就可以了。getter進行大量計算很好。但是,如果它做了很多工作,您可能希望通過使其成為一種名稱來描述其功能的動詞的方法來引起他們的注意。

該操作沒有用戶可見的副作用。 訪問實際字段不會更改對象或程序中的任何其他狀態。 它不會產生輸出,寫入文件等。getter也不應執行這些操作。“用戶可見”部分很重要。 getter可以修改隱藏狀態或產生帶外副作用,這是很好的選擇。 getter可以懶惰地計算和存儲其結果,寫入緩存,記錄日志等。只要調用者不關心副作用,就可以了。

該操作是冪等的。 “冪等”是一個奇怪的詞,在此情況下,基本上意味著多次調用該操作每次都會產生相同的結果,除非在這些調用之間明確修改了某些狀態。 (顯然,如果在兩次調用之間向列表中添加元素,list.length會產生不同的結果。)這里的“相同結果”并不意味著獲取器必須在連續調用中從字面上產生相同的對象。 要求這樣做將迫使許多getter進行脆性緩存,從而抵消了使用getter的全部要點。 每次使用getter返回新的future或列出新的future,這都是很正常的事情,而且非常好。 重要的是,future的完成價值相同,并且列表包含相同的元素。換句話說,結果值在調用者關心的方面應該相同。

生成的對象不會顯示所有原始對象的狀態。 字段僅顯示一個對象。 如果您的操作返回的結果暴露了原始對象的整個狀態,則最好使用to ___()或as ___()方法。

如果以上所有都描述了您的操作,那應該是一種getter。

對于在概念上更改屬性的操作,請使用setter。

在setter和方法之間進行決定類似于在getter和方法之間進行決定。?

該操作采用單個參數,并且不產生結果值。

該操作會更改對象中的某些狀態。

該操作是冪等的。 就調用者而言,用相同的值兩次調用相同的setter不應再次執行任何操作。 在內部,也許您有一些緩存失效或正在進行日志記錄。 沒關系。 但是從呼叫者的角度來看,第二個呼叫似乎沒有任何作用。

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

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

如果您確實有這種類型的成員可能會返回null,請非常清楚地記錄下來,包括將返回null的條件。

Types

如果代碼帶有類型注釋,則該類型已明確寫入代碼中。

如果可以推斷出代碼,則不會編寫任何類型注釋,并且Dart會自行成功地找出類型。推論可能會失敗,在這種情況下,準則不會認為推論是正確的。在某些地方,推理失敗是一個靜態錯誤。在其他情況下,Dart使用動態作為后備類型。

如果代碼是動態的,則其靜態類型是特殊的動態類型。可以將代碼顯式地注釋為動態的,也可以對其進行推斷。

如果類型不明顯,則可以使用類型注釋公共字段和頂級變量。

“顯而易見”的情況,省略類型注釋:

字面常量。

構造函數調用。

引用其他顯式鍵入的常量。

關于數字和字符串的簡單表達式。

讀者應該熟悉的工廠方法,例如int.parse(),Future.wait()等。

如有疑問,請添加類型注釋。 即使類型很明顯,您仍可能希望顯式注釋。 如果推斷的類型依賴于其他庫的值或聲明,則您可能需要鍵入注釋的聲明,以使對該其他庫的更改不會在沒有意識到的情況下默默更改您自己的API的類型。

如果類型不明顯,請考慮類型注釋私有字段和頂級變量。

公共聲明上的類型注釋可幫助您的代碼用戶。 私人成員的類型可以幫助維護者。 私有聲明的范圍較小,那些需要了解聲明類型的人也更可能熟悉周圍的代碼。

局部變量,特別是在現代的代碼中,函數往往很小,它們的作用域很小。 省略類型會使讀者將注意力集中在更重要的變量名稱及其初始值上。

for (var recipe in cookbook) {....

bool isValid(String value, bool Function(String) test) => ...

例外:有時,您需要一個表示多個不同函數類型的并集的類型。 例如,您可以接受帶有一個參數的函數或帶有兩個參數的函數。 由于我們沒有聯合類型,因此無法精確鍵入該類型,因此通常必須使用動態。

setter總是在Dart中返回void。 寫這個詞是沒有意義的。

typedef Comparison<T> = int Function(T a, T b);

直接定義函數類型,函數類型語法

Iterable<T> where(bool Function(T) predicate) => ...

class FilteredObservable {?

?final bool Function(Event) _predicate;?

?final List<void Function(Event)> _observers;

某些操作適用于任何可能的對象。 例如,log()方法可以接受任何對象并在其上調用toString()。 Dart中有兩種類型允許所有值:Object和dynamic。 但是,它們傳達了不同的東西。 如果只想聲明允許所有對象,請像在Java或C#中那樣使用Object。使用動態發送更復雜的信號。 這可能意味著Dart的類型系統不夠復雜,無法表示允許的類型集,或者值來自互操作或靜態類型系統的權限之外,或者您明確希望在運行時具有動態性。

Parameters

如果用戶可能想省略較早的參數,請避免使用可選的位置參數。

可選的位置參數應具有邏輯順序,以使較早的參數傳遞的頻率比較晚的參數傳遞的頻率高。 用戶幾乎永遠不需要顯式地傳遞一個“空洞”來忽略較早的位置參數而傳遞較晚的位置參數。 最好使用命名參數。

避免接受強制的參數,該參數接受特殊的“無參數”值。

如果用戶在邏輯上省略了一個參數,則可以通過使該參數成為可選參數,而不是強制他們傳遞null,空字符串或其他表示“未傳遞”的特殊值,而讓他們實際忽略該參數。

省略該參數會更簡潔,并有助于防止在用戶認為自己提供真實值時意外傳遞類似null的前哨值的錯誤。

不要使用包含開始和包含結束參數來接受范圍。

如果要定義一個方法或函數,以使用戶可以從某些整數索引的序列中選擇一系列元素或項目,請獲取一個開始索引,該索引指的是第一項,結束索引(可能是可選的)大于一個 最后一項的索引。

Equality

如果您覆蓋==,請覆蓋hashCode。

默認的哈希碼實現提供了一個身份哈希-兩個對象通常只有完全相同的對象才具有相同的哈希碼。同樣,==的默認行為是身份。

如果要覆蓋==,則意味著您可能有不同的對象,這些對象被類視為“相等”。相等的任何兩個對象必須具有相同的哈希碼。否則,map和其他基于哈希的集合將無法識別這兩個對象是等效的。

請確保您的==運算符服從相等的數學規則。

等價關系應為:

反身:a == a應該總是返回true。

對稱:a == b應該返回與b == a相同的結果。

傳遞的:如果a == b和b == c都返回true,那么a == c也應該如此。

使用==的用戶和代碼希望遵守所有這些法律。如果您的班級不遵守這些規則,則==不是您要表達的操作的正確名稱。

當定義==時,還必須定義hashCode。兩者都應考慮對象的字段。如果這些字段發生更改,則表明該對象的哈希碼可以更改。

大多數基于散列的集合都不希望這樣-他們假設對象的散列代碼永遠是相同的,并且如果情況并非如此,則可能表現出不可預測的行為。

不要在自定義==運算符中檢查null。

語言指定自動執行此檢查,并且僅當右側不為null時才調用==方法。

class Person {

?final String name;?

?// ···?

?bool operator ==(other) => other is Person && name == other.name;

?int get hashCode => name.hashCode;

}









?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。