高效 Dart:風格
良好程式碼的一個出人意料的重要部分是良好的風格。一致的命名、排序和格式化有助於使 *相同* 的程式碼 *看起來* 相同。它利用了我們大多數人眼中強大的模式匹配硬體。如果我們在整個 Dart 生態系統中使用一致的風格,那麼我們所有人學習和貢獻彼此的程式碼都會變得更容易。
識別符號
#Dart 中的識別符號有三種類型。
UpperCamelCase命名法將每個單詞的首字母大寫,包括第一個單詞。lowerCamelCase命名法將每個單詞的首字母大寫,*除了* 第一個單詞始終小寫,即使它是縮寫詞。lowercase_with_underscores命名法只使用小寫字母,即使是縮寫詞,並用_分隔單詞。
務必使用 UpperCamelCase 命名型別
#Linter 規則: camel_case_types
類、列舉型別、類型別名和型別引數應將每個單詞的首字母大寫(包括第一個單詞),且不使用分隔符。
class SliderMenu {
...
}
class HttpRequest {
...
}
typedef Predicate<T> = bool Function(T value);這甚至包括旨在用於元資料註解的類。
class Foo {
const Foo([Object? arg]);
}
@Foo(anArg)
class A {
...
}
@Foo()
class B {
...
}如果註解類的建構函式不帶引數,你可能需要為其建立一個單獨的 lowerCamelCase 常量。
const foo = Foo();
@foo
class C {
...
}務必使用 UpperCamelCase 命名擴充套件
#Linter 規則: camel_case_extensions
與型別一樣,擴充套件應將每個單詞的首字母大寫(包括第一個單詞),且不使用分隔符。
extension MyFancyList<T> on List<T> {
...
}
extension SmartIterable<T> on Iterable<T> {
...
}務必使用 lowercase_with_underscores 命名包、目錄和原始檔
#Linter 規則: file_names, package_names
某些檔案系統不區分大小寫,因此許多專案要求檔名全部小寫。使用分隔符可以使名稱在這種形式下仍然可讀。使用下劃線作為分隔符可確保名稱仍然是有效的 Dart 識別符號,如果語言將來支援符號匯入,這可能會有所幫助。
my_package
└─ lib
└─ file_system.dart
└─ slider_menu.dartmypackage
└─ lib
└─ file-system.dart
└─ SliderMenu.dart務必使用 lowercase_with_underscores 命名匯入字首
#Linter 規則: library_prefixes
import 'dart:math' as math;
import 'package:angular_components/angular_components.dart' as angular_components;
import 'package:js/js.dart' as js;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
類成員、頂層定義、變數、引數和命名引數應將每個單詞的首字母大寫,*除了* 第一個單詞,並且不使用分隔符。
var count = 3;
HttpRequest httpRequest;
void align(bool clearItems) {
// ...
}建議為常量名使用 lowerCamelCase
#Linter 規則: constant_identifier_names
在新程式碼中,常量變數(包括列舉值)使用 lowerCamelCase 命名。
const pi = 3.14;
const defaultTimeout = 1000;
final urlScheme = RegExp('^([a-z]+):');
class Dice {
static final numberGenerator = Random();
}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。
兩個字母的縮寫和縮寫詞是例外。如果兩個字母在英語中都大寫,那麼當它們在識別符號中使用時,它們都應該保持大寫。否則,像單詞一樣首字母大寫。
// 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"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 識別符號的開頭時,該縮寫應全部小寫
var httpConnection = connect();
var tvSet = Television();
var mrRogers = 'hello, neighbor';建議對未使用的回撥引數使用萬用字元
#有時回撥函式的型別簽名需要一個引數,但回撥實現並 *沒有使用* 該引數。在這種情況下,慣用的做法是將未使用的引數命名為 _,這聲明瞭一個非繫結的萬用字元變數。
futureOfVoid.then((_) {
print('Operation complete.');
});由於萬用字元變數是非繫結的,你可以將多個未使用的引數命名為 _。
.onError((_, _) {
print('Operation failed.');
});此準則僅適用於 *匿名且區域性* 的函式。這些函式通常立即在一個明確未使用引數表示什麼的上下文中被使用。相比之下,頂層函式和方法宣告沒有這種上下文,因此它們的引數必須命名,以便清楚每個引數的用途,即使它沒有被使用。
不要對非私有識別符號使用前導下劃線
#Dart 在識別符號中使用前導下劃線來標記成員和頂層宣告為私有。這訓練使用者將前導下劃線與這些宣告中的一種關聯起來。他們看到“_”就認為是“私有”。
對於區域性變數、引數、區域性函式或庫字首,沒有“私有”的概念。當其中任何一個的名稱以下劃線開頭時,它會向讀者傳送一個令人困惑的訊號。為了避免這種情況,不要在這些名稱中使用前導下劃線。
不要使用字首字母
#匈牙利命名法和其他命名方案出現在 BCPL 時代,那時編譯器對幫助你理解程式碼的作用不大。由於 Dart 可以告訴你宣告的型別、作用域、可變性以及其他屬性,因此沒有理由在識別符號名稱中編碼這些屬性。
defaultTimeoutkDefaultTimeout不要顯式命名庫
#在 library 指令後附加名稱在技術上是可行的,但它是一個遺留特性,不推薦使用。
Dart 根據每個庫的路徑和檔名為其生成一個唯一的標籤。命名庫會覆蓋這個生成的 URI。如果沒有 URI,工具可能更難找到相關的主庫檔案。
library my_library;/// A really great test library.
@TestOn('browser')
library;排序
#為了保持檔案開頭的整潔,我們規定了指令的出現順序。每個“部分”都應該用空行分隔。
一個 linter 規則處理所有排序指南:directives_ordering。
務必將 dart: 匯入放在其他匯入之前
#Linter 規則: directives_ordering
import 'dart:async';
import 'dart:collection';
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';務必將 package: 匯入放在相對匯入之前
#Linter 規則: directives_ordering
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
import 'util.dart';務必在所有匯入之後,在單獨的部分中指定匯出
#Linter 規則: directives_ordering
import 'src/error.dart';
import 'src/foo_bar.dart';
export 'src/error.dart';import 'src/error.dart';
export 'src/error.dart';
import 'src/foo_bar.dart';務必按字母順序對各部分進行排序
#Linter 規則: directives_ordering
import 'package:bar/bar.dart';
import 'package:foo/foo.dart';
import 'foo.dart';
import 'foo/foo.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 個字元或更少
#Linter 規則: lines_longer_than_80_chars
可讀性研究表明,長文字行更難閱讀,因為當眼睛移到下一行的開頭時,需要移動更遠的距離。這就是報紙和雜誌使用多欄文字的原因。
如果你真的發現自己想要超過 80 個字元的行,我們的經驗是你的程式碼可能過於冗長,可以更緊湊一些。主要的問題通常是 VeryLongCamelCaseClassNames。問問自己:“該型別名稱中的每個單詞是否提供了關鍵資訊或避免了名稱衝突?” 如果不是,請考慮省略它。
請注意,dart format 預設為 80 個字元或更少,但你可以配置預設值。它不會將長字串字面量拆分為 80 列以內,因此你需要手動完成此操作。
例外: 當 URI 或檔案路徑出現在註釋或字串中(通常在匯入或匯出中)時,即使導致行超過 80 個字元,它也可以保持完整。這使得在原始檔中搜索路徑更容易。
例外: 多行字串可以包含超過 80 個字元的行,因為換行符在字串內部具有重要意義,將行拆分為更短的行可能會改變程式。
務必為所有控制流語句使用花括號
#Linter 規則: curly_braces_in_flow_control_structures
這樣做可以避免懸掛 else 問題。
if (isWeekDay) {
print('Bike to work!');
} else {
print('Go dancing or read a book!');
}例外: 當你有一個沒有 else 子句的 if 語句,並且整個 if 語句都在一行上時,如果你願意,可以省略花括號
if (arg == null) return defaultValue;但是,如果主體換行到下一行,則使用花括號
if (overflowChars != other.overflowChars) {
return overflowChars < other.overflowChars;
}if (overflowChars != other.overflowChars)
return overflowChars < other.overflowChars;