[TOC]
命名是編寫可讀,可維護代碼的重要部分。以下最佳實踐可幫助您實現該目標。
## 請務必使用條款。
在整個代碼中,對同一個名稱使用相同的名稱。如果用戶可能知道的API之外已存在先例,請遵循該先例。
~~~
pageCount //一個字段。
updatePageCount ()//與pageCount一致。
toSomething ()//與Iterable的toList()一致。
asSomething ()//與List的asMap()一致。
Point //一個熟悉的概念。
~~~
以下是應該避免的示例:
~~~
renumberPages () //與pageCount混淆不同。
convertToSomething () //與toX()先例不一致。
wrappedAsSomething () //與asX()先例不一致。
Cartesian //大多數用戶都不熟悉。
~~~
目標是利用用戶已經知道的東西。這包括他們對問題域本身的知識,核心庫的約定以及您自己的API的其他部分。通過構建這些知識,您可以減少他們在獲得高效率之前必須獲得的新知識量。
## 避免縮寫。
除非縮寫比未縮寫的術語更常見,否則不要縮寫。如果您縮寫,請正確地將其大寫。
~~~
pageCount
buildRectangles
IOStream
HttpRequest
~~~
以下是應該避免的示例:
~~~
numPages //“num”是number(of)的縮寫
buildRects
InputOutputStream
HypertextTransferProtocolRequest
~~~
## 優先將最具描述性的名詞放在最后。
最后一個詞應該是最具描述性的東西。您可以在其前面添加其他單詞,例如形容詞,以進一步描述該事物。
~~~
pageCount //(頁數)計數。
ConversionSink //用于進行轉換的接收器。
ChunkedConversionSink //一個被分塊的ConversionSink。
CssFontFaceRule // CSS中字體面的規則。
~~~
以下是應該避免的示例:
~~~
numPages //不是頁面的集合。
CanvasRenderingContext2D //不是“2D”。
RuleFontFaceCss //不是CSS。
~~~
## 考慮將代碼讀成句子。
如果對命名有疑問,請使用你的API寫一些代碼,然后嘗試像讀普通語句一樣閱讀他們。
~~~
// "If errors is empty..."
if (errors.isEmpty) ...
// "Hey, subscription, cancel!"
subscription.cancel();
// "Get the monsters where the monster has claws."
monsters.where((monster) => monster.hasClaws);
~~~
以下是存在問題的示例:
~~~
// Telling errors to empty itself, or asking if it is?
if (errors.empty) ...
// Toggle what? To what?
subscription.toggle();
// Filter the monsters with claws *out* or include *only* those?
monsters.filter((monster) => monster.hasClaws);
~~~
嘗試使用API并查看在代碼中使用它時如何“閱讀”會很有幫助,但是你可能會走得太遠。這不是有益的補充文章和講話的其他部分,迫使你的名字從字面上看像一個語法正確的句子。例如:
~~~
if (theCollectionOfErrors.isEmpty) ...
monsters.producesANewSequenceWhereEach((monster) => monster.hasClaws);
~~~
## 首選非布爾屬性或變量的名詞短語。
讀者關注的是房產是什么。如果用戶更關心 如何確定屬性,那么它應該是一個帶有動詞短語名稱的方法。
~~~
list.length
context.lineWidth
quest.rampagingSwampBeast
~~~
以下是反面示例:
~~~
list.deleteItems
~~~
## 首選布爾屬性或變量的非命令性動詞短語。
布爾名稱通常用作控制流中的條件,因此您需要一個在那里讀取的名稱。比較:
~~~
if (window.closeable) ... // Adjective.
if (window.canClose) ... // Verb.
~~~
好名字往往以幾種動詞中的一種開頭:
* 對于一個form表單來說: ,isEnabled,。wasShown willFire到目前為止,這些是最常見的。
* 一個輔助動詞:hasElements,canClose, shouldConsume,mustSave。
* 一個主動詞:ignoresInput,wroteFile。這些很少見,因為它們通常含糊不清。loggedResult是一個壞名字,因為它可能意味著“無論是否記錄結果”或“記錄的結果”。同樣,closingConnection可以是“連接是否正在關閉”或“正在關閉的連接”。當名稱只能作為謂詞讀取時,允許使用主動詞。
將所有這些動詞短語與方法名稱區分開來的是它們不是必要的。 布爾名稱永遠不應該像告訴對象做某事的命令,因為訪問屬性不會更改對象。 (如果屬性確實以有意義的方式修改了對象,那么它應該是一個方法。)
~~~
isEmpty
hasElements
canClose
closesWindow
canShowPopup
hasShownPopup
~~~
反面例子:
~~~
empty // Adjective or verb?
withElements // Sounds like it might hold elements.
closeable // Sounds like an interface.
// "canClose" reads better as a sentence.
closingWindow // Returns a bool or a window?
showPopup // Sounds like it shows the popup.
~~~
>這條規則有一個例外。Angular 組件中的輸入屬性有時會使用命令式動詞來表示布爾設置器,因為這些setter是在模板中調用的,而不是從其他Dart代碼中調用的。
## 考慮省略命名布爾參數的動詞。
這改進了先前的規則。對于布爾值的命名參數,名稱通常在沒有動詞的情況下一樣清晰,并且代碼在調用站點處讀取更好。
~~~
Isolate.spawn(entryPoint, message, paused: false);
var copy = List.from(elements, growable: true);
var regExp = RegExp(pattern, caseSensitive: false);
~~~
## 首選布爾屬性或變量的“正”名稱。
大多數布爾名稱具有概念上的“正面”和“負面”形式,前者感覺就像基本概念,后者是否定 - “開放”和“封閉”,“啟用”和“禁用”等。通常后者的名稱從字面上有否定前者的前綴:“可見”和“ 不可見”,“已連接”和“ 連接已關閉”,“零”和“ 非 零”。
當選擇true代表兩種情況中的哪一種 - 以及屬性的名稱屬于哪種情況時 - 更喜歡積極或更基本的情況。布爾成員通常嵌套在邏輯表達式中,包括否定運算符。如果你的屬性本身就像一個否定,那么讀者就更難以在心理上執行雙重否定并理解代碼的含義。
~~~
if (socket.isConnected && database.hasData) {
socket.write(database.read());
}
~~~
以下是反面例子:
~~~
if (!socket.isDisconnected && !database.isEmpty) {
socket.write(database.read());
}
~~~
此規則的一個例外是負面形式是用戶絕大多數需要使用的屬性。選擇正向的情況將迫使他們在任何地方使用感嘆號(!)對屬性取反。相反,對該屬性使用否定案例可能更好。
對于某些屬性,沒有明顯的正面形式。已刷新到磁盤的文檔是“已保存”或“未更改”? 文檔是否未刷新“ 未保存”或“已更改”?在模棱兩可的情況下,傾向于不太可能被用戶否定或具有較短名稱的選擇。
## 首選一個主要目的是副作用的函數或方法的命令式動詞短語。
可調用成員可以將結果返回給調用者并執行其他工作或副作用。在像Dart這樣的命令式語言中,成員通常主要是因為他們的副作用:他們可能會改變對象的內部狀態,產生一些輸出或與外界交談。
這些類型的成員應該使用命令式動詞短語來命名,該短語闡明了成員所執行的工作。
~~~
list.add("element");
queue.removeFirst();
window.refresh();
~~~
這樣,調用就像執行該工作的命令一樣。
## 如果返回值是其主要目的,則首選函數或方法的名詞短語或非命令性動詞短語。
其他可調用成員幾乎沒有副作用,但會向調用者返回有用的結果。如果成員不需要參數來執行此操作,則通常應該是getter。但是,有時邏輯“屬性”需要一些參數。例如, elementAt()從集合中返回一段數據,但它需要一個參數來知道要返回哪一塊數據。
這意味著該成員在語法上是一個方法,但從概念上講它是一個屬性,并且應該使用描述成員返回內容的短語來命名。
~~~
var element = list.elementAt(3);
var first = list.firstWhere(test);
var char = string.codeUnitAt(4);
~~~
本指南比前一個指令影響小。有時,一個方法沒有副作用,但仍然是簡單的名稱與動詞短語像 list.take()或string.split()。
## 如果要引起對其執行的工作的注意,請考慮函數或方法的命令式動詞短語。
當一個成員產生沒有任何副作用的結果時,它通常應該是一個getter或一個名詞短語名稱描述它返回的結果的方法。但是,有時產生該結果所需的工作很重要。它可能容易出現運行時故障,或者使用網絡或文件I/O等重量級資源。在這種情況下,您希望調用者考慮成員正在進行的工作,為成員提供描述該工作的動詞短語名稱。
~~~
var table = database.downloadData();
var packageVersions = packageGraph.solveConstraints();
~~~
但請注意,此指南比前兩個更柔和。操作執行的工作通常是與調用者無關的實現細節,并且性能和健壯性邊界隨時間而變化。在大多數情況下,成員的命名基于他們為調用者做了什么,而不是他們為調用者怎么做。
## 避免用get為前綴來命名方法名。
在大多數情況下,該方法應該是除去get的成員的取值器 。例如,名為getBreakfastOrder()的方法,定義一個名為的 breakfastOrder的取值器。
即使成員確實需要成為一個方法,因為它需要參數或者getter不能滿足要求,你仍然應該避免get。與之前的指南一樣,要么:
* 簡單地刪除get并使用名詞短語名稱例如breakfastOrder() 如果調用者主要關心方法返回的值。
* 使用動詞短語的名字,如果調用者關心的工作正在做,但需要挑選一個比get更精確地描述的動詞,如 create,download,fetch,calculate,request,aggregate,等。
## 優先使用to__()命名方法,如果這個方法拷貝一個對象的狀態到另一個狀態。
轉換方法是返回一個新對象的方法,該對象包含幾乎所有接收器狀態的副本,但通常采用某種不同的形式或表示形式。 核心庫有一個約定,即這些方法的名稱首先跟隨結果類型。
如果您定義轉換方法,遵循該約定會很有幫助。
~~~
list.toSet();
stackTrace.toString();
dateTime.toLocal();
~~~
## 優先使用as__()命名方法,如果他返回由原始對象支持的不同表示。
轉換方法是“快照”。 生成的對象具有自己的原始對象狀態的副本。 還有其他類似轉換的方法返回視圖 - 它們提供了一個新對象,但該對象引用了原始對象。 稍后對原始對象的更改將反映在視圖中。
您要遵循的核心庫約定是as___()。
~~~
var map = table.asMap();
var list = bytes.asFloat32List();
var future = subscription.asFuture();
~~~
## 避免描述函數或方法名稱中的參數。
用戶將在調用點看到參數,因此通常無法在名稱本身中引用它。
~~~
list.add(element);
map.remove(key);
~~~
以下是反面例子:
~~~
list.addElement(element)
map.removeKey(key)
~~~
但是,提及一個參數可以將其與其他類似命名的采用不同類型的方法消除歧義:
~~~
map.containsKey(key);
map.containsValue(value);
~~~
## 在命名類型參數時,請遵循現有的助記符約定。
單字母名稱并不完全有啟發性,但幾乎所有通用類型都使用它們。幸運的是,他們大多以一致的助記方式使用它們。慣例是:
*E對于集合中的元素類型:
~~~
class IterableBase<E> {}
class List<E> {}
class HashSet<E> {}
class RedBlackTree<E> {}
~~~
* K以及關聯集合中V的鍵和值類型:
~~~
class Map<K, V> {}
class Multimap<K, V> {}
class MapEntry<K, V> {}
~~~
* R對于用作函數的返回類型或類的方法的類型。這種情況并不常見,但有時會出現在typedef中,也會出現在實現訪問者模式的類中:
~~~
abstract class ExpressionVisitor<R> {
R visitBinary(BinaryExpression node);
R visitLiteral(LiteralExpression node);
R visitUnary(UnaryExpression node);
}
~~~
* 否則,將T,S和U用于具有單個類型參數的泛型,并且周圍類型使其含義明顯。 這里有多個字母允許嵌套而不會遮蔽周圍的名稱。 例如:
~~~
class Future<T> {
Future<S> then<S>(FutureOr<S> onValue(T value)) => ...
}
~~~
這里,泛型方法then\<S>()用于S避免覆蓋T 在Future\<T>。
## 如果上述情況都不合適,則可以使用另一個單字母助記符名稱或描述性名稱:
~~~
class Graph<N, E> {
final List<N> nodes = [];
final List<E> edges = [];
}
class Graph<Node, Edge> {
final List<Node> nodes = [];
final List<Edge> edges = [];
}
~~~
實際上,現有的約定涵蓋了大多數類型參數。