摘要
这篇文章主要介绍了阿里巴巴Java开发的编码规范实践解析,重点聚焦于系统设计规范。文中强调了存储方案和底层数据结构设计的重要性,指出其需要经过严格评审并形成文档。同时,详细阐述了设计与评审流程,包括设计方案初稿、建模设计文档等阶段,并明确了各阶段的责任人。此外,还列举了多个强制或推荐的系统设计规范,如使用用例图、状态图、时序图等来表达复杂需求和关系,以及在设计阶段遵循单一原则、依赖倒置原则等,旨在提高系统的可扩展性、可维护性和稳定性。
1. 【强制】存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档。
说明:有缺陷的底层数据结构容易导致系统风险上升,可扩展性下降,重构成本也会因历史数据迁移和系统平滑过渡而陡然增加,所以,存储方案和数据结构需要认真地进行设计和评审,生产环境提交执行后,需要进行double check。
正例:评审内容包括存储介质选型、表结构设计能否满足技术方案、存取性能和存储空间能否满足业务发展、表或字段之间的辩证关系、字段名称、字段类型、索引等;数据结构变更(如在原有表中新增字段)也需要在评审通过后上线。
1.1. ✅ 为什么这条规范是“强制”
底层数据结构 = 系统地基,其一旦设计有缺陷,会造成:
- ❌ 业务无法扩展(如字段类型限制、字段缺失)
- ❌ 数据一致性问题(字段语义不明、冗余冲突)
- ❌ 性能瓶颈(缺少索引、数据冗余、JOIN 复杂)
- ❌ 重构成本高(历史数据迁移、接口兼容)
- ❌ 影响稳定性(死锁、慢 SQL、磁盘爆)
1.2. ✅ 设计与评审流程(阿里风格)
阶段 | 说明 | 责任人 |
1️⃣ 设计方案初稿 | 说明存储目的、使用场景、性能要求 | 开发/架构师 |
2️⃣ 建模设计文档 | 绘制 ER 图、字段含义、约束说明 | DBA/开发 |
3️⃣ 评审会议 | 从技术、业务、DBA 安全、数据治理角度评审 | 多方参与 |
4️⃣ 存储评审记录沉淀 | 存储方案、字段设计、表结构截图、SQL DDL | 固化到 Confluence/语雀/Notion |
5️⃣ 提交生产前二次确认 | 与 DBA double-check,避免漏审 | 开发 + DBA |
1.3. ✅ 评审内容 Checklist(推荐模板)
项目 | 检查内容 | 说明 |
存储介质 | MySQL、Redis、Mongo、ElasticSearch 等是否合适 | 选型必须合理 |
表结构设计 | 是否满足数据模型完整性,字段含义是否清晰 | 主键、唯一约束 |
字段类型选择 | 是否存在 varchar(255) 滥用、json 字段可解析性 | 禁用 magic 字段 |
字段命名规范 | 是否命名清晰、无拼音、无多义性 | 推荐采用驼峰/下划线统一规范 |
主键策略 | 是否使用雪花 ID、自增主键、联合主键是否合理 | 确保唯一性与查询效率 |
索引设计 | 查询字段是否有合适索引、是否避免冗余索引 | 联合索引顺序是否合理 |
表之间关系 | 是否存在重复字段、数据冗余,是否有依赖风险 | 冗余字段必须有合理理由 |
预估数据量 | 单表数据量是否超过 1千万,是否考虑分库分表 | 否则可能出现热点写入 |
可扩展性 | 新增字段、修改字段是否平滑、可兼容 | 老系统兼容性必须考虑 |
1.4. ✅ 变更操作的规范化流程
即便是新增字段、修改字段类型、增加索引,也必须:
- 提交 DDL 审批 → 通常通过 DBA 系统(如阿里内的 DMS)执行审批流程
- 压测验证性能变化 → 对比加索引前后 SQL 性能差异
- 灰度执行变更 → 小流量先试运行观察是否有风险
- 记录到系统字典或元数据平台 → 如阿里 DataMap、字节 DataLeap 等平台
- 文档沉淀 → 必须将变更记录同步到团队文档平台
1.5. ✅ 文档沉淀示例模板(可用语雀/Confluence)
1.5.1. 存储结构设计评审文档:用户支付流水表 pay_transaction
1.5.1.1. 背景说明
当前系统对支付记录存储性能不足,需重新建表支持百万级交易并发写入。
1.5.1.2. 表结构设计
- 表名:
pay_transaction
- 主键:
id
(雪花算法) - 业务主键:
transaction_id
(唯一索引) - 字段列表:
字段名 | 类型 | 含义 | 索引 |
id | bigint | 主键 | 主键 |
user_id | bigint | 用户ID | 是 |
amount | decimal(18,2) | 支付金额 | 否 |
status | varchar(16) | 支付状态 | 否 |
pay_time | datetime | 支付时间 | 否 |
1.5.1.3. 索引设计
- 唯一索引:transaction_id
- 普通索引:user_id、pay_time
1.5.1.4. 预估数据量
- 每日 500 万笔,月数据量约 1.5 亿,需归档策略
1.5.1.5. 变更风险评估
- 数据写入高并发,需配合数据库写入限速、按用户 ID 分表
1.5.1.6. 评审通过人员
- 业务负责人:xxx
- DBA:xxx
- 架构师:xxx
1.6. ✅ Double Check 操作建议
- 所有 SQL 上生产前,必须由 DBA 二次确认是否安全、是否会锁表
- 使用专门的 SQL 审批平台(如阿里 DMS)强制执行自动审核和人工确认
- 操作前强制备份或启用闪回机制,保障可回滚
1.7. ✅ 总结:这条规范背后的核心思想
核心价值 | 具体体现 |
降低系统风险 | 杜绝隐性缺陷数据结构上线 |
提高可扩展性 | 提前考虑字段兼容和变更成本 |
强化团队协作 | 建立规范化评审流程与文档沉淀 |
加强运维安全 | 有审计、有追踪、有审批 |
2. 【强制】在需求分析阶段,如果与系统交互的User超过一类并且相关的 UseCase 超过 5 个,使用用例图来表达更加清晰的结构化需求。
3. 【强制】如果某个业务对象的状态超过 3 个,使用状态图来表达并且明确状态变化的各个触发条件。
说明:状态图的核心是对象状态,首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,再明确触发状态转换的条件是什么。
正例:淘宝订单状态有已下单、待付款、已付款、待发货、已发货、已收货等。比如已下单与已收货这两种状态之间是不可能有直接转换关系的。
3.1. ✅ 为什么必须使用状态图?
- 避免混乱:状态超过 3 个时,如果仅靠代码或文档描述,容易遗漏边界情况,或出现非法状态跳转。
- 增强可维护性:未来开发者查看状态图能立刻理解业务流程,避免重复造轮子或误解状态定义。
- 辅助编码/建模:状态图可以指导状态机实现,例如用 Spring Statemachine、Squirrel、状态枚举类等。
3.2. 🧩 状态图设计要素
- 状态集合(State):列出该对象的所有可能状态
- 事件/条件(Trigger/Event):触发状态变化的外部操作或系统事件
- 状态转移(Transition):从哪个状态可转到哪个状态,并标明触发条件
- 非法路径禁止跳转:列明哪些状态间禁止直接跳转
3.3. 📘 示例:贷款审批单状态图设计
3.3.1. 状态定义
状态编码 | 状态名称 | 说明 |
INIT | 初始化 | 用户刚提交申请 |
AUDIT | 审核中 | 系统或人工审核 |
APPROVED | 审批通过 | 信审通过,进入放款准备 |
REJECTED | 审批拒绝 | 信审未通过 |
LOANING | 放款中 | 通知渠道放款 |
LOANED | 已放款 | 成功打款到账 |
CLOSED | 已关闭 | 终态,非正常关闭或完成关闭 |
3.3.2. 状态转移图(文字描述)
[INIT] --提交申请--> [AUDIT]
[AUDIT] --审核通过--> [APPROVED]
[AUDIT] --审核拒绝--> [REJECTED]
[APPROVED] --调用放款渠道--> [LOANING]
[LOANING] --放款成功--> [LOANED]
[任意状态] --用户取消/异常--> [CLOSED]
❌ 错误示例:INIT
不能直接跳转到 LOANED
,即“未经审核放款”是不合法流程。
3.4. ✅ 应用建议(阿里/大厂最佳实践)
场景 | 是否建议使用状态图 | 建议工具 |
支付/订单/贷款流转 | ✅ 强制使用 | ProcessOn 、PlantUML、draw.io |
简单任务(2 状态以内) | ❌ 可选 | - |
多人协同审批流 | ✅ 必须 | BPMN+状态图 |
状态绑定数据库字段 | ✅ 推荐配合 enum 管理 | Java Enum + 状态机框架 |
3.5. 🛠 补充:代码层的状态建模建议
public enum LoanStatus {INIT, AUDIT, APPROVED, REJECTED, LOANING, LOANED, CLOSED;public boolean canTransitionTo(LoanStatus next) {switch (this) {case INIT: return next == AUDIT;case AUDIT: return next == APPROVED || next == REJECTED;case APPROVED: return next == LOANING;case LOANING: return next == LOANED;default: return false;}}
}
4. 【强制】如果系统中某个功能的调用链路上的涉及对象超过3个,使用时序图来表达并且明确各调用环节的输入与输出。
说明:时序图反映了一系列对象间的交互与协作关系,清晰立体地反映系统的调用纵深链路。
5. 【强制】如果系统中模型类超过 5 个,且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系。
说明:类图像建筑领域的施工图,如果搭平房,可能不需要,但如果建造蚂蚁 Z 空间大楼,肯定需要详细的施工图。
6. 【强制】如果系统中超过 2 个对象之间存在协作关系,并需要表示复杂的处理流程,使用活动图来表示。
说明:活动图是流程图的扩展,增加了能够体现协作关系的对象泳道,支持表示并发等。
7. 【强制】系统设计时要准确识别出弱依赖,并针对性地设计降级和应急预案,保证核心系统正常可用。
说明:系统依赖的第三方服务被降级或屏蔽后,依然不会影响主干流程继续进行,仅影响信息展示、或消息通知等非关键功能,那么这些服务称为弱依赖。
正例:当系统弱依赖于多个外部服务时,如果下游服务耗时过长,则会严重影响当前调用者,必须采取相应降级措施,比如,当调用链路中某个下游服务调用的平均响应时间或错误率超过阈值时,系统自动进行降级或熔断操作,屏蔽弱依赖负面影响,保护当前系统主干功能可用。
反例:某个疫情相关的二维码出错:“服务器开了点小差,请稍后重试”,不可用时长持续很久,引起社会高度关注,原因可能为调用的外部依赖服务 RT 过高而导致系统假死,而在显示端没有做降级预案,只能直接抛错给用户。
8. 【推荐】系统架构设计时明确以下目标:
- 确定系统边界。 确定系统在技术层面上的做与不做。
- 确定系统内模块之间的关系。确定模块之间的依赖关系及模块的宏观输入与输出。
- 确定指导后续设计与演化的原则。 使后续的子系统或模块设计在一个既定的框架内和技术方向上继续演化。
- 确定非功能性需求。非功能性需求是指安全性、可用性、可扩展性等。
9. 【推荐】需求分析与系统设计在考虑主干功能的同时,需要充分评估异常流程与业务边界。
10. 【推荐】类在设计与实现时要符合单一原则。
说明:单一原则最易理解却是最难实现的一条规则,随着系统演进,很多时候,忘记了类设计的初衷。
11. 【推荐】谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现。
说明:不得已使用继承的话,必须符合里氏代换原则,此原则说父类能够出现的地方子类一定能够出现,比如,
钱交出来”,钱的子类美元、欧元、人民币等都可以出现。
继承(extends
)意味着强耦合,子类会绑定父类结构,继承的是“实现”,不是“行为契约”。组合(Composition)/聚合(Aggregation)则更灵活,运行时替换,解耦能力更强。
11.1. 🎓 对比继承和组合
对比项 | 继承(extends) | 组合(has-a) |
耦合度 | 高耦合 | 低耦合 |
灵活性 | 编译时静态绑定 | 运行时动态替换 |
可扩展性 | 继承层级深易崩 | 容易插拔新功能 |
可维护性 | 改父类影响子类 | 独立模块互不干扰 |
设计模式支持 | 模板方法等 | 策略、桥接、装饰等 |
11.2. ✅ 正确使用继承的前提:满足里氏代换原则(LSP)
子类对象必须能完全替代父类对象运行
11.2.1. ✅ 正例(满足 LSP):
public class Money {public BigDecimal getAmount() { ... }
}public class Dollar extends Money {// 合理拓展,不破坏父类行为
}
11.2.2. ❌ 反例(不满足 LSP):
public class Bird {public void fly() { ... }
}public class Ostrich extends Bird {@Overridepublic void fly() {throw new UnsupportedOperationException("不会飞");}
}
Bird
本意代表会飞的鸟,Ostrich
不满足这一契约,破坏里氏代换原则。
11.3. ✅ 推荐用组合重构的场景
11.3.1. ❌ 不推荐:
public class EmailOrderService extends OrderService {// 为了发邮件继承整个 OrderService,不合理
}
11.3.2. ✅ 推荐组合方式:
public class EmailOrderService {private final OrderService orderService;public void sendOrderWithEmail(Order order) {orderService.process(order);emailSender.send(order);}
}
EmailOrderService
拥有OrderService
,而不是继承它- 更清晰职责分离、可单测、可替换
11.4. 🏢 阿里/大厂的最佳实践
原则 | 做法 |
✅ 封装变化 | 用组合 + 策略/委托代替继承 |
✅ 保持职责单一 | 每个类组合具体职责组件 |
✅ 控制继承层级 | 不超过 2 层,超过就考虑组合 |
✅ 组合优先 | 实体类组合属性、服务类组合依赖,灵活解耦 |
11.5. 📌 总结:设计建议
建议 | 说明 |
🚫 不为“代码复用”而继承 | 复用实现应该通过组合或工具类完成 |
✅ 父类设计要抽象 | 继承应该是“is-a”关系,而非“use-a” |
✅ 多用接口 + 实现类 | 接口聚合行为,避免具体实现污染继承体系 |
✅ 必须继承时,校验里氏代换原则 | 保证子类不会破坏父类行为语义 |
12. 【推荐】系统设计阶段,根据依赖倒置原则,尽量依赖抽象类与接口,有利于扩展与维护。
说明:低层次模块依赖于高层次模块的抽象,方便系统间的解耦。
13. 【推荐】系统设计阶段,注意对扩展开放,对修改闭合。
说明:极端情况下,交付的代码是不可修改的,同一业务域内的需求变化,通过模块或类的扩展来实现。
一个软件实体(类、模块、函数)应该对扩展开放,对修改关闭。
- ✅ 对扩展开放:允许通过添加新代码(而非修改原代码)来增强系统功能。
- ❌ 对修改关闭:已有的代码是“交付件”,应避免修改,以免引发未知问题。
13.1. 📌 为什么在“极端情况下”原代码不可修改?
- 原始代码已上线,改动风险大。
- 多团队协作,原模块由其他组维护。
- 代码已沉淀为框架/SDK,版本不可频繁迭代。
- 有审计和合规要求,不允许轻易更改。
13.2. ✅ 实现 OCP 的常见手段
手段 | 说明 | 示例 |
接口 + 实现类 | 抽象行为,通过实现类扩展 |
|
策略模式 | 对行为进行抽象,每种策略独立实现 | 评分规则、风控策略 |
模板方法模式 | 抽象主流程 + 提供钩子扩展点 | 审批流中“审批通过”自定义逻辑 |
SPI 插件机制 | 服务发现方式动态加载实现类 | Dubbo / Java SPI / Spring Factories |
配置驱动 | 通过配置文件组合行为 | 新增字段类型 / 页面模块无需改代码 |
扩展点注册 | 自定义扩展注册机制 |
|
13.3. ✅ 示例:订单通知模块的开放封闭实现
❌ 反例:逻辑硬编码在类里
public class NotifyService {public void send(Order order) {if ("EMAIL".equals(order.getChannel())) {// 发送邮件} else if ("SMS".equals(order.getChannel())) {// 发送短信} else if ("PUSH".equals(order.getChannel())) {// 发送推送}}
}
- 新增渠道就要改原类,违反 OCP。
✅ 正例:基于接口 + 策略模式实现
public interface NotifyHandler {boolean supports(String channel);void send(Order order);
}
@Component
public class EmailNotifyHandler implements NotifyHandler {public boolean supports(String channel) { return "EMAIL".equals(channel); }public void send(Order order) { ... }
}
@Service
public class NotifyService {@Autowiredprivate List<NotifyHandler> handlers;public void send(Order order) {handlers.stream().filter(h -> h.supports(order.getChannel())).findFirst().orElseThrow().send(order);}
}
- 新增渠道只需实现新的
NotifyHandler
,无需修改原有类,完全符合 OCP。
13.4. 🏗️ 在阿里等大厂项目中的 OCP 实践
领域 | 变化点隔离方式 |
风控系统 | 策略引擎 + 动态脚本 + SPI |
支付系统 | 通道工厂 + 策略模式 |
工作流引擎 | 状态机 + 可插拔节点 |
规则系统 | 配置驱动 + Groovy 执行 |
营销系统 | 不同活动通过扩展类加载规则模块 |
中台架构 | 各业务线通过接口 + 插件组合复用基础模块 |
14. 【推荐】系统设计阶段,共性业务或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,在
系统中不出现重复代码的情况,即RY原则(Don't Repeat Yourself)。
说明: 随着代码的重复次数不断增加, 维护成本指数级上升。 随意复制和粘贴代码, 必然会导致代码的重复, 在维护代码时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化。
正例:一个类中有多个 public 方法,都需要进行数行相同的参数校验操作,这个时候请抽取:
private boolean checkParam(DTO dto) {...}
14.1. ✅ 为什么必须遵守 RY 原则?
- 可维护性提升:只需修改一处即可完成所有修改,避免遗漏
- 代码一致性增强:逻辑重复必然导致不一致,容易产生“伪 bug”
- 代码体积变小:冗余逻辑抽象后,文件变短,易于阅读
14.2. 🔧 典型的共性代码抽取方式(实战)
类型 | 示例说明 | 抽取方式 |
参数校验 | 多个 Controller 方法都验证手机号格式 | 封装为 |
接口日志打印 | 多个 Service 方法都打 request/response 日志 | 使用 AOP 实现统一切面拦截 |
异常处理逻辑 | try-catch 模板重复,处理 DB 异常 | 提炼为 |
Bean 拷贝逻辑 | DTO 与 Entity 频繁互转 | 使用 MapStruct 或 |
缓存设置逻辑 | 多个地方 Redis 缓存结构、key 组成逻辑重复 | 提取为 |
时间范围检查 | 多个方法中都校验开始时间小于结束时间 | 封装为 |
14.3. 📘 重复参数校验抽取
14.3.1. ❌ 不推荐写法(重复)
public void createUser(UserDTO dto) {
if (dto.getPhone() == null || !dto.getPhone().matches("^1[3-9]\\d{9}$")) {throw new IllegalArgumentException("手机号格式错误");
}...
}
public void updateUser(UserDTO dto) {
if (dto.getPhone() == null || !dto.getPhone().matches("^1[3-9]\\d{9}$")) {throw new IllegalArgumentException("手机号格式错误");
}...
}
14.3.2. ✅ 推荐写法(抽取公共方法)
public void createUser(UserDTO dto) {ParamChecker.checkPhone(dto.getPhone());...
}
public void updateUser(UserDTO dto) {ParamChecker.checkPhone(dto.getPhone());...
}public class ParamChecker {public static void checkPhone(String phone) {if (phone == null || !phone.matches("^1[3-9]\\d{9}$")) {throw new IllegalArgumentException("手机号格式错误");}}
}
14.4. 📦 阿里推荐的公共模块分层结构(组件化建议)
common/├── common-util // 通用工具类(校验、转换、加解密、UUID、Json)├── common-model // 通用模型(DTO、VO、Enum)├── common-exception // 通用异常体系(业务码、封装类)├── common-aop // 通用切面(日志、权限、幂等等)├── common-config // 通用配置(Spring Bean、拦截器、过滤器)└── common-constant // 通用常量(字符串、正则表达式、header key 等)
✅ 推荐:将这些模块统一打包成 jar
供多个子项目依赖(如基于 Maven 构建 common-lib
)
14.5. 🧠 实战建议(如何发现重复)
方法 | 描述 |
代码审查(Code Review) | 他人更容易发现你的重复逻辑 |
静态代码扫描(SonarQube) | 可识别出逻辑重复、代码克隆等问题 |
抽象思维训练 | 自问:“这个逻辑未来会改几次?是否会被别人再写一次?” |
模板提取(代码模板工具) | IntelliJ Live Template 可用于统一固定逻辑 |
14.6. ✅ RY 原则背后的思考路径
抽取复用 = 抽象 + 封装 + 解耦 + 模块边界控制
每当你看到:
- 多个类中拷贝粘贴一样的逻辑
- 同样的异常处理逻辑写了两遍
- 相同的表达式或字段匹配出现多处
就该提醒自己:“是不是违反了 RY 原则?” —— 并考虑是否值得提炼为公共组件或工具方法。
15. 【推荐】避免如下误解:敏捷开发=讲故事+编码+发布。
说明:敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上的必要设计和文档沉淀是需要的。
反例:某团队为了业务快速发展,敏捷成了产品经理催进度的借口,系统中均是勉强能运行但像面条一样的代码,可维护性和可扩展性极差,一年之后,不得不进行大规模重构,得不偿失。
16. 【参考】设计文档的作用是明确需求、理顺逻辑、后期维护, 次要目的用于指导编码。
说明:避免为了设计而设计,系统设计文档有助于后期的系统维护和重构,所以设计结果需要进行分类归档保存。
17. 【参考】可扩展性的本质是找到系统的变化点,并隔离变化点。
说明:世间众多设计模式其实就是一种设计模式即隔离变化点的模式。
正例:极致扩展性的标志,就是需求的新增,不会在原有代码交付物上进行任何形式的修改。
“可扩展性的本质是找到系统的变化点,并隔离变化点。”是架构设计中的核心原则,也是几乎所有设计模式、微服务、插件式架构、DDD、可插拔引擎等背后的思想根源。它帮助我们实现开放封闭原则(OCP):对扩展开放、对修改关闭。
17.1. ✅ 什么是“变化点”?
在系统中,变化点指的是:
类型 | 示例 |
业务逻辑经常变化 | 风控策略、营销活动、价格计算 |
接口协议经常变化 | 第三方支付、外部供应商 API |
配置项经常变化 | 参数化控制、限额、开关等 |
调用链可能变动 | 多渠道、不同方式的分发逻辑 |
扩展类目 | 新增商品类型、新业务模块 |
实现可能差异化 | 不同银行、不同租户,不同国家逻辑 |
17.2. 📘 如何通过“隔离变化点”实现极致扩展?
17.2.1. 🎯 场景:订单处理流程的扩展设计
❌ 不推荐做法(紧耦合)
public class OrderService {public void processOrder(Order order) {if ("PHYSICAL".equals(order.getType())) {// 实体商品处理} else if ("DIGITAL".equals(order.getType())) {// 虚拟商品处理} else if ("COUPON".equals(order.getType())) {// 优惠券订单}}
}
新增一种订单类型,就必须改动原有代码。
✅ 推荐做法(策略模式 + SPI 插件隔离)
public interface OrderHandler {boolean support(Order order);void handle(Order order);
}
@Component
public class PhysicalOrderHandler implements OrderHandler {public boolean support(Order order) {return "PHYSICAL".equals(order.getType());}public void handle(Order order) {// 处理实体商品}
}
@Service
public class OrderService {@Autowiredprivate List<OrderHandler> handlers;public void processOrder(Order order) {handlers.stream().filter(h -> h.support(order)).findFirst().orElseThrow(() -> new IllegalStateException("不支持的订单类型")).handle(order);}
}
➡️ 特点: 新增订单类型,只需实现一个新的 OrderHandler
,无需修改原有代码。
17.3. 💡 变化点隔离的常用设计模式
设计模式 | 用途 | 场景举例 |
策略模式 | 行为切换点(多种方式实现) | 支付方式、评分规则、风控策略 |
工厂模式 | 产品创建点(创建不同类型的类) | 消息对象生成、渠道工厂 |
责任链模式 | 多步骤链式执行 | 请求处理链、过滤器链 |
模板方法 | 不变主流程 + 可变子流程 | 统一执行逻辑下的自定义点 |
观察者模式 | 变化后通知多个下游 | 事件分发、埋点系统 |
装饰器模式 | 功能增强点 | 日志增强、权限增强 |
状态模式 | 对象状态变化的隔离 | 审批流、订单状态管理 |
17.4. 🏗️ 阿里大厂项目中如何隔离变化点(工程实践)
模块 | 常用做法 |
规则引擎 | 使用 Groovy / SpEL / 脚本引擎加载外部规则 |
风控策略 | 每一类策略使用 SPI 扩展点,动态加载 |
多渠道推送 | 使用策略 + 工厂模式管理短信/邮件/站内信等 |
多租户 | 使用插件化结构(如 Filter/Handler 插件包) |
产品配置 | 使用 JSON 配置动态驱动功能组合 |
工作流流程 | 状态机引擎(如 Spring StateMachine) |
17.5. ✅ 极致扩展性的目标是什么?
新增需求无需改动原有类/方法,仅需新增“可插拔组件”。这背后就是在设计阶段准确识别“未来会变”的点,并通过模块化、接口化、组件化将这些点隔离出来。
18. 【参考】设计的本质就是识别和表达系统难点。
说明:识别和表达完全是两回事,很多人错误地认为识别到系统难点在哪里,表达只是自然而然的事情,但是大家在设计评审中经常出现语焉不详,甚至是词不达意的情况。准确地表达系统难点需要具备如下能力:表达规则和表达工具的熟练性。抽象思维和总结能力的局限性。基础知识体系的完备性。深入浅出的生动表达力。
19. 【参考】代码即文档的观点是错误的,清晰的代码只是文档的某个片断,而不是全部。
说明:代码的深度调用,模块层面上的依赖关系网,业务场景逻辑,非功能性需求等问题要相应的文档来完整地呈现。
20. 【参考】在做无障碍产品设计时,需要考虑到:
- 所有可交互的控件元素必须能被 tab 键聚焦, 并且焦点顺序需符合自然操作逻辑。
- 用于登录校验和请求拦截的验证码均需提供图形验证以外的其它方式。
- 自定义的控件类型需明确交互方式。
正例:登录场景中,输入框的按钮都需要考虑 tab 键聚焦,符合自然逻辑的操作顺序如下,
"输入用户名,输入密码,输入验证码,点击登录",其中验证码实现语音验证方式。如有自定义标签实现的控件设置控件类型可使用 role 属性。
博文参考
《阿里巴巴java规范》