目錄

Effective Dart:風格

好的程式碼中,風格出乎意料地重要。一致的命名、排序和格式化有助於讓相同的程式碼看起來相同。它利用了我們大多數人在視覺系統中擁有的強大模式比對硬體。如果我們在整個 Dart 生態系統中使用一致的風格,我們可以更容易地從彼此的程式碼中學習和貢獻。

識別符

#

在 Dart 中,識別符有三種形式。

  • UpperCamelCase 名稱會將每個字詞的第一個字母大寫,包括第一個字詞。

  • lowerCamelCase 名稱會將每個字詞的第一個字母大寫,除了第一個字詞之外,無論是否為縮略字,第一個字詞都一律是小寫。

  • lowercase_with_underscores 名稱僅使用小寫字母,即使是縮略字也一樣,並以 _ 分隔字詞。

類型名稱使用 UpperCamelCase

#

Linter 規則:camel_case_types

類別、列舉類型、類型別名和型別參數應將每個字詞的第一個字母大寫(包括第一個字詞),且不使用分隔符號。

好的dart
class SliderMenu { ... }

class HttpRequest { ... }

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

這甚至包括預期用於中繼資料註釋的類別。

好的dart
class Foo {
  const Foo([Object? arg]);
}

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

@Foo()
class B { ... }

如果註釋類別的建構子不接受任何參數,您可能會想為其建立一個單獨的 lowerCamelCase 常數。

好的dart
const foo = Foo();

@foo
class C { ... }

擴展名稱使用 UpperCamelCase

#

Linter 規則:camel_case_extensions

與類型一樣,擴展應將每個字詞的第一個字母大寫(包括第一個字詞),且不使用分隔符號。

好的dart
extension MyFancyList<T> on List<T> { ... }

extension SmartIterable<T> on Iterable<T> { ... }

套件、目錄和原始檔名使用 lowercase_with_underscores

#

Linter 規則:file_namespackage_names

某些檔案系統不區分大小寫,因此許多專案要求檔案名稱必須全部小寫。使用分隔字元可讓名稱在此形式中仍然可讀。使用底線作為分隔符號可確保名稱仍然是有效的 Dart 識別符,如果語言稍後支援符號匯入,這可能會很有用。

好的
my_package
└─ lib
   └─ file_system.dart
   └─ slider_menu.dart
不好的
mypackage
└─ lib
   └─ file-system.dart
   └─ SliderMenu.dart

匯入前置詞名稱使用 lowercase_with_underscores

#

Linter 規則:library_prefixes

好的dart
import 'dart:math' as math;
import 'package:angular_components/angular_components.dart' as angular_components;
import 'package:js/js.dart' as js;
不好的dart
import 'dart:math' as Math;
import 'package:angular_components/angular_components.dart' as angularComponents;
import 'package:js/js.dart' as JS;

其他識別符名稱使用 lowerCamelCase

#

Linter 規則:non_constant_identifier_names

類別成員、頂層定義、變數、參數和具名參數應將每個字詞的第一個字母大寫,除了第一個字詞之外,且不使用分隔符號。

好的dart
var count = 3;

HttpRequest httpRequest;

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

常數名稱偏好使用 lowerCamelCase

#

Linter 規則:constant_identifier_names

在新程式碼中,常數變數(包括列舉值)使用 lowerCamelCase

好的dart
const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');

class Dice {
  static final numberGenerator = Random();
}
不好的dart
const PI = 3.14;
const DefaultTimeout = 1000;
final URL_SCHEME = RegExp('^([a-z]+):');

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

為了與現有程式碼保持一致,您可以使用 SCREAMING_CAPS,如下列情況:

  • 將程式碼新增至已使用 SCREAMING_CAPS 的檔案或函式庫時。
  • 產生與 Java 程式碼並行的 Dart 程式碼時,例如,從 protobufs 產生的列舉類型。

首字母縮略字和縮寫超過兩個字母時,像單字一樣大寫

#

大寫的縮略字可能難以閱讀,而且多個相鄰的縮略字可能會導致名稱模糊。例如,給定識別符 HTTPSFTP,讀者無法判斷它是指 HTTPS FTP 還是 HTTP SFTP。為避免這種情況,大多數縮略字和縮寫應像普通單字一樣大寫。如果指前者,此識別符將為 HttpsFtp,如果指後者,則為 HttpSftp

兩個字母的縮寫和縮略字是例外。如果兩個字母在英文中都是大寫,則在識別符中使用時,它們都應保持大寫。否則,請像單字一樣將其大寫。

好的dart
// Longer than two letters, so always like a word:
Http // "hypertext transfer protocol"
Nasa // "national aeronautics and space administration"
Uri // "uniform resource identifier"
Esq // "esquire"
Ave // "avenue"

// Two letters, capitalized in English, so capitalized in an identifier:
ID // "identifier"
TV // "television"
UI // "user interface"

// Two letters, not capitalized in English, so like a word in an identifier:
Mr // "mister"
St // "street"
Rd // "road"
不好的dart
HTTP // "hypertext transfer protocol"
NASA // "national aeronautics and space administration"
URI // "uniform resource identifier"
esq // "esquire"
Ave // "avenue"

Id // "identifier"
Tv // "television"
Ui // "user interface"

MR // "mister"
ST // "street"
RD // "road"

當任何形式的縮寫出現在 lowerCamelCase 識別符的開頭時,該縮寫應全部小寫

dart
var httpConnection = connect();
var tvSet = Television();
var mrRogers = 'hello, neighbor';

未使用回呼參數偏好使用 ___

#

有時,回呼函式的類型簽名需要參數,但回呼實作並未使用該參數。在這種情況下,將未使用參數命名為 _ 是慣用的。如果函式有多個未使用參數,請使用其他底線以避免名稱衝突:_____ 等。

好的dart
futureOfVoid.then((_) {
  print('Operation complete.');
});

此準則僅適用於匿名且本機的函式。這些函式通常在清楚表示未使用參數的內容中立即使用。相反地,頂層函式和方法宣告沒有這種內容,因此必須命名其參數,以便清楚每個參數的用途,即使未使用它。

非私有識別符請勿使用開頭底線

#

Dart 在識別符中使用開頭底線來將成員和頂層宣告標記為私有。這會訓練使用者將開頭底線與這些宣告類型之一建立關聯。他們看到「_」就會認為是「私有」。

對於區域變數、參數、區域函式或程式庫前綴,沒有「私有」的概念。當這些名稱以底線開頭時,會向讀者發出混淆的訊號。為了避免這種情況,請不要在這些名稱中使用開頭底線。

請勿使用前置字母

#

匈牙利命名法和其他命名方案是在 BCPL 時代出現的,當時編譯器並不能提供太多協助來理解您的程式碼。由於 Dart 可以告訴您宣告的類型、範圍、可變性和其他屬性,因此沒有理由將這些屬性編碼到識別符號名稱中。

好的dart
defaultTimeout
不好的dart
kDefaultTimeout

請勿明確命名函式庫

#

技術上來說,可以將名稱附加到 library 指令,但這是一個遺留功能,不建議使用。

Dart 會根據程式庫的路徑和檔案名稱為每個程式庫產生一個唯一的標籤。命名程式庫會覆蓋這個產生的 URI。如果沒有 URI,工具可能會更難找到相關的主要程式庫檔案。

不好的dart
library my_library;
好的dart
/// A really great test library.
@TestOn('browser')
library;

排序

#

為了保持檔案開頭部分的整潔,我們規定了指令應出現的順序。每個「區段」應以空白行分隔。

單一的程式碼檢查規則處理所有的順序準則:directives_ordering

務必將 dart: 導入語句放在其他導入語句之前

#

程式碼檢查規則:directives_ordering

好的dart
import 'dart:async';
import 'dart:html';

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

務必將 package: 導入語句放在相對導入語句之前

#

程式碼檢查規則:directives_ordering

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

import 'util.dart';

所有匯入之後,應在單獨的區段中指定匯出

#

程式碼檢查規則:directives_ordering

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

export 'src/error.dart';
不好的dart
import 'src/error.dart';
export 'src/error.dart';
import 'src/foo_bar.dart';

區段應按字母順序排序

#

程式碼檢查規則:directives_ordering

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

import 'foo.dart';
import 'foo/foo.dart';
不好的dart
import 'package:foo/foo.dart';
import 'package:bar/bar.dart';

import 'foo/foo.dart';
import 'foo.dart';

格式化

#

和許多語言一樣,Dart 會忽略空白字元。但是,人類不會。擁有一致的空白字元風格有助於確保人類讀者看到的程式碼與編譯器看到的一樣。

務必使用 dart format 格式化您的程式碼

#

格式化是繁瑣的工作,在重構期間尤其耗時。幸運的是,您不必擔心這個問題。我們提供了一個名為 dart format 的複雜自動化程式碼格式化工具,它可以為您完成這項工作。Dart 的官方空白字元處理規則是dart format 產生的任何結果格式化工具常見問題解答可以提供更多關於其強制執行的樣式選擇的深入了解。

其餘的格式化準則適用於 dart format 無法為您修正的少數事項。

考慮變更程式碼以使其更易於格式化

#

格式化工具會盡力處理您拋給它的任何程式碼,但它並非萬能。如果您的程式碼有特別長的識別符號、深度巢狀的表達式、不同種類的運算子混合等,格式化後的輸出可能仍然難以閱讀。

當這種情況發生時,請重新組織或簡化您的程式碼。考慮縮短區域變數名稱或將表達式提升到新的區域變數中。換句話說,進行與您手動格式化程式碼並嘗試使其更易讀時相同的修改。將 dart format 視為一種合作夥伴關係,您們可以一起合作,有時是迭代地,產生優美的程式碼。

避免超過 80 個字元的行

#

程式碼檢查規則:lines_longer_than_80_chars

可讀性研究表明,較長的文字行更難閱讀,因為當移動到下一行的開頭時,您的眼睛必須移動更遠。這就是為什麼報紙和雜誌使用多欄文字的原因。

如果您真的發現自己想要超過 80 個字元的行,我們的經驗是您的程式碼可能過於冗長,可以更簡潔一些。主要的罪魁禍首通常是 VeryLongCamelCaseClassNames。問問自己,「該類型名稱中的每個單字都告訴我一些關鍵資訊或防止名稱衝突嗎?」如果不是,請考慮省略它。

請注意,dart format 為您完成 99% 的工作,但最後 1% 由您完成。它不會拆分長字串文字以符合 80 個字元的欄寬,因此您必須手動完成。

例外情況:當 URI 或檔案路徑出現在註解或字串中(通常在導入或匯出中)時,即使它導致該行超過 80 個字元,它也可能保持完整。這使得在原始碼檔案中搜尋路徑更容易。

例外情況:多行字串可以包含超過 80 個字元的行,因為換行符在字串內部是重要的,並且將這些行拆分成較短的行可能會改變程式。

所有流程控制陳述式使用大括號

#

程式碼檢查規則:curly_braces_in_flow_control_structures

這樣做可以避免 懸空 else 的問題。

好的dart
if (isWeekDay) {
  print('Bike to work!');
} else {
  print('Go dancing or read a book!');
}

例外情況:當您有一個沒有 else 子句的 if 語句,並且整個 if 語句在一行中時,如果您願意,可以省略大括號

好的dart
if (arg == null) return defaultValue;

但是,如果主體換行到下一行,請使用大括號

好的dart
if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}
不好的dart
if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;