高效 Dart 語言指南
在過去的幾年裡,我們編寫了大量的 Dart 程式碼,並從中收穫了很多經驗和教訓。我們將與你分享這些經驗,這些經驗將有助於你編寫出一致、健壯、高效的程式碼。這裡包含兩主題:
-
保持一致 當談論到格式、命名以及引數的相關規則時,哪種規則更好,得出的結論通常是主觀且無法達成一致的。但我們知道的是,客觀上保持 一致 是非常有益的。
如果兩段程式碼看起來不同,那它們就應該有不同的含義。當一段突出的程式碼吸引到你的注意時,那它就應該有吸引你的理由。
-
保持簡潔 Dart 會讓開發者感到很親切,因為它繼承了許多與 C、Java、JavaScript 及其他語言相同的陳述式和表示式語法。我們之所以開發 Dart 語言,是因為這些語言依然有很大的改進的空間。我們提供了很多新的特性,比如字串插值、初始化正規化等,以幫助你更簡單、更輕鬆地表達意圖。
如果有多種方式來描述一件事情,那麼你通常應該選擇其中最簡潔的方式。這並不意味著你要像 code golf(程式碼高爾夫挑戰賽)一樣將所有程式碼塞到一行中。而是應該讓程式碼變得 簡約 而非 密集。
指南
為了便於理解,這裡我們將指南分成了幾個部分:
-
風格指南 – 該部分定義了佈局和組織程式碼的規則,或者說是
dart format
不能為你格式化的那些程式碼的佈局和組織規則。風格指南還為你指定了識別符號的格式,比如:駝峰式大小寫、下劃線的使用等等。 -
註釋指南 – 該部分會告訴你註釋中應該包含哪些內容。包括文件註釋以及常規的普通程式碼註釋。
-
使用指南 – 該部分將教你如何充分利用語言特性來實現相關功能。比如你可以在該部分找到如何利用陳述式或表示式來實現某個功能。
-
設計指南 – 該部分是最易理解但是覆蓋範圍最廣的。其涵蓋了我們所總結的為庫設計一致、可用 API 的經驗。比如這些 API 的型別簽名或宣告的說明則會在這裡找到。
有關所有指南的連結,請查閱 概覽。
如何閱讀這些指南
每個指南都分為了幾個部分。每個部分包含一些詳細的準則。每條準則都以下面其中的一個詞作為開頭:
-
要 準則所描述的內容應該始終被遵守。不應該以任何的理由來偏離違背這些準則。
-
不要 準則所描述的內容是相反的:描述的準則不是一個好主意。幸運的是,我們不會像其他語言有那麼多這樣的準則,因為我們沒有太多的歷史包袱。
-
推薦 準則所描述的內容 應該 被遵守。但是在有些情況下,可能有更好的或者更合理的做法。請確保在你完全理解準則的情況下,再忽視這些準則。
-
避免 該單詞描述的準則與 “推薦” 描述的準則相反:顯然,這些事不應該做,但不排除在極少數場合下有充分的理由可以做。
-
考慮 準則所描述的內容可以遵守也可以不遵守。取決於具體的情況、習慣做法以及自己的偏好。
某些準則描述了規則 不 適用的 例外情況。當這些例外列出時,也有可能不是詳盡的—你可能還需要對其它的情況作出判斷。
這聽起來好像有點小題大做。其實並沒有那麼糟糕。大部分的準則都是常識,也符合我們的認知。最終要達到的目標是寫出優雅,可讀,可維護的程式碼。
Dart 分析器提供了一個 Linter 工具,可以幫助你編寫優秀的、一致性的以及符合各種指南的程式碼。如果存在一個或者多個 linter 規則 來幫助你遵循某個指南準則,那麼這些指南的連結也會有顯示。比如下面的範例:
Linter rule: unnecessary_getters_setters
學習如何使用 linter,請查閱文件 啟用 linter 規則,以及可用的各種 linter 規則。
術語表
為了使指南保持簡潔,我們使用一些簡寫術語來指代不同的 Dart 結構。
-
庫成員 表示一個最上層欄位、Getter 或 Setter 方法、或函式。基本而言,任何最上層的東西都不會是一種型別。
-
類成員 表示類內部宣告的建構函式、欄位、Getter 或 Setter 方法、函式或運運算元。類成員可以是例項或靜態的,也可以是抽象或具體的。
-
成員 可以表示是一個函式庫成員或者類成員。
-
變數 通常指的是最上層變數、引數和區域變數。它不包括靜態或例項欄位。
-
型別 表示所有命名型別的宣告:一個類別、typedef 或列舉。
-
屬性 表示最上層變數、Getter 和 Setter 方法(位於類中或最上層,可以是例項或靜態的)或欄位(例項或靜態的)。基本上是任何類似欄位的命名結構都可以稱為屬性。
所有準則摘要
Style
Identifiers
- DO name types using
UpperCamelCase
. - DO name extensions using
UpperCamelCase
. - DO name packages, directories, and source files using
lowercase_with_underscores
. - DO name import prefixes using
lowercase_with_underscores
. - DO name other identifiers using
lowerCamelCase
. - PREFER using
lowerCamelCase
for constant names. - DO capitalize acronyms and abbreviations longer than two letters like words.
- PREFER using
_
,__
, etc. for unused callback parameters. - DON’T use a leading underscore for identifiers that aren’t private.
- DON’T use prefix letters.
- DON’T explicitly name libraries.
Ordering
- DO place
dart:
imports before other imports. - DO place
package:
imports before relative imports. - DO specify exports in a separate section after all imports.
- DO sort sections alphabetically.
Formatting
Documentation
Comments
Doc comments
- DO use
///
doc comments to document members and types. - PREFER writing doc comments for public APIs.
- CONSIDER writing a library-level doc comment.
- CONSIDER writing doc comments for private APIs.
- DO start doc comments with a single-sentence summary.
- DO separate the first sentence of a doc comment into its own paragraph.
- AVOID redundancy with the surrounding context.
- PREFER starting function or method comments with third-person verbs.
- PREFER starting a non-boolean variable or property comment with a noun phrase.
- PREFER starting a boolean variable or property comment with "Whether" followed by a noun or gerund phrase.
- DON’T write documentation for both the getter and setter of a property.
- PREFER starting library or type comments with noun phrases.
- CONSIDER including code samples in doc comments.
- DO use square brackets in doc comments to refer to in-scope identifiers.
- DO use prose to explain parameters, return values, and exceptions.
- DO put doc comments before metadata annotations.
Markdown
- AVOID using markdown excessively.
- AVOID using HTML for formatting.
- PREFER backtick fences for code blocks.
Writing
Usage
Libraries
- DO use strings in
part of
directives. - DON’T import libraries that are inside the
src
directory of another package. - DON’T allow an import path to reach into or out of
lib
. - PREFER relative import paths.
Null
- DON’T explicitly initialize variables to
null
. - DON’T use an explicit default value of
null
. - DON’T use
true
orfalse
in equality operations. - AVOID
late
variables if you need to check whether they are initialized. - CONSIDER assigning a nullable field to a local variable to enable type promotion.
Strings
- DO use adjacent strings to concatenate string literals.
- PREFER using interpolation to compose strings and values.
- AVOID using curly braces in interpolation when not needed.
Collections
- DO use collection literals when possible.
- DON’T use
.length
to see if a collection is empty. - AVOID using
Iterable.forEach()
with a function literal. - DON’T use
List.from()
unless you intend to change the type of the result. - DO use
whereType()
to filter a collection by type. - DON’T use
cast()
when a nearby operation will do. - AVOID using
cast()
.
Functions
- DO use a function declaration to bind a function to a name.
- DON’T create a lambda when a tear-off will do.
- DO use
=
to separate a named parameter from its default value.
Variables
- DO follow a consistent rule for
var
andfinal
on local variables. - AVOID storing what you can calculate.
Members
- DON’T wrap a field in a getter and setter unnecessarily.
- PREFER using a
final
field to make a read-only property. - CONSIDER using
=>
for simple members. - DON’T use
this.
except to redirect to a named constructor or to avoid shadowing. - DO initialize fields at their declaration when possible.
Constructors
- DO use initializing formals when possible.
- DON’T use
late
when a constructor initializer list will do. - DO use
;
instead of{}
for empty constructor bodies. - DON’T use
new
. - DON’T use
const
redundantly.
Error handling
- AVOID catches without
on
clauses. - DON’T discard errors from catches without
on
clauses. - DO throw objects that implement
Error
only for programmatic errors. - DON’T explicitly catch
Error
or types that implement it. - DO use
rethrow
to rethrow a caught exception.
Asynchrony
Design
Names
- DO use terms consistently.
- AVOID abbreviations.
- PREFER putting the most descriptive noun last.
- CONSIDER making the code read like a sentence.
- PREFER a noun phrase for a non-boolean property or variable.
- PREFER a non-imperative verb phrase for a boolean property or variable.
- CONSIDER omitting the verb for a named boolean parameter.
- PREFER the "positive" name for a boolean property or variable.
- PREFER an imperative verb phrase for a function or method whose main purpose is a side effect.
- PREFER a noun phrase or non-imperative verb phrase for a function or method if returning a value is its primary purpose.
- CONSIDER an imperative verb phrase for a function or method if you want to draw attention to the work it performs.
- AVOID starting a method name with
get
. - PREFER naming a method
to___()
if it copies the object’s state to a new object. - PREFER naming a method
as___()
if it returns a different representation backed by the original object. - AVOID describing the parameters in the function’s or method’s name.
- DO follow existing mnemonic conventions when naming type parameters.
Libraries
Classes and mixins
- AVOID defining a one-member abstract class when a simple function will do.
- AVOID defining a class that contains only static members.
- AVOID extending a class that isn’t intended to be subclassed.
- DO document if your class supports being extended.
- AVOID implementing a class that isn’t intended to be an interface.
- DO document if your class supports being used as an interface.
- DO use
mixin
to define a mixin type. - AVOID mixing in a type that isn’t intended to be a mixin.
Constructors
Members
- PREFER making fields and top-level variables
final
. - DO use getters for operations that conceptually access properties.
- DO use setters for operations that conceptually change properties.
- DON’T define a setter without a corresponding getter.
- AVOID using runtime type tests to fake overloading.
- AVOID public
late final
fields without initializers. - AVOID returning nullable
Future
,Stream
, and collection types. - AVOID returning
this
from methods just to enable a fluent interface.
Types
- DO type annotate variables without initializers.
- DO type annotate fields and top-level variables if the type isn’t obvious.
- DON’T redundantly type annotate initialized local variables.
- DO annotate return types on function declarations.
- DO annotate parameter types on function declarations.
- DON’T annotate inferred parameter types on function expressions.
- DON’T type annotate initializing formals.
- DO write type arguments on generic invocations that aren’t inferred.
- DON’T write type arguments on generic invocations that are inferred.
- AVOID writing incomplete generic types.
- DO annotate with
dynamic
instead of letting inference fail. - PREFER signatures in function type annotations.
- DON’T specify a return type for a setter.
- DON’T use the legacy typedef syntax.
- PREFER inline function types over typedefs.
- PREFER using function type syntax for parameters.
- AVOID using
dynamic
unless you want to disable static checking. - DO use
Future<void>
as the return type of asynchronous members that do not produce values. - AVOID using
FutureOr<T>
as a return type.
Parameters
- AVOID positional boolean parameters.
- AVOID optional positional parameters if the user may want to omit earlier parameters.
- AVOID mandatory parameters that accept a special "no argument" value.
- DO use inclusive start and exclusive end parameters to accept a range.
Equality