程序员“头痛”起名根治指南

作者:marinewu,腾讯PCG客户端开发工程师

| 导语基于 Readability 的出题阅卷经验的一些不完全的具体的命名指南。

There are only two hard things in Computer Science: cache invalidation and naming things.

-- Phil Karlton


软件开发中一个著名的反直觉就是“起名儿”这个看上去很平凡的任务实际上很有难度。身边统计学显示,越是有经验的程序员,越为起名头痛。给小孩起名儿都没这么费劲,对吧,王浩然?

命名的困难可能来自于以下几个方面:

命名就像写作,会写字不等于会写作。而且,命名更多像是一门艺术[注](此处艺术的含义取自于 Knuth -- 命名会诉诸品味和个人判断。),不存在一个可复制的命名操作手册。

本文描述一些实用主义的、可操作的、基于经验的命名指南,并提供了一个代码词汇表,和部分近义词辨析。本文没有涉及讨论名字的形而上学,例如如何做更好的设计和抽象以利于命名,也没有涉及如何划分对象等,也无意讨论分析哲学。

命名原则

命名是一门平衡准确性和简洁性的艺术 -- 名字应该包含足够的信息能够表达完整的含义,又应该不包含冗余的信息。

准确 Precision

名字最重要的属性是准确。名字应该告诉用户这个对象/方法的意图 -- “它是什么” 和 “它能做什么”。 事实上,它是体现意图的第一媒介 -- 名字无法表现含义时读者才会阅读文档。

名字应该是有信息量的、无歧义的。以下一些策略可以增加名字的准确度:

可读

最基本的语法原理,是一个类(Class/Record/Struct/... 随你喜欢)应该是一个名词,作为主语。一个方法应该是动词,作为谓语。 换言之,类“是什么”,方法“做什么”, 它们应该是可读的,应该是 [Object] [Does ...] 式的句子。

可读是字面意思,意味着它应该是通顺的,所以应该:

避免 API 中使用缩写

就像是给老板的汇报中不会把商业计划写成 Busi Plan 一样,也不应该在公开 API 中使用一些奇怪的缩写。现在已经不是 1970 年了,没有一个方法不能超过 8 个字符的限制。把类/方法的名字写全,对读者好一点,可以降低自己被同事打一顿的风险。

creat 是个错误,是个错误,是个错误!

但是,首字母缩略词的术语是可行并且推荐的,如 Http, Id, Url

以下是可用的、得到普遍认可的缩写:

未得到普遍认可的缩写:

req/resp/svr 在服务名称中很常见。这非常糟糕。请使用全称。

再次说明:以上的说明是针对 API 名称,具体包括公开对象/函数名字、RPC/Web API 名字。在局部变量使用缩写不受此影响。

避免双关

对类/方法的命名,不要使用 2 表示 To, 4 表示 For。

func foo2Bar(f *Foo) *Bar   // BAD

func fooToBar(f *Foo) *Bar  // GOOD

func to(f *Foo) *Bar        // Good if not ambiguous.

2/4 这种一般只有在大小写不敏感的场合才会使用,例如包名 e2e 比 endtoend 更可读。能区分大小写的场合,不要使用 2/4。

合乎语法

虽然不能完全符合语法(例如通常会省略冠词),但是,方法的命名应该尽量符合语法。例如:

class Car {
    
    void tireReplace(Tire tire);  // BAD, reads like "Car's tire replaces"
    
    void replaceTire(Tire tire);  // GOOD, reads like "replace car's tire"
}

关于命名的语法见“语法规则”一章。

使用单一的概念命名

命名本质上是分类(taxonomy)。即,选择一个单一的分类,能够包含类的全部信息,作为名字。

考虑以下的角度:

例如,把大象装进冰箱,需要有三步 -- 打冰箱门打开,把大象放进去,把冰箱门关上。但是,这可以用单一的概念来描述:“放置”。

class Fridge {
    public void openDoorAndMoveObjectIntoFridgeAndCloseDoor(Elephant elphant); // BAD

    public void put(Elephant elphant); // GOOD
}

应该使用所允许的最细粒度的分类

避免使用过于宽泛的类别。例如,这世界上所有的对象都是“对象”,但显然,应该使用能够完整描述对象的、最细颗粒度的类别。

class Fridge {
    public put(Elephant elephant);   // GOOD.

    public dealWith(Elephant elephant);  // BAD: deal with? Anything can be dealt with. How?
}

简而言之,名字应该是包含所有概念的分类的下确界

简洁 Simplicity

名字长通常会包含更多信息,可以更准确地表意。但是,过长的名字会影响可读性。例如,“王浩然”是一个比“浩然·达拉崩吧斑得贝迪卜多比鲁翁·米娅莫拉苏娜丹尼谢莉红·迪菲特(defeat)·昆图库塔卡提考特苏瓦西拉松·蒙达鲁克硫斯伯古比奇巴勒·王”可能更好的名字。(来自于达啦崩吧)

在此,我提出一个可能会有争议的观点:所有的编程语言的命名风格应该是趋同的。不同于通常认为 Java 的命名会倾向于详尽,Go 的命名会倾向于精简,所有的语言对具体的“名字到底有多长”的建议应该是几乎一样的 -- 对外可见应该更详细,内部成员应该更精简。具体地:

上述规则像是 Go 的风格指南。但是,并没有规定 Java 不能这样做。事实上,Java 的冗长是 Java 程序员的自我束缚。即使在 Java 的代码里,也可以这样写:


public class BazelRuntime {

    public boolean exec(Command cmd) {
        String m = cmd.mainCommand();  // YES, you can use single-letter variables in Java.
        // ...
    }
}

同样,在 Go 的代码中也不应该出现大量的无意义的缩写,尤其是导出的结构体和方法。

type struct Runtime {} // package name is bazel, so bazel prefix is unnecessary

type struct Rtm {}  // BAD. DO NOT INVENT ABBREVIATION!

当然,由于语言特性,在命名风格上可能会有差异。例如,由于 Go 的导入结构体都需要加包前缀,所以结构名中通常不会重复包前缀;但 C++/Java 通常不会依赖包名。但是,上述的原则仍然是成立的 -- 可见度越高,应该越少依赖上下文,并且命名越详尽。

Google Go Style Guide 是唯一详尽讨论命名长度的风格指南,非常值得参考,并且不限于 Go 编程: https://google.github.io/styleguide/go/decisions#variable-names

一致 Consistency

另一个容易被忽略的命名的黄金原则是一致性。换言之,名字的选取,在项目中应该保持一致。遵守代码规范,避免这方面的主观能动性,方便别人阅读代码。通常情况下,一个差的、但是达成共识的代码规范,也会远好于几个好的、但是被未达成共识的规范。

这个图我能用到下辈子: [xkcd 927]()

但是仅符合代码规范是不够的。如同所有的语言,同一个概念,有多个正确的写法。

考虑以下的例子:

message Record {
    int32 start_time_millis = 1;  // OK

    int32 commited_at = 2;  // Wait. Why not commit_time? Anything special?

    int32 update_time = 3; // What unit? Also millis?

    google.types.Timestamp end_time = 4;   // WTF? Why only end_time is typed?
}

几种都是合理的(虽然不带单位值得商榷)。但是,如果在一个代码中出现了多种风格,使用起来很难预测。您也不想使用这样的 API 吧?

所以,在修改代码的时候,应该查看上下文,选择已有的处理方案。一致性大于其它要求,即使旧有的方案不是最好的,在做局部修改时,也应该保持一致。

另一个可考虑的建议是项目的技术负责人应该为项目准备项目的专有词汇表。

语法规则

类/类型

类应该是名词形式,通常由单个名词或名词短语组成。其中,主要名词会作为名词短语的末尾。例如 Thread, PriorityQueue, MergeRequestRepository。

接口

接口的命名规则和类相同。除此之外,当接口表示可行动类型时,可使用另一个语法,即 Verb-able。例如:

public interface Serializable {
  byte[] serialize();
}

public interface Copyable {
  T copy();
}

public interface Closable {
  void close();
}

(Go 通常不使用这个命令风格。只在 Java/C++ 中使用。)

辅助类

只在 Java(注1)中使用。一个类或概念所有的辅助方法应该聚合在同一个辅助类。这个类应该以被辅助类的复数形式出现。不推荐使用 Helper/Utils 后缀表示辅助类。尤其不推荐使用 Utils/Helpers 做类名,把所有的辅助方法包进去。如:

class Collections {} // For Collection

class Strings {} // For String

class BaseRuleClasses {} // For BaseRuleClass

class StringUtils {} // WORSE!

class StringHelper {} // WORSE!

注1: 客观来说,这适用于所有强制 OOP 的语言(所有强制把方法放在类里的语言)。但是除了 Java, 没有别的语言这么烦啦。

方法

方法通常是谓语(动词),或是 谓宾(动词+名词) 结构。注意以上语法中,动词都在最前端。例如:

class Expander {
    String expand(String attribute);  // 主-谓

    String expandAndTokenizeList(String attribute, List values);  // 主-谓-宾
}

除此之外,有以下特例值得注意:

访问器 Getter

直接使用所 Get 的对象的名词形式,即 Foo()。不要使用 GetFoo()

Java: 所有的 Getter 都需要一个 get 前缀是来自于过时的 Java Beans Specification,以及 Javaer 的思想钢印。

func Counts() int;  // GOOD

func GetCounts() int;  // BAD: UNNECESSARY.

断言 Predicate

断言函数指返回结果是布尔型(即真伪值)的函数。它们通常有以下命名格式:

系动词: 主-系-表

isAdjective()areAdjective() 格式,表示是否具有某个二元属性。类似于 Getter,可以省略系语,只使用表语,即: adjective()

func IsDone() bool {} // OK-ish. But could be better.

func Done() bool {} // GOOD. Why bother with is/are?

func CheckEnabled() bool { // BAD. Nobody cares if it is "checked". Just tell the user if it is enabled.
    return enabled;
}

func Enabled() bool {} // GOOD.

情态动词: 主-助谓-谓-(宾/表)

情态动词也是常见的断言形式。常见的是以下三个:


func Compile(s string) Regexp, error  // Returns error upon failure

func MustCompile(s string) Regexp   // Panics upon failure

func (r Regexp) CanExpand(s string) bool // Whether s is legal and can be expanded

func (r Regexp) Expands(s string) bool // Whether r expands s, i.e. r can expand s.

func (r Regexp) ShouldReset() bool // Whether the state requires reset. Does not perform de-facto reset.

func (r Regexp) Reset()   // De-facto reset.

表尝试: 主-maybe/try-谓-(宾/表)

上文 "must" 的反面,表示尝试性的执行,并且失败不会造成严重后果:

void maybeExecute() {
  if (!predicate()) {
    return;
  }
  // execute
}

std::unique_ptr ParseOrDie(std::string_view dateTime);

bool TryParse(string_view dateTime, DateTime* dateTime);

第三人称单数

另一个常见场景是我们希望表示类拥有某些属性,但是使用助动词并不合适。如果前文描述,常见的选择是使用第三人称单数的静态动词(Stative verb)(注1) 表示类满足给定断言。

func (l *List) Contains(e interface{}) bool

func (r Regexp) Expands(s string) bool

注1: 简单地说,静态动词是表示状态的动词,与动态动词(Dynamic verb)表示动作对应。或言“持续性动词”。

一阶逻辑 First-order logic, Predicate Logic

一阶逻辑量词也是常见的前缀:

语法: <一阶量词><动词|形容词>

class Stream {
    // Returns whether all elements of this stream match the provided predicate.
    boolean allMatch(Predicate<? super T> p);
    // Returns whether any elements of this stream match the provided predicate.
    boolean anyMatch(Predicate<? super T> p);
    // Returns whether no elements of this stream match the provided predicate. 
    boolean noneMatch(Predicate<? super T> predicate)
}

介词

介词经常与某些动词固定搭配,因此,通常可以省略动词,而只使用介词作为方法名称。


class Foo {

    public List toList(); // Convert to (Construct a new instance of) a new List. Creates a new list.

    public List asList(); // Return a List as a different **view**. Holds reference of the original reference.

    static Foo of();    // Construct Foo as a factory method.

    static Foo from(Bar);  // Construct Foo from Bar.

    Foo with(Bar);  // Construct a new Foo by replacing Bar with new Bar.

    void onClick(ClickEvent e);  // Act upon click event.
}

参考资料:


词汇表

下文按用途归类了常见动词和名词,并对同义近义词进行了辨析。

类/名词

类继承

Abstract/Base
Impl
Default

Java


// https://github.com/bazelbuild/bazel with some fake examples

public interface SkyFunction {}

public abstract class AbstractFileChainUniquenessFunction implements SkyFunction {}

public class DefaultSkyFunction implements SkyFunction {}

public class BazelModuleInspectorFunction implements SkyFunction {}

public interface VisibilityProvider {}

public final class VisibilityProviderImpl {}

C++

// levelDB
// includes/db.h
class DB {
 public:
  virtual ~DB(); // MUST!
  virtual Status Delete(const WriteOptions&, const Slice&) = 0;
}

// db/db_impl.h
class DBImpl : public DB {}

// rocksDB
// Base class
class CacheShardBase {}

Go

异常

Exception/Error

Java

所有的异常扩展应该以 Exception 为后缀。所有的错误应该以 Error 为后缀。 对异常和错误的区别请参见 https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html

public class IllegalArgumentException;

public class OutOfMemoryError;

C++

C++ 的 exception 通常指语法特性,与 throw 对应,而 error 可以用来表示具体的异常错误。

// stdlib
std::exception;
std::runtime_error

Go

所有的错误都是 error。因此,所有自定义的对 error 的扩展都以 Error 作为后缀。

os.PathError

测试

Test

Java/Go/C++ 均使用 Test 作为测试类的后缀。

模块

Module/Component

Module/Component 通常会在框架中使用。不同的语言/框架对于 Module/Component 有不同的定义。 在非框架代码中应该减少使用 Module/Componenet 等命名,因为可能与已有框架冲突,并且 Module/Componenet 过于宽泛而缺少实质意义。

Module/Component 是意义相近的词,都可以表示“模块”或者“组件”。两者虽然有细微的分别,但是框架通常都显式(即在文档中指定,或者通过框架语义约束)地把它们定义为框架语境下的某些结构层级。

总结,Module/Component 命名应该注意:

服务

Service

Service 通常用于作为 C-S 架构下的服务提供者的名称的后缀,如:

HelloService

但除此之外,Service 可以表示任何长期存活的、提供功能的组件。例如:

BackgroundService // Android 后台服务

ExecutorService // 线程池执行服务,也是服务

BAD: 不要使用 Svr 缩写。使用全称。

容器

Holder/Container/Wrapper

Holder/Container/Wrapper 都表示“容器”,具有同一个意图:为一个类增加额外的数据/功能,例如:

通常的结构如下:

class ObjectHolder {
  private final Object object;
  // other stuff ...
  
  public Object object() {}

  // Other methods
}

这三个词没有区别。在同一个项目中,应该保持一致

控制类

Manager/Controller

Manager 和 Controller 是同义词。它们通常用来表示专门控制某些类的类。

这两个词有以下几个常见场景:

即使如此,Manager/Controller 是无意义词汇,出现时充满了可疑的味道 -- 类应该管理它们自己。 Controller/Manager 多了一层抽象,而这很可能是多余的。 认真考虑是否需要 Manager/Controller。

辅助类

Util/Utility/Utils/Helper/{ClassName}s

辅助类是强制 OOP 的语言(i.e. Java) 所需要的特殊类。通常它们是一些辅助方法的合集。

Java

将与某个类型相关的辅助方法放在一个类中,并且以复数形式命名辅助类。如:

// Java std lib
public final class Strings {}

public final class Lists {}

public final class Collections {}

避免使用 Util/Utility/Utils/Helper。它们是无意义词汇。

C++

使用全局方法。如果担心命名污染,将之置入更细粒度的 namespace。

Go

使用全局方法。

函数式

Function/Predicate/Callback

Function 通常表示任意函数。 Predicate 表示命题,即通常返回类型为 bool。 Callback 指回调函数,指将函数作为参数传递到其它代码的某段代码的引用。换言之, Function 可以作为 Callback 使用。因此,Callback 在现代函数式编程概念流行后,通常很少使用。

Java

熟悉 Java 8 开始提供的函数式语义。如果可以使用标准库的语义,不要自己创建名词。 注意 Function 指单入参、单出参的函数。如果是多入参的函数,直接定义 FunctionalInterface 并且按用途命名,例如 OnClickListener.listen(Context, ClickEvent)

// java.util.function
Predicate   // f(T) -> bool
Function // f(T) -> R
Consumer    // f(T) -> void
Supplier    // f() -> T

C++

first-class 函数的标准类型为 std::function

C++ 表示命名函数对象的惯用法是 fun。Stdlib 中会缩写 functionfun,如 pmem_fun_ref,因此与 stdlib 一致,在代码中不要使用 fn 或是 func

Go

Go 通常使用 Func 或是 Fn 表示函数类型。

type ProviderFunc func(config ConfigSource, source PassPhraseSource) (Provider, error)

type cancelFn func(context.Context) error

在同一个项目中,应该保持一致

作为参数时,函数不会特意标明 Fn,而是遵从普通的参数命名方式:

func Sort(less func(a, b string) int)

换言之,函数是一等公民。

设计模式类

类/方法通常都按它们的行为模式来命名。恰好,设计模式就归类抽象了很多行为模式。所以设计模式提供了很多好的名字。

创建式

Factory: 工厂模式。通常,使用工厂方法即可,不需要一个额外的工厂类。只有当工厂特别复杂,或者工厂有状态时再考虑使用工厂类。

Builder:构建者模式。一般来说 Builder 都是作为 inner class,如

class Foo {
  static class FooBuilder {}
}

行为式

Adapter: 适配器

在 GoF 中 Adapter 本来是将一个类封装以可以被作为另一个类型被调用,这样调用方不需要额外改变代码。这种用途通常被内化到容器上,见上文[容器类]部分。

在现代,Adapter 更多地被作为 数据类 -> 数据类的转化,如常见的 pb -> pb:

class ProtoAdapter {}

Decorator:装饰器

在 GoF 中 Decorator 本来是将一个类作为抽象类,通过组合+继承实现添加功能。实际上现代的编程实践中往往通过直接提供一个容器的封装提供装饰功能,见上文 [容器类]部分。 所以 GoF 式 Decorator 并不常见,除非像 Python 在语法层面提供了装饰器。在 Java 中类似的功能是注解

Delegation:委派模式

GoF 中是非常基本的模式:由一个类负责接受请求,并把请求转发到合适的实例类中执行。

class RealPrinter {}

class Printer {
  RealPrinter printer;
}

Delegate 非常常见,也提供了两个名字,请注意区分:

Facade: 外观模式

GoF 中 Facade Pattern 通常是指为子系统提供一个更高层的统一界面,屏蔽子系统的独有的细节。 在现实中,Facade 通常用来为非常复杂的类/系统定义一个较为简化的界面,如:


// proto, extremely complicated TripResponse
message TripResponse {
  // ...
  // ...
  string last_message = 3279;
}

class TripResponseFacade {
  private final TripResponse response;

  Trip trip();

  Endpoint source(); // Abstracted and processed
  Endpoint target(); // Abstracted and processed
}

Facade 与 Adapter 的主要区别在于 Facade 的主要目的是为了简化,或者说更高层次的抽象,并且通常简化的界面不服务于专门的对接类。 Adapter 通常是为了一个特定的对接类实现。

注意 Facade 命名通常可以省略。仅当你的意图是明确告知用户这是关于某个类的外观时使用。

Proxy:代理模式

GoF 中代理模式用来添加一层抽象,以对实际类进行控制,并添加某些行为(如 lazy/memoized),或是隐藏某些信息(例如可见性或是执行远程调用)。

Proxy 与 Facade 的区别在于 Proxy 通常是为了额外的控制/记录等行为,而非只是为了更高的抽象/简化。

注意 Proxy 作为代码模式时,通常不应该出现在命名之中。使用具体的 Proxy 的目的作为命名,如 LazyCar 或是 TracedRuntime,而非 CarProxy 或是 RuntimeProxy

Proxy 还有另一个含义就是真正的“代理”,如代理服务器。在这种情况下,使用 Proxy 是合适且应该的。这也是另一个为什么代理模式不应该用 Proxy 命名的原因。

Iterator: 迭代器

时至今日仍然最常见的模式之一。Interator 有以下两个术语,不要混淆:

Visitor: 访问者模式

访问者模式用来遍历一个结构内的多个对象。对象提供 accept(Visitor) 方法,调用 Visitor.visit 方法。

即使如此,Visitor 应该并不常见,因为它可以简单地被函数式的写法替换:

class Car {
  void accept(Consumer visitor); // No longer need to define Visitor class.
}

Observer/Observable: 观察者模式

Observer/Publisher/Subscriber/Producer/Consumer

时至今日最常见的模式之一。和事件驱动编程(Event-based)有紧密关系 -- Oberservable 发布消息,所有注册的 Obeserver 会接收消息。 Publisher/Subscriber 也是类似的,它们的区别在于 Observer 模式往往是强绑定的 -- 注册和分发通常在 Observable 类中实现; 而 PubSub 模式通常有专门的 Message Broker,即 Publisher 与 Subscriber 是完全解耦的。

PubSub 与 Producer/Consumer 的区别是:

所有的消息注册的模式由三部分组成:

关于命名参见 [事件] 部分。

Strategy:策略模式

Strategy/Policy

策略模式在 GoF 中用以指定某个行为在不同场景下的不同实现,作为“策略”。

Strategy 模式往往不是很显式。现代通常使用 Strategy 表示实际的“策略”,即对信息不同的处理策略,而不采取 Strategy 模式的含义。

在“策略”这个语义中,Strategy/Policy 没有区别。在同一个项目中,应该保持一致

Command:命令模式

命令模式在 GoF 中以类开代表实际行动,将行动封装,以支持重复、取消等操作。

Command 在现代编程实践中可以通过简单的函数式方案替换,如:

Function command; // Java

std::function command; // C++

type Command func(T*) T* // Go

现代通常使用 Command 表示实际的“命令”,而不采取 Command 模式的含义。

Null Object 模式

Tombstone

Null Object 模式不在 GoF 当中。它是一个用来代替 null 的 object,对其所有的操作都会被吞掉。 Null Object 主要是为了避免空指针。 合理的零值,例如 go time.Time = 0,也可以理解为一种 Null Object。

通常会有一个专门的对象表示 Null Object。可以借用 Tombstone 表示 Null Object。

Object Pool 对象池模式

Pool

对象池模式不在 GoF 当中。它是将一系列昂贵的对象创建好放在一个池子中,并使用户通过向池子申请对象,而不再自己手动地创建/销毁对象。最著名的池化的例子是线程池,即 ThreadPool。

Pool 通常用来表示对象池子,例如 ThreadPool, ConnectionPool

Arena

Arena 是指 Region-based memory management,是指一片连续的内存空间,用户在其中分配创建对象,管理内存。

前/后缀

并发/异步

Concurrent
Synchronized
Async

有时候我们需要特别标明一个类是线程安全的。通常这是特意为了与另一个线程不安全的实现做区分。典型的例子是 HashMapConcurrentHashMap。如果一个类只是单纯是线程安全的,那么通常不需要在名字里特意说明,在文档里说明即可。

例如:

/** This class is designed to be thread safe. */
class SomeClassThreadSafe {}

/** This class is immutable thus thread safe. */
class SomeClassImmutable {}

Concurrent 通常是用来说明该类是线程安全的前缀。Synchronized 是另一个在 Java 中可用的标明类是线程安全的前缀。但是,这通常说明这个类是通过 synchronized 机制来保证线程安全的,所以只在 Java 中使用。

另一个常见的场景是同一个方法有两种实现:同步阻塞和异步不阻塞的。在这种情况下,通常会命名异常不阻塞的方法为 {synchronizedMethod}Async,例如:

public T exec();
public Future execAsync();

如果一个异步的方法并没有对应的同步方法,通常不需要加 Async 后缀。

在 Go 中,如果一个方法是意图在其它协程中异步执行,不需要加 Async 后缀。

缓存/惰性

Cached/Buffered
Lazy
Memoized

名词辨析:

注意 Buffered 不应该与 Buffer 混淆。 Buffer 作为名词专指“缓冲区”。
注意 Cached 不应该与 Cache 混淆。 Cache 作为名词专指“缓存”。

Cached/Buffered 应该在项目中是一致的。 Cached/Lazy/Memoized 取决于对象是被获取的,还是创建的,还是计算获得的。

不可变性

Mutable
Immutable

Mutable 显式地声明一个类是可变的,Immutable 显式地声明一个类是不可变的。 通常情况下,类似于并发安全性,是否可变应该在类文档中说明,而不应该在类名中,显得臃肿。只有当一个类同时有可变/不可变版本时,可以使用 Class/ImmutableClass

存储/数据/处理

数据类

Object
Data
Value
Record
Entity
Instance

上面几个都可以用来表示一个表示数据的类。但是这些词是典型的“无意义词汇”,如果把它们从名字中删除,仍然可以表示完整意义,那么应该删掉。

class CarObject {} // Bad
class CarEntity {} // Bad
class CarInstance {} // Bad
class Car {} // Good

class MapKey {}
class MapValue {} // OK. Couldn't be shortened.


class LoggingMetricsData {} // Bad
class LoggingMetricsValue {} // Bad
class LoggingMetricsRecord {} // Bad
class Logging Metrics {} // Good

class DrivingRecord {} // OK. Couldn't be shortened.

Statistics/Stats

表示“统计数据”。 Stats 是公认的可用的 Statistics 的缩写,Java/C++/Go 均可。

存储

Storage
Database
Store
DB

Cache

Verbs:
- save/store/put

Storage/Database/Store/DB 都可以作为“存储服务”,即广义上的“数据库”(不是必须是完整的 DBMS)。 其中,在 C++/Go 中 DB 是常见且可接受的。在 Java 中通常使用全称。

项目内应该选择一个术语保持一致。

save/store/put 在数据库类中是同义词。同一个项目中应该保持一致

数据格式

Schema
Index
Format
Pattern

名词辨析:

哈希

Hash/Digest/Fingerprint/Checksum

Hash/Digest 哈希是一种将任何数据映射到一个较小的空间的方法。映射通常被称为哈希函数(Hash Function),映射值通常被称为摘要(Digest)

 Hash(Data) = Digest

Checksum 出自编码论,可以理解为一种特殊的哈希函数,用来检查文件的完整性。换言之,如果一份数据出现了任何变动,Checksum 应该期待会改变。(但是 Checksum 实际上并不要求唯一性,见 Fingerpint)

Fingerprint 类似于 Checksum,但是 Fingerprint 通常更严格,它通常要求最少有 64-bit,使得任何两个文件只要不同,几乎(概率意义上接近 2^-64)不可能有同一份指纹,即唯一性。(但是 Fingerprint 的定义不要求密码安全性即 cryptographic)

所以 Checksum 只是作为文件变更校验,而 Fingerprint 可以作为数据的唯一标记。

在命名时,优先使用 Fingerprint/Checksum,或其它特定指定用途的术语。当以上均不合适时,回退到更泛化的概念,即 Digest。

流式编程

Stream
Source/Sink
Pipe/Piped

流式编程通常有自己的专有词汇表。具体地:

原则是:选择你的团队里最常使用的流式处理系统所使用的词汇表。

状态

State/Status

很讽刺地,很多人认为这两个词有区别,但是他们认为区别的点各不相同。见下文参考文献。笔者倾向于认为它们其实没什么本质区别。

鼓励使用 State 表示状态。因为 HTTP 和 RPC 已经占用了 Status 这个术语,为了避免误解,使用 State 表示自定义状态。

参考:

计数

Num/Count/Size/Length/Capacity

方法/动词

动词是句子的精髓。选择精准的动词是代码可读性的关键。 本章对动作做了分类,并且提供了部分备选。如果动词有反义词,它们会被聚合在一个词条中。 本章的词汇有两种:

创建/提供

Producer/Provider/Supplier/Generator/Constructor/Factory
Builder.build

Verbs:

创建/提供名词辨析:

动词辨析:

消费

Consumer.accept/consume/poll

消费名词:

注意区分轮 xun 中文的歧义:

注意轮询是 poll 不是 pull,虽然后者直觉上是“拉取,但 poll 强制间断性地主动地采样/获取数据,是正式的计算机术语。

查找

Verbs:
- find/search/query

同义词。推荐在项目中保持一致。 具体地,这几个词实际上有细微的不一致。通常情况下它们可能有以下区分:

参考 https://stackoverflow.com/questions/480811/semantic-difference-between-find-and-search

拷贝

Verbs:
- copy/clone

同义词。遵循语言惯例。

Java 使用 clone。 Go/C++ 使用 copy。

添加

Verbs:
- add/append/put/insert/push

动词辨析:

对于自定义的可添加 api,应该贴近底层的标准库的数据结构所使用的动词。作为泛用的添加,使用 add。

更新

Verbs:
- set/update/edit

同义词。在代码 API 中使用 set,在 RPC API 中使用 update。

删除

Verbs:
- remove/delete/erase/clear/pop

动词辨析:

编排

Scheduler/Dispatcher/Coordinator/Orchestrator/Delegator
- Verb: schedule/dispatch/orchestrate

Scheduler/Dispatcher 均借用于操作系统概念。

名词辨析:

检查/验证

Validator/Checker/Verifier
- Verb: validate/check/verify/assert

Validation/Verification 的明确区分来自于软件测试。

在程序中,不沿用这种区分。通常:

具体地:


public void process(String s, ComplicatedObject co) {
  checkNotNull(s); // check
  validateComplicatedObject(co); // validate
}


@Test
public void testProcess() {
  process("ss", co);

  Truth.assertThat(...); // assert
  verifyZeroInvocations(co); // verify
}

执行/操作

Task/Job/Runnable

Executor/Operator/Processor/Runner
- Verb: exec/execute/do/process/run

名词辨析:

但是,推荐不做区分,认为它们都是同义词。使用 Task 或者 Job 作为类名。

名词辨析: Processor/Executor/Operator 是从计算机架构借用的概念。

但是,推荐不做区分,认为它们都是同义词。日常编程中,使用 Executor 作为 Job 执行器。

开启 vs 关闭

toggle/switch/enable/disable/turnOn/turnOff/activate/deactivate

二元状态的开启关闭。上述全是同义词。

在项目中保持统一。注意比起 toggle(bool)switch(bool),更推荐分离的 enable/disable

读取 vs 写入

Reader/Prefetcher/Fetcher/Downloader/Loader
- Verb: read/get/fetch/load/retrieve
Writer/Uploader
- Verb: write/upload

Lifecycle:
- open/close

名词辨析:

优先使用 read/fetch/download,当均不合适时,回退到 load。

序列化 vs 反序列化

Serializer
- Verb: serialize/pack/marshal
Deserializer
- Verb: deserialize/unpack/unmarshal

动词辨析:

但是,不需要做这个区分。可以认为它们都是同义词。按语言惯例使用:

注意反序列化是 deserialize, 比 unserialize 更常见。 但 pack -> unpack, marshal -> unmarshal。

转换

Applier/Converter/Transformer/Mapper
- Verb: apply/convert/transform/map/to/translate

可以认为它们都是同义词。在项目中应该保持一致。 严格来说,Mapper 更多指同一数据的两种形式的双向映射,例如数据库存储和运行时对象。 在 Applier/Converter/Transformer 中,Applier 最为常见,因为源自设计模式。 Mapper 在框架中较常见。

匹配

Filter/Matcher
- Verb: query/filter/match

可以认为它们都是同义词。 在项目中应该保持一致。

事件

Event

Listener/Notifier Verbs: notify
Observer/Observable Verbs: observe
Handler Verbs: handle
Publisher/Subscriber
Publisher/Consumer

在 [Observer Pattner: 观察者模式] 中已经解释。

见 https://stackoverflow.com/questions/42471870/publish-subscribe-vs-producer-consumer

文本处理

Regex/Pattern/Template

Pruner/Stripper/Trimmer
Formatter/Prettier
Resolver/Parser/Expander

- Verb: compile/parse/resolve/expand
- Verb: format/split/separate/merge/join

通常,一个程序中有 20% 的代码在处理字符串。所以与文本相关的内容非常多。这里没有列出全部。

“模板”名词解析:

“修剪”动名词解析:

但是,Prune/Strip/Trim 在编程中通常认为是同义词。它们通常情况下:

语言可能会为之赋予特殊含义,例如在 Java 11 中,Trim 会清理掉所有的普通空格,而 Strip 会清理掉所有的 Unicode 空格。

“格式化”动名词解析:

“解析”动名词解析:

生命周期

Lifecycle

Initializer/Finalizer

Verb:
- init/setup/prepare
- pause/resume
- start/begin
- end/terminate/stop/halt
- destroy/release/shutdown/teardown

生命周期解析: 一个对象的生命周期,称为 Lifecycle,通常有以下流程:

计算

Calculator

Verb:
- compute/calculate/calc

使用 Calculator 而非 Computer 表示某个运算的执行器。Computer 虽然也是“计算器”,但是在代码语境下有歧义。

compute/calculate/calc 可以认为是同义词。如果是 Calculator,使用 calculate。其它情况下,使用 compute。

元数据(配置/环境/...)

Option/Config/Configuration/Setting/Preference/Property/Parameter/Argument

Context/Environment

Info/Metadata/Manifest/Version

配置名词解析: 这个有类似的名词辨析,但是它们在编程时通常认为都是“配置”的同义词。它们还会出现在用户界面,尤其是 Settings/Options/Preferences。

在编程的角度,Option/Config/Configuration 是同义词,均表示配置。惯例使用 Options 作为子类定义一个类所需的配置,尤其是作为依赖注入时。

使用 Property 表示单个属性, 而且通常是 k-v 结构。换言之,Option/Config 通常由多个 Properties 组织。只有当 Property 是动态属性时,才定义特殊的 Property 类,否则,在 Option 中定义具体的域表示 Property。

struct Options {
  int fur_layer_count; // Good
  int fur_layer_count_property; // Bad! Property unnecessary
  
  struct ColorProperty {
    int a;
    int r;
    int g;
    int b;
  } // Bad! Prefer Color.
  ColorProperty color; 
}

参数解析:

例如:

func foo(param string)

foo(arg)

https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter

上下文名词辨析:

元数据辨析:

展开阅读全文

页面更新:2024-05-24

标签:同义词   动词   程序员   函数   头痛   名词   对象   常见   名字   模式   指南   方法   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top