內容

Effective Dart:樣式

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

識別碼

#

Dart 中的識別碼有三個類型。

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

  • lowerCamelCase 名稱將每個字的第一個字母大寫,第一個字母永遠是小寫,即使它是縮寫也是如此。

  • lowercase_with_underscores 名稱僅使用小寫字母,即使是縮寫也是如此,並使用 _ 來區分字詞。

DO 使用 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 產生的列舉類型。

DO 將大於兩個字母的縮寫和簡寫大寫,就像單字一樣

#

大寫縮寫詞可能難以閱讀,而且多個相鄰的縮寫詞可能會導致名稱不明確。例如,給定一個以 HTTPSFTP 開頭的名稱,無法得知它指的是 HTTPS FTP 還是 HTTP SFTP。

為避免此問題,縮寫詞和簡稱會像一般字詞一樣大寫。

例外:兩個字母的縮寫詞,例如 IO(輸入/輸出),會全部大寫:IO。另一方面,兩個字母的簡稱,例如 ID(識別),仍會像一般字詞一樣大寫:Id

dart
class HttpConnection {}
class DBIOPort {}
class TVVcr {}
class MrRogers {}

var httpRequest = ...
var uiHandler = ...
var userId = ...
Id id;
dart
class HTTPConnection {}
class DbIoPort {}
class TvVcr {}
class MRRogers {}

var hTTPRequest = ...
var uIHandler = ...
var userID = ...
ID iD;

優先使用 ___ 等,作為未使用的回呼參數

#

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

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

此準則僅適用於匿名且為區域性的函式。這些函式通常會立即在一個清楚未使用的參數代表什麼的內容中使用。相反地,頂層函式和方法宣告沒有那個內容,因此其參數必須命名,以清楚每個參數的用途,即使它沒有被使用。

DON'T 對非私有的識別碼使用前導底線

#

Dart 在識別碼中使用前導底線,將成員和頂層宣告標記為私有的。這訓練使用者將前導底線與其中一種宣告關聯。他們看到「_」就會想到「私有的」。

區域變數、參數、區域函式或程式庫前置詞沒有「私有」的概念。當其中一個名稱以底線開頭時,會向讀者傳達一個令人困惑的訊號。為避免這種情況,請勿在這些名稱中使用前導底線。

DON'T 使用前綴字母

#

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

dart
defaultTimeout
dart
kDefaultTimeout

DON'T 明確命名函式庫

#

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

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

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

排序

#

為了讓你的檔案前導保持整齊,我們有一個規定的指令出現順序。每個「區段」都應該用空白行分隔。

單一 linter 規則處理所有排序準則:directives_ordering。

請將 dart: 匯入放在其他匯入之前

#

Linter 規則:directives_ordering

dart
import 'dart:async';
import 'dart:html';

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

請將 package: 匯入放在相對匯入之前

#

Linter 規則:directives_ordering

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

import 'util.dart';

DO 在所有 import 之後,在一個獨立區段中指定 export

#

Linter 規則: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';

DO 按字母順序排列區段

#

Linter 規則: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 無法為您修正的少數事項。

CONSIDER 變更您的程式碼,使其更適合格式化

#

格式化工具會盡力處理您提供的任何程式碼,但它無法創造奇蹟。如果您的程式碼有特別長的識別碼、深度巢狀的表達式、不同類型的運算子混合等,格式化後的輸出可能仍然難以閱讀。

發生這種情況時,請重新整理或簡化您的程式碼。考慮縮短局部變數名稱或將表達式提升到新的局部變數中。換句話說,進行與手動格式化程式碼並嘗試使其更具可讀性時相同的修改。將 dart format 視為一種夥伴關係,您有時會反覆合作,以產生漂亮的程式碼。

AVOID 超過 80 個字元的行

#

Linter 規則:lines_longer_than_80_chars

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

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

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

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

例外:多行字串可以包含超過 80 個字元的行,因為換行符號在字串內很重要,而將行拆分為較短的行可能會改變程式。

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

#

Linter 規則:curly_braces_in_flow_control_structures

這樣做可以避免dangling 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;