[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 = [];
}
~~~
实际上,现有的约定涵盖了大多数类型参数。