专治写代码不会命名

Java 类命名指南:让代码自己表达意图

日常编码中,代码命名是一门大学问。能快速看懂开源软件的代码结构和设计意图,也是开发者的一项必备能力。那它们有什么规律吗?

Java 项目尤其如此。它常采用较长的类名,让类自己表达主要职责;再配合强大的 IDE,开发者不必死记硬背,靠模糊匹配就能找到所需资源。

为了更好地理解命名套路,我借鉴了 Spring、Netty、libgdx、Guava、Logback 等流行 Java 开源项目,总结了 10 类常见类命名。它们大多数以后缀形式存在,也可以组合使用,用来表达多重语义。

图 1:Java 常见类命名全景图

mindmap root((Java 类命名)) 管理类 Bootstrap / Starter Processor Manager Holder Factory Provider Registrar Engine Service Task 传播类 Context Propagator 回调类 Handler / Callback / Trigger / Listener Aware 监控类 Metric Estimator Accumulator Tracker 内存管理类 Allocator Chunk Arena Pool 过滤检测类 Pipeline / Chain Filter Interceptor Evaluator Detector 结构类 Cache Buffer Composite Wrapper Option / Param / Attribute Tuple Aggregator Iterator Batch Limiter 设计模式 Strategy Adapter Action / Command Event Delegate Builder Template Proxy 解析类 Converter / Resolver Parser Customizer Formatter 网络类 Packet Protocol Encoder / Decoder / Codec Request / Response 其他 Util / Helper Mode / Type Invoker / Invocation Initializer Future / Promise Selector Reporter Constants Accessor Generator

图 2:Java 类命名分类思维导图


一、管理类命名

写代码,少不了对统一资源的管理。清晰的启动过程可以有效地组织代码;为了让程序跑起来,也少不了各种资源的注册、调度,以及公共集合资源的管理。

flowchart LR Start[Bootstrap / Starter] --> Engine[Engine / Service / Task] Engine --> Factory[Factory / Provider] Factory --> Manager[Manager / Holder / Registrar] Manager --> Processor[Processor]

图 3:管理类命名常见协作关系

Bootstrap,Starter

一般作为程序启动器使用,或者作为启动器的基类。通俗来说,可以认为是 main 函数的入口。

AbstractBootstrap
ServerBootstrap
MacosXApplicationStarter
DNSTaskStarter

Processor

某一类功能的处理器,用来表示某个处理过程,是一系列代码片段的集合。如果你不知道一些顺序类的代码怎么命名,就可以使用它,显得专业一些。

CompoundProcessor
BinaryComparisonProcessor
DefaultDefaultValueProcessor

Manager

对有生命状态的对象进行管理,通常作为某一类资源的管理入口。

AccountManager
DevicePolicyManager
TransactionManager

Holder

表示持有某个或者某类对象的引用,并可以对其进行统一管理。多见于不好回收的内存统一处理,或者一些全局集合容器的缓存。

QueryHolder
InstructionHolder
ViewHolder

Factory

毫无疑问,这是工厂模式的命名,耳熟能详。尤其在 Spring 中,多不胜数。

SessionFactory
ScriptEngineFactory
LiveCaptureFactory

Provider

Provider = Strategy + Factory Method。它更高级一些,把策略模式和方法工厂揉在了一起,让人用起来很顺手。Provider 一般是接口或者抽象类,以便能够完成子实现。

AccountFeatureProvider
ApplicationFeatureProviderImpl
CollatorProvider

Registrar

注册并管理一系列资源。

ImportServiceRegistrar
IKryoRegistrar
PipelineOptionsRegistrar

Engine

一般是核心模块,用来处理一类功能。引擎是个非常高级的名词,一般的类没有资格用它。

ScriptEngine
DataQLScriptEngine
C2DEngine

Service

某个服务。太简单,不忍举例。范围太广,不要滥用。

IntegratorServiceImpl
ISelectionService
PersistenceService

Task

某个任务。通常是个 Runnable。

WorkflowTask
FutureTask
ForkJoinTask

二、传播类命名

为了完成一些统计类或者全局类的功能,有些参数需要一传到底。传播类对象可以通过统一封装的方式进行传递,并在合适的地方进行拷贝或者更新。

sequenceDiagram participant Entry as 入口 participant Context as Context participant Propagator as Propagator participant Handler as Handler Entry->>Context: 写入全局变量 Context->>Propagator: 复制 / 添加 / 清除 / 重置 Propagator->>Handler: 传播上下文 Handler-->>Entry: 回调结果

图 4:Context 与 Propagator 的传播链路

Context

如果你的程序执行过程中,有一些变量需要从函数执行的入口开始,一直传到大量子函数执行完毕之后。这些变量或者集合如果以参数形式传递,将会让代码变得冗长无比。这时,你就可以把变量统一塞到 Context 里面,以单个对象的形式进行传递。

在 Java 中,由于 ThreadLocal 的存在,Context 甚至可以不用在参数之间进行传递。

AppContext
ServletContext
ApplicationContext

Propagator

传播、繁殖。用来将 Context 中传递的值进行复制、添加、清除、重置、检索、恢复等动作。通常,它会提供一个叫做 propagate 的方法,实现真正的变量管理。

TextMapPropagator
FilePropagator
TransactionPropagator

三、回调类命名

使用多核可以增加程序运行效率,但也不可避免会引入异步化。我们需要一定的手段获取异步任务执行的结果,并对任务执行过程中的关键点进行检查。回调类 API 可以通过监听、通知等形式,获取这些事件。

Handler,Callback,Trigger,Listener

Callback 通常是一个接口,用于响应某类消息,进行后续处理;Handler 通常表示持有真正消息处理逻辑的对象,它是有状态的;Trigger 触发器代表某类事件的处理,属于 Handler,通常不会出现在类的命名中;Listener 的应用更加局限,通常在观察者模式中用来表示特定含义。

ChannelHandler
SuccessCallback
CronTrigger
EventListener

Aware

Aware 就是感知的意思。一般以该单词结尾的类,都实现了 Aware 接口。拿 Spring 来说,Aware 的目的是让 Bean 获取 Spring 容器的服务。具体回调方法由子类实现,比如 ApplicationContextAware。它有点回调的意思。

ApplicationContextAware
ApplicationStartupAware
ApplicationEventPublisherAware

四、监控类命名

现在的程序都比较复杂,运行状态监控已经成为“居家必备之良品”。监控数据的收集往往需要侵入程序的边边角角,如何有效地与正常业务进行区分,是非常有必要的。

Metric

表示监控数据。不要用 Monitor 了,比较丑。

TimelineMetric
HistogramMetric
Metric

Estimator

估计、统计。用于计算某一类统计数值的计算器。

ConditionalDensityEstimator
FixedFrameRateEstimator
NestableLoadProfileEstimator

Accumulator

累加器的意思。用来缓存累加的中间计算结果,并提供读取通道。

AbstractAccumulator
StatsAccumulator
TopFrequencyAccumulator

Tracker

一般用于记录日志或者监控值,通常用于 APM 中。

VelocityTracker
RocketTracker
MediaTracker

五、内存管理类命名

如果你的应用用到了自定义的内存管理,那么下面这些名词是绕不开的。比如 Netty,就实现了自己的内存管理机制。

flowchart TB Allocator[Allocator 分配器] --> Chunk[Chunk 内存块] Chunk --> Arena[Arena 管理舞台] Arena --> Pool[Pool 池]

图 5:内存管理类命名关系

Allocator

与存储相关,通常表示内存分配器或者管理器。如果你的程序需要申请有规律的大块内存,Allocator 是不二选择。

AbstractByteBufAllocator
ArrayAllocator
RecyclingIntBlockAllocator

Chunk

表示一块内存。如果你想要对一类存储资源进行抽象,并统一管理,可以采用它。

EncryptedChunk
ChunkFactory
MultiChunk

Arena

英文是舞台、竞技场的意思。由于 Linux 把它用在内存管理上并发扬光大,它普遍用于各种存储资源的申请、释放与管理。为不同规格的存储 Chunk 提供舞台,好像也是非常形象的表示。

关键是,这个词很美,作为后缀让类名显得很漂亮。

BookingArena
StandaloneArena
PoolArena

Pool

表示池子。内存池、线程池、连接池,池池可用。

ConnectionPool
ObjectPool
MemoryPool

六、过滤检测类命名

程序收到的事件和信息非常多,有些是合法的,有些则需要过滤扔掉。根据不同的使用范围和功能性差别,过滤操作也有多种形式。你会在框架类代码中发现大量这样的名词。

flowchart LR A[请求 / 事件] --> B[Pipeline / Chain] B --> C[Filter] C --> D[Interceptor] D --> E[Evaluator] E --> F[Detector] F --> G[响应 / 处理结果]

图 6:过滤检测类命名常见链路

Pipeline,Chain

一般用在责任链模式中。Netty、Spring MVC、Tomcat 等都有大量应用。通过将某个处理过程加入到责任链的某个位置中,就可以接收前面处理过程的结果,强制添加或者改变某些功能。就像 Linux 的管道操作一样,最终构造出想要的结果。

Pipeline
ChildPipeline
DefaultResourceTransformerChain
FilterChain

Filter

过滤器,用来筛选某些满足条件的数据集,或者在满足某些条件的时候执行一部分逻辑。如果和责任链连接起来,则通常能够实现多级过滤。

FilenameFilter
AfterFirstEventTimeFilter
ScanFilter

Interceptor

拦截器,其实和 Filter 差不多。不过在 Tomcat 中,Interceptor 可以拿到 Controller 对象,但 Filter 不行。拦截器是被包裹在过滤器中的。

HttpRequestInterceptor

Evaluator

英文里是评估器的意思。可用于判断某些条件是否成立,一般内部方法 evaluate 会返回 boolean 类型。比如你传入一个非常复杂的对象或者字符串,进行正确与否的判断。

ScriptEvaluator
SubtractionExpressionEvaluator
StreamEvaluator

Detector

探测器。用来管理一系列探测性事件,并在发生的时候能够进行捕获和响应。比如 Android 的手势检测、温度检测等。

FileHandlerReloadingDetector
TransformGestureDetector
ScaleGestureDetector

七、结构类命名

除了基本的数据结构,如数组、链表、队列、栈等,其他更高一层的常见抽象类,能够大量减少大家的交流成本,并能封装常见的变化。

Cache

这个没啥好说的,就是缓存。大块的缓存。常见的缓存算法有 LRU、LFU、FIFO 等。

LoadingCache
EhCacheCache

Buffer

Buffer 是缓冲,不同于缓存,它一般用在数据写入阶段。

ByteBuffer
RingBuffer
DirectByteBuffer

Composite

将相似的组件进行组合,并以相同的接口或者功能进行暴露,使用者不知道这到底是一个组合体还是其他个体。

CompositeData
CompositeMap
ScrolledComposite

Wrapper

用来包装某个对象,做一些额外的处理,以便增加或者去掉某些功能。

IsoBufferWrapper
ResponseWrapper
MavenWrapperDownloader

Option,Param,Attribute

用来表示配置信息。说实话,它和 Properties 的区别并不大,但由于 Option 通常是一个类,所以功能可以扩展得更强大一些。它通常比 Config 的级别更小,关注的也是单个属性的值。Param 一般是作为参数存在,对象生成的速度要快一些。

SpecificationOption
SelectOption
AlarmParam
ModelParam

Tuple

元组的概念。由于 Java 中缺乏元组结构,我们通常会自定义这样的类。

Tuple2
Tuple3

Aggregator

聚合器,可以做一些聚合计算。比如分库分表中的 sum、max、min 等聚合函数的汇集。

BigDecimalMaxAggregator
PipelineAggregator
TotalAggregator

Iterator

迭代器。可以实现 Java 的迭代器接口,也可以有自己的迭代方式。在数据集很大的时候,需要进行深度遍历,迭代器可以说是必备的。使用迭代器还可以在迭代过程中安全地删除某些元素。

BreakIterator
StringCharacterIterator

Batch

某些可以批量执行的请求或者对象。

SavedObjectBatch
BatchRequest

Limiter

限流器,使用漏桶算法或者令牌桶来完成平滑的限流。

DefaultTimepointLimiter
RateLimiter
TimeBasedLimiter

八、常见设计模式命名

设计模式是名词的重灾区,这里只列出最常使用的几个。

Strategy

将抽象部分与它的实现部分分离,使它们都可以独立地变化。策略模式:相同接口,不同实现类,同一方法结果不同,实现策略不同。比如一个配置文件,是放在 XML 里,还是放在 JSON 文件里,都可以使用不同的 Provider 去命名。

RemoteAddressStrategy
StrategyRegistration
AppStrategy

Adapter

将一个类的接口转换为客户希望的另一个接口,Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

不过,相对于传统的适配器进行 API 转接,如果你的某个 Handler 里面方法特别多,可以使用 Adapter 实现一些默认的方法进行 0 适配。那么其他类使用的时候,只需要继承 Adapter,然后重写它想要重写的方法就可以了。这也是 Adapter 的常见用法。

ExtendedPropertiesAdapter
ArrayObjectAdapter
CardGridCursorAdapter

Action,Command

将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

用来表示一系列动作指令,用来实现命令模式,封装一系列动作或者功能。Action 一般用在 UI 操作上,后端框架可以无差别地使用。

在 DDD 的概念中,CQRS 中的 Command,即命令。

DeleteAction
BoardCommand

Event

表示一系列事件。一般的,在语义上,Action、Command 等来自于主动触发;Event 来自于被动触发。

ObservesProtectedEvent
KeyEvent

Delegate

代理或者委托模式。委托模式是将一件属于委托者做的事情,交给另外一个被委托者来处理。

LayoutlibDelegate
FragmentDelegate

Builder

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

构建者模式的标准命名。比如 StringBuilder。当然 StringBuffer 是个另类。这也说明了,规则是人定的,人也可以破坏。

JsonBuilder
RequestBuilder

Template

模板方法类的命名。定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

JDBCTemplate

Proxy

代理模式。为其他对象提供一种代理以控制对这个对象的访问。

ProxyFactory
SlowQueryProxy

九、解析类命名

写代码要涉及大量的字符串解析、日期解析、对象转换等。根据语义和使用场合的区别,它们也分为多种。

Converter,Resolver

转换和解析。一般用于不同对象之间的格式转换,把一类对象转换成另一类。注意它们语义上的区别:一般特别复杂的转换,或者有加载过程的需求,可以使用 Resolver。

DataSetToListConverter
LayoutCommandLineConverter
InitRefResolver
MustacheViewResolver

Parser

用来表示非常复杂的解析器,比如解析 DSL。

SQLParser
JSONParser

Customizer

用来表示对某个对象进行特别的配置。由于这些配置过程特别复杂,值得单独提取出来进行自定义设置。

ContextCustomizer
DeviceFieldCustomizer

Formatter

格式化类。主要用于字符串、数字或者日期的格式化处理工作。

DateFormatter
StringFormatter

十、网络类命名

网络编程的同学,永远绕不过去这几个名词。

Packet

通常用于网络编程中的数据包。

DhcpPacket
PacketBuffer

Protocol

同样用于网络编程中,用来表示某个协议。

RedisProtocol
HttpProtocol

Encoder、Decoder、Codec

编码解码器。

RedisEncoder
RedisDecoder
RedisCodec

Request,Response

一般用于网络请求的进和出。如果你用在非网络请求的方法上,会显得很怪异。


十一、CRUD 命名

这个就有意思多了。统一的 Controller、Service、Repository,没什么好说的。但你一旦用了 DDD,那就得按照 DDD 那一套的命名来。

由于 DDD 不属于通用编程范畴,它的名词就不多做介绍了。


十二、其他常见命名

Util,Helper

都表示工具类。Util 一般是无状态的,Helper 往往需要创建实例才能使用。但是一般没有使用 Tool 作为后缀的。

HttpUtil
TestKeyFieldHelper
CreationHelper

Mode,Type

看到 Mode 这个后缀,就能猜到这个类大概率是枚举。它通常把常见的可能性都列到枚举类里面,其他地方就可以引用这个 Mode。

OperationMode
BridgeMode
ActionType

Invoker,Invocation

Invoker 是一类接口,通常会以反射或者触发的方式,执行一些具体的业务逻辑。通过抽象出 invoke 方法,可以在 invoke 执行之前对入参进行记录或者处理;在 invoke 执行之后对结果和异常进行处理,是 AOP 中常见的操作方式。

MethodInvoker
Invoker
ConstructorInvocation

Initializer

如果你的应用程序需要经过大量的初始化操作才能启动,那就需要把它独立出来,专门处理初始化动作。

MultiBackgroundInitialize
ApplicationContextInitializer

Future,Promise

它们都是用在多线程之间的,进行数据传递。

Future 相当于一个占位符,代表一个操作将来的结果。一般通过 get 可以直接阻塞得到结果,或者让它异步执行,然后通过 callback 回调结果。

但如果回调中嵌入了回调呢?如果层次很深,就是回调地狱。Java 中的 CompletableFuture 其实就是 Promise,用来解决回调地狱问题。Promise 是为了让代码变得优美而存在的。

Selector

根据一系列条件,获得相应的同类资源。它比较像 Factory,但只处理单项资源。

X509CertSelector
NodeSelector

Reporter

用来汇报某些执行结果。

ExtentHtmlReporter
MetricReporter

Constants

一般用于常量列表。

Accessor

封装了一系列 get 和 set 方法的类。像 Lombok 就有 Accessors 注解,生成这些方法。但 Accessor 类一般是要通过计算来完成 get 和 set,而不是直接操作变量。这适合比较复杂的对象存取服务。

ComponentAccessor
StompHeaderAccessor

Generator

生成器,一般用于生成代码、生成 ID 等。

CodeGenerator
CipherKeyGenerator

命名速查表

后缀 / 名词 核心含义 常见场景
Bootstrap / Starter 启动器、入口 程序启动、服务启动
Processor 处理器 顺序处理、功能集合
Manager 管理器 资源、事务、设备管理
Holder 持有者 对象引用、全局缓存
Factory 工厂 对象创建
Provider 提供者 策略 + 工厂方法
Registrar 注册器 资源注册与管理
Engine 引擎 核心功能模块
Service 服务 业务服务
Task 任务 Runnable、异步任务
Context 上下文 全局变量传递
Propagator 传播器 上下文复制、清除、恢复
Handler / Callback / Trigger / Listener 回调与监听 异步结果、事件响应
Aware 感知 Spring Bean 获取容器服务
Metric 指标 监控数据
Estimator 估算器 统计计算
Accumulator 累加器 中间结果缓存
Tracker 追踪器 日志、APM
Allocator 分配器 内存分配
Chunk 内存块 存储资源抽象
Arena 舞台、竞技场 内存管理
Pool 池 内存池、线程池、连接池
Pipeline / Chain 管道、责任链 多级处理
Filter 过滤器 数据筛选
Interceptor 拦截器 请求拦截
Evaluator 评估器 条件判断
Detector 探测器 事件检测
Cache 缓存 大块缓存
Buffer 缓冲 数据写入
Composite 组合 统一接口组合
Wrapper 包装器 功能增强或剥离
Option / Param / Attribute 配置项 参数、属性
Tuple 元组 多值返回
Aggregator 聚合器 聚合计算
Iterator 迭代器 遍历数据集
Batch 批量 批量请求或对象
Limiter 限流器 漏桶、令牌桶
Strategy 策略 不同实现切换
Adapter 适配器 接口转换
Action / Command 动作、命令 命令模式、CQRS
Event 事件 被动触发
Delegate 委托 代理、委托
Builder 构建器 复杂对象构建
Template 模板 模板方法
Proxy 代理 访问控制
Converter / Resolver 转换器、解析器 对象转换、加载解析
Parser 解析器 DSL、SQL、JSON
Customizer 定制器 复杂配置
Formatter 格式化器 字符串、日期、数字
Packet 数据包 网络传输
Protocol 协议 网络协议
Encoder / Decoder / Codec 编解码器 网络编解码
Request / Response 请求与响应 网络进出
Util / Helper 工具类 通用辅助
Mode / Type 模式、类型 枚举
Invoker / Invocation 调用器、调用 AOP、反射
Initializer 初始化器 启动初始化
Future / Promise 未来结果、承诺 异步编程
Selector 选择器 按条件选资源
Reporter 汇报器 结果汇报
Constants 常量 常量列表
Accessor 访问器 get/set 封装
Generator 生成器 代码、ID 生成

总结

代码要带感,命名也风骚。名字起得好,代码会看起来很爽,大家也都喜欢。

上面这些命名,高频率存在于各种框架中。你要是搞懂了这些名词,阅读大部分源代码可以说是一点障碍都没有了。在同一个场景下,优先使用这些名词,已经是大家心照不宣的规范。

有很多名词来自设计模式,但又在特定场合使用了比较特殊的单词,比如 Provider。大家仔细感受下其中的区别就可以了。

命名是编码中非常重要的一环。希望你能找到其中的规律,让代码功能上强大,读起来也赏心悦目。

posted @ 2023-03-07 11:54  元宇宙的元  阅读(142)  评论(0)    收藏  举报