跳到主要內容

Effective Dart:風格

良好程式碼中一個非常重要的部分是良好的風格。一致的命名、排序和格式化有助於使相同的程式碼看起來也相同。它利用了我們大多數人在視覺系統中擁有的強大模式匹配硬體。如果我們在整個 Dart 生態系統中使用一致的風格,將使我們所有人更容易學習和貢獻彼此的程式碼。

識別符

#

在 Dart 中,識別符有三種風格。

  • UpperCamelCase 名稱將每個單字(包括第一個單字)的首字母大寫。

  • lowerCamelCase 名稱將每個單字(除了第一個單字,即使是縮寫也總是小寫)的首字母大寫。

  • lowercase_with_underscores 名稱僅使用小寫字母(即使是縮寫),並用 _ 分隔單字。

類型命名應使用 UpperCamelCase

#

Linter 規則:camel_case_types

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

gooddart
class SliderMenu {
   ...
}

class HttpRequest {
   ...
}

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

這甚至包括預期用於元數據註解的類別。

gooddart
class Foo {
  const Foo([Object? arg]);
}

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

@Foo()
class B {
   ...
}

如果註解類別的建構子不帶任何參數,您可能需要為其建立一個單獨的 lowerCamelCase 常數。

gooddart
const foo = Foo();

@foo
class C {
   ...
}

擴展命名應使用 UpperCamelCase

#

Linter 規則:camel_case_extensions

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

gooddart
extension MyFancyList<T> on List<T> {
   ...
}

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

套件、目錄和原始碼檔案命名應使用 lowercase_with_underscores

#

Linter 規則:file_namespackage_names

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

good
my_package
└─ lib
   └─ file_system.dart
   └─ slider_menu.dart
bad
mypackage
└─ lib
   └─ file-system.dart
   └─ SliderMenu.dart

匯入前綴命名應使用 lowercase_with_underscores

#

Linter 規則:library_prefixes

gooddart
import 'dart:math' as math;
import 'package:angular_components/angular_components.dart' as angular_components;
import 'package:js/js.dart' as js;
baddart
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

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

gooddart
var count = 3;

HttpRequest httpRequest;

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

常數名稱建議使用 lowerCamelCase

#

Linter 規則:constant_identifier_names

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

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

class Dice {
  static final numberGenerator = Random();
}
baddart
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

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

gooddart
// 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"
baddart
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';

未使用的回呼參數建議使用萬用字元

#

有時,回呼函式的類型簽名需要參數,但回呼實作不使用該參數。在這種情況下,習慣上將未使用的參數命名為 _,這會宣告一個萬用字元變數,它是非綁定的。

gooddart
futureOfVoid.then((_) {
  print('Operation complete.');
});

由於萬用字元變數是非綁定的,因此您可以將多個未使用的參數命名為 _

gooddart
.onError((_, _) {
  print('Operation failed.');
});

此指南僅適用於匿名且本地的函式。這些函式通常在上下文中立即使用,在這種情況下,很清楚未使用的參數代表什麼。相反,頂層函式和方法宣告沒有該上下文,因此即使未使用參數,也必須命名參數,以便清楚每個參數的用途。

非私有識別符請勿使用前導底線

#

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

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

請勿使用前綴字母

#

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

gooddart
defaultTimeout
baddart
kDefaultTimeout

請勿明確命名函式庫

#

從技術上講,將名稱附加到 library 指令是可能的,但這是一項遺留功能,不建議使用。

Dart 會根據每個函式庫的路徑和檔案名稱產生唯一的標籤。命名函式庫會覆寫此產生的 URI。如果沒有 URI,工具可能更難找到有問題的主要函式庫檔案。

baddart
library my_library;
gooddart
/// A really great test library.
@TestOn('browser')
library;

排序

#

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

單一 linter 規則處理所有排序指南:directives_ordering

dart: 匯入應放在其他匯入之前

#

Linter 規則:directives_ordering

gooddart
import 'dart:async';
import 'dart:collection';

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

package: 匯入應放在相對匯入之前

#

Linter 規則:directives_ordering

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

import 'util.dart';

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

#

Linter 規則:directives_ordering

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

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

區段應按字母順序排序

#

Linter 規則:directives_ordering

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

import 'foo.dart';
import 'foo/foo.dart';
baddart
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 個字元

#

Linter 規則:lines_longer_than_80_chars

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

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

請注意,dart format 預設為 80 個或更少的字元,但您可以設定預設值。它不會拆分長字串文字以適應 80 個欄位,因此您必須手動執行此操作。

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

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

所有流程控制語句都應使用大括號

#

Linter 規則:curly_braces_in_flow_control_structures

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

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

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

gooddart
if (arg == null) return defaultValue;

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

gooddart
if (overflowChars != other.overflowChars) {
  return overflowChars < other.overflowChars;
}
baddart
if (overflowChars != other.overflowChars)
  return overflowChars < other.overflowChars;