创建通用的共享组件导致了一系列问题,比如耦合、协调难度和复杂度增加。
当我看到一个个巨大的 common 包时,我开始痛恨 common
、base
、util
这些该死的包,还有它们目录下统一管理的 bean
。我们真的已经把它们用烂了,所以你应该重新审视一下你的项目代码。
所以,从这种意义上来说:复用与低耦合 ,本身存在一定的互斥关系。
公共代码往往缺乏抽象,又或者是追求过度的复用。
它真是个 util 吗?
哦,不,它是个恶魔,因为它是 util。
你会往 xxUtil 不加思索地扔入逻辑,正如你会往 common/bean 中扔入所有的 model,直次有一天,你拥有一个巨大无比的 base、common 代码。
大多数情况下,所有和业务相关的 Util 都存在一定的问题,如 CaptchaUtil,它要么应该划到自己的上下文中去,要么扔到诸如于 domain/shared 等共享上下文,而不是和其它 util 放到一起。
而诸如 FileUtil、DateUtil、RedisUtil、JdbcUtil 这些都可以说是基础设施相关的部分,它们可以划到 infrastructure/file 又或者是 infrastructure/date 目录下,而不是统一的管理这些 util。
如 StackOverflow 的相关问题所列,我们还有诸如 Coordinator、Builder、Writer、Reader、Handler、Container、Protocol、Target、Converter、Controller、View、Factory、Entity、Bucket 等名称。
含义更加丰富的名字启示如下:
XXX器[拟物化]:
Listener 监听器 | Adapter 适配器 | Filter 过滤器 | Iterator 迭代器 | Buffer 缓冲器 | Connector 连接器 |
---|---|---|---|---|---|
Decortor 装饰器 | Iterepter 解释器 | Interceptor 拦截器 | Reactor 反应器 | Configurator 配置器 | Wrapper 包装器 |
Proactor 主动器 | Monitor 监视器 | Controller 控制器 | Translator 转换器 | Acceptor 接收器 | Selector 选择器 |
Container 容器 | Manager 管理器 | Evictor 驱逐器 | Activator 激活器 | Mapper映射器 | Locator 定位器 |
Handler 处理器 | Assembler 汇编器 | Driver 驱动器 | Spliterator 分割器 | Builder 构建器 | Formatter 格式器 |
Scanner 扫描器 | Timer 定时器 | Converter 转化器 | Dispatcher 分配器 | Multicaster 广播器 | Transfer 传输器 |
Desriptor 描述器 | Encoder编码器 | Decoder 解码器 | Introspector 内省器 | Tokenizer 分词器 | Loader 加载器(ClassLoader) |
Logger 记录器 | Parser 解析器 | Resolver 分解器 | Incrementer 增加器 | Counter 计数器 | Collector 收集器 |
Initializer 初始化器 | Setter 设置器 | Getter 取值器 | Marshaller 编组器 | UnMarshaller 解组器 | Helper 帮助器 |
Accessor 访问器 | Visitor 访问器 | Reflector 反射器 | Embedder 嵌入器 | Finalizer 回收器 | Specifier 标识器 |
Supplier 供应器 | Processor 处理器 | Joiner 接合器 | Recorder 记录器 | Reducer 归集器 | Analyzer 分析器 |
Invoker 调用器 | Provider 供应器 | Renderer 渲染器 | Holder 持有器 | Closer 关闭器 | Operator 操作器 |
Appender 添加器 | Printer 打印器 | Tuplizer 元组器 | Caller 调用器 | Identifier 标识器 | Walker 漫步器 |
Brower 浏览器 | Server 服务器 | Aggregator 聚合器 | Binder 绑定器 | Validator 校验器 | Finder 查找器 |
Launcher 发射器/启动器 | Weaver 织入器 | Messenger 信差/消息器 | Extractor 提取器 | Sampler 取样器 | Profiler 优化器 |
Tracer 追踪器 | Estimator 预估器 | Generator 生成器 | Instrumenter 插装器 | Viewer 查看器 | Debugger 调试器 |
Analyser 分析器 | Inspector 检查器 | Linker 链接器 | Editor 编辑器 | Recognizer 识别器 | Decompiler 反编译器 |
Translator 解释器 | Lexer 词法分析器 | Tracker 追踪器 | Constructor 构造器 | Destructor 析构器 | Executor 执行器 |
Synchronizer 同步器 | Barrier 障碍器 | Allocator 分配器 | Bundler 打包器 | Applier 分发器 | Trigger 触发器 |
XXX者[拟人化]:
Consumer消费者 | Producer 生产者 | Observer 观察者 | Caller 调用者 | Supervisor 监管者 | Keeper 管理员(ZooKeeper) |
---|---|---|---|---|---|
Wokrer 工作者 |
器和者的一些名字可以互换。比如Builder 可以是构建器,也可以是构建者。名字选择很多,但是不要过度封装,用最简单的概念表现更多的含义。
试着干掉 Utils ,你将收获更多的类,笑~。
Utils / Helper 多数是恶魔,无法满足单一职责和开闭原则。好的 OO 设计,大部分的类只表示一个事物,及其所有属性和操作。
- 尽可能减少 Utils / Helper 类。好的 OO 设计,大部分的类只表示一个事物,及其所有属性和操作。
- 如果使用一个 Utils 用于操作类,如 IList,那么它应该划到类中。除非该类不存在于当前的应用中。
- Utils 中的方法应该是无状态的,比如没有 static 变量。
- 如果有大量的 Utils 方法,应该把划分到类中,以便快速找到它们。
过度设计
好的设计是尽可能简单的,它最易于适应新的设计,并能跟随业务的变化而变化。
- 开发人员:『这个功能是给未来准备的』
- Tech Lead:未来是多久?一个月后?半年后?
- 开发人员:……
图片出自:https://stackoverflow.com/questions/1001120/what-is-over-engineering-as-applied-to-software
与之相对应的设计不足,则是因为经验的缘故。
重新定义:消除二义性
当我们谈论 service 的时候,我们谈论的是同一个 service 吗?当我们谈论 model 的时候,我们谈论的是同一种 model 吗?
若对于一个文法的某一句子存在两棵不同的语法树,则该文法是二义性文法。
如果有多种不同类型的类,都被放置在 model 包下。那么,你应该消除 model 这个包,改为更表意的名称,如 Entity、Request、Response 等等。同理,一旦你们展开对某个名称的讨论时,是时候好好考虑其中的二义性。
最后,你还需要有一个相关领域的名词表。避免产生二异性的词语。
类进行内聚
参考下文中的模型重构。
划分技术部分
如 Spring 框架的源码:
└── springframework
├── cache
│ ├── annotation
│ ├── concurrent
│ ├── config
│ ├── interceptor
│ └── support
├── context
│ ├── annotation
│ ├── config
│ ├── event
│ ├── expression
│ ├── i18n
│ ├── index
│ ├── support
│ └── weaving
划分业务部分
业务模块中的技术部分。
职责少 => 平级
└── orm
├── context
├── support
业务代码多 => 再按业务拆分
infrastructure
└── repository
├── context
│ ├── blog
│ ├── advert
│ └── pages
├── kafka
下一节:模块/组件是软件的部署单元,是整个软件系统在部署过程中可以独立部署的最小实体。 —— 《架构整洁之道》