上篇文章聊了变量命名的事,有读者私信我说:“名字是起好了,但方法写得太长,名字再优雅也救不回来。”
这话我太有共鸣了。
我之前接了一个离职同事的活儿,他留下的代码里,有一个 process 方法,整整一百二十行。我当时年轻气盛,心想:重构还不简单,按功能拆成几个小方法就行了。
然后我就动手了。
拆完之后一运行,报错。再拆,再报错。折腾了一下午,最后默默回滚了代码,在旁边加了一行注释:// 此方法逻辑复杂,谨慎修改。
现在回头看,问题不在于“拆分”这个动作本身,而在于我没搞懂那段代码到底做了哪几件事。老张写的时候脑子里有一个业务流程图,但我看到的只是一堵代码墙。我要拆的不是代码,是业务逻辑。但当时的我,连那段代码里有几个业务步骤都没数清楚。
后来我开始用AI帮忙做这件事。不是让它直接帮我重构——那太危险了。而是让它先帮我把代码里的业务步骤标出来。就像吃一条鱼,先把主刺挑出来,肉自然就分开了。
今天就聊聊这个:怎么让AI帮你识别长方法里的逻辑边界,把一堵墙变成几个抽屉。
一、场景痛点:这个方法不长,但读起来像在吃压缩饼干
先澄清一个误区:长方法的“长”,不一定是行数多。有些方法只有40行,但你读完之后脑子里一团浆糊;有些方法80行,但逻辑清晰,读下来很顺畅。
问题不在于行数,而在于一个方法里塞进了太多不同层级的事情。
比如下面这个采购订单创建方法:
@Service
public class PurchaseOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private SupplierMapper supplierMapper;
@Autowired
private AuditLogMapper auditLogMapper;
@Transactional
public void createOrder(CreateOrderRequest request) {
// 1. 校验供应商是否存在且状态正常
Supplier supplier = supplierMapper.selectById(request.getSupplierId());
if (supplier == null) {
throw new BusinessException("供应商不存在");
}
if (supplier.getStatus() != 1) {
throw new BusinessException("供应商状态异常,无法下单");
}
// 2. 校验物料库存是否充足
List<OrderItem> items = request.getItems();
for (OrderItem item : items) {
Inventory inventory = inventoryMapper.selectByMaterialId(item.getMaterialId());
if (inventory == null) {
throw new BusinessException("物料" + item.getMaterialId() + "不存在库存记录");
}
if (inventory.getAvailableQty() < item.getQuantity()) {
throw new BusinessException("物料" + item.getMaterialId() + "库存不足");
}
}
// 3. 计算订单总金额
BigDecimal totalAmount = BigDecimal.ZERO;
for (OrderItem item : items) {
BigDecimal itemTotal = item.getUnitPrice().multiply(new BigDecimal(item.getQuantity()));
item.setTotalPrice(itemTotal);
totalAmount = totalAmount.add(itemTotal);
}
// 4. 生成订单号并保存订单
String orderNo = "PO" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
Order order = new Order();
order.setOrderNo(orderNo);
order.setSupplierId(request.getSupplierId());
order.setTotalAmount(totalAmount);
order.setStatus(1);
order.setCreateTime(new Date());
orderMapper.insert(order);
// 5. 扣减库存
for (OrderItem item : items) {
inventoryMapper.deductQty(item.getMaterialId(), item.getQuantity());
}
// 6. 记录审计日志
AuditLog log = new AuditLog();
log.setOperationType("CREATE_ORDER");
log.setOperator(request.getOperator());
log.setContent("创建采购订单:" + orderNo);
log.setCreateTime(new Date());
auditLogMapper.insert(log);
}
}
这段代码的问题在哪?
它不丑。变量命名没问题,注释也写了,逻辑也清楚。但问题是:如果你想理解“创建订单”这件事到底做了哪些步骤,你必须从头读到尾,一个注释一个注释地看。
而且这个方法里混了三个层级的事情:
- 业务校验:供应商校验、库存校验
- 业务计算:金额计算、订单号生成
- 数据持久化:订单保存、库存扣减、日志记录
另外还有一个更隐蔽的性能隐患:库存校验的循环里,每次都去数据库查一次 selectByMaterialId。如果一个订单有50个物料,就是50次数据库查询。在低并发时还能混过去,一旦业务量上来,数据库连接池直接打满。
当一个新人想问“库存校验的逻辑在哪”时,他得在这个60行的方法里找。当产品经理说“订单金额计算规则要改”时,你得小心地在中间那坨循环里改。
二、AI介入:不是帮你写代码,是帮你理清“业务步骤”
把上面这段代码扔给AI,我们需要的不只是机械地按注释拆分,而是按照业务语义来拆分,让拆分后的结构本身就能当作文档看。
关键在于你向AI描述这段代码时的引导:
提示词示例:
我有一段采购订单创建的代码,整个方法包含了从校验到落地的完整流程。请帮我:
- 先列出这个方法里包含了哪几个“业务步骤”,用业务语言描述
- 按业务步骤拆分成多个方法,主方法变成步骤清单
- 检查代码中是否存在性能隐患或不规范写法(如循环查库、N+1问题等),一并给出优化建议
- 不要改变任何业务逻辑,只做结构重组
AI的分析输出(模拟)
AI首先会列出业务步骤,然后指出循环查库的问题:
业务步骤识别:
- 校验供应商下单资格:确认供应商存在且状态正常
- 校验物料库存充足性:确认所有订购物料库存足够
- 计算订单明细金额:计算每个物料项的总价及订单总金额
- 生成并保存采购订单:生成订单号、组装订单对象、入库
- 扣减物料库存:订单生效后扣减可用库存
- 记录操作审计日志:记录谁在什么时候创建了什么订单
发现的问题及优化建议:
- 循环内查库:
validateInventorySufficiency 中在循环内调用 selectByMaterialId,存在 N+1 查询问题。建议改为批量查询:先收集所有 materialId,一次性查询所有库存记录,再在内存中进行匹配校验。
三、优化后代码:主方法变成“业务目录”
@Service
public class PurchaseOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private SupplierMapper supplierMapper;
@Autowired
private AuditLogMapper auditLogMapper;
@Transactional
public void createOrder(CreateOrderRequest request) {
validateSupplierEligibility(request.getSupplierId());
validateInventorySufficiency(request.getItems());
calculateOrderItemTotals(request.getItems());
Order savedOrder = savePurchaseOrder(request);
deductInventory(request.getItems());
logOrderCreation(savedOrder.getOrderNo(), request.getOperator());
}
private void validateSupplierEligibility(Long supplierId) {
Supplier supplier = supplierMapper.selectById(supplierId);
if (supplier == null) {
throw new BusinessException("供应商不存在");
}
if (supplier.getStatus() != 1) {
throw new BusinessException("供应商状态异常,无法下单");
}
}
private void validateInventorySufficiency(List<OrderItem> items) {
// 批量查询:一次性把所有物料ID查出来,避免循环查库
List<Long> materialIds = items.stream()
.map(OrderItem::getMaterialId)
.collect(Collectors.toList());
List<Inventory> inventories = inventoryMapper.selectByMaterialIds(materialIds);
// 转为Map方便快速匹配
Map<Long, Inventory> inventoryMap = inventories.stream()
.collect(Collectors.toMap(Inventory::getMaterialId, inv -> inv));
for (OrderItem item : items) {
Inventory inventory = inventoryMap.get(item.getMaterialId());
if (inventory == null) {
throw new BusinessException("物料" + item.getMaterialId() + "不存在库存记录");
}
if (inventory.getAvailableQty() < item.getQuantity()) {
throw new BusinessException("物料" + item.getMaterialId() + "库存不足");
}
}
}
private void calculateOrderItemTotals(List<OrderItem> items) {
for (OrderItem item : items) {
BigDecimal itemTotal = item.getUnitPrice()
.multiply(new BigDecimal(item.getQuantity()));
item.setTotalPrice(itemTotal);
}
}
private Order savePurchaseOrder(CreateOrderRequest request) {
String orderNo = "PO" + System.currentTimeMillis() + RandomUtils.nextInt(1000, 9999);
BigDecimal totalAmount = request.getItems().stream()
.map(OrderItem::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Order order = new Order();
order.setOrderNo(orderNo);
order.setSupplierId(request.getSupplierId());
order.setTotalAmount(totalAmount);
order.setStatus(1);
order.setCreateTime(new Date());
orderMapper.insert(order);
return order;
}
private void deductInventory(List<OrderItem> items) {
for (OrderItem item : items) {
inventoryMapper.deductQty(item.getMaterialId(), item.getQuantity());
}
}
private void logOrderCreation(String orderNo, String operator) {
AuditLog log = new AuditLog();
log.setOperationType("CREATE_ORDER");
log.setOperator(operator);
log.setContent("创建采购订单:" + orderNo);
log.setCreateTime(new Date());
auditLogMapper.insert(log);
}
}
关键改动说明:
- 主方法变成了6行:每一行对应一个业务步骤,读起来像看目录。
- 库存校验改成了批量查询:无论订单有多少物料,只查一次数据库。
- 每个私有方法只做一件事:方法名就是业务动作的描述。
四、提升点分析:拆分带来的隐性收益
| 维度 |
拆分前 |
拆分后 |
| 主方法可读性 |
需要读60行才能理解完整流程 |
读6行就能知道业务由哪几步组成 |
| 业务步骤显性化 |
藏在注释里,容易被忽略 |
方法名本身就是业务步骤清单 |
| 单点修改范围 |
修改金额计算逻辑时,需要在60行里定位 |
直接定位到 calculateOrderItemTotals |
| 复用可能性 |
库存校验逻辑无法被其他方法复用 |
validateInventorySufficiency 可以被其他下单场景调用 |
| 单元测试 |
只能测试整个 createOrder,Mock成本高 |
可以单独测试每个私有方法的逻辑 |
| Code Review |
Reviewer需要理解全部60行 |
可以先看主方法的步骤清单,再深入具体步骤 |
| 性能优化 |
循环查库,N+1问题 |
批量查询,一次搞定 |
一个容易被忽略的价值:当主方法变成了“业务步骤清单”后,它天然成为了一份文档。新人问你“创建订单会校验什么”时,你直接让他点进 validateSupplierEligibility 和 validateInventorySufficiency 看就行。不用再补一句“哦对了,还有一个地方也做了校验……”
五、什么时候用AI做这件事效果最好
- 接手别人的代码时:把那个80行的
processOrder 丢给AI,让它帮你梳理出业务步骤,你就能快速理解这段代码到底做了什么。
- Code Review前自检:写完一个方法后,让AI判断“这个方法做了几件事”,如果AI列出超过3件,就该考虑拆分了。
- 加新功能之前:让AI帮你把现有方法按业务语义拆分好,再加新功能时就不会把方法越撑越大。
六、AI提示词模板(可直接复制)
我有一段Java业务方法,请帮我分析并按业务流程进行拆分。
要求:
1. 先用1-2句话概括这段代码的业务目的
2. 列出这个方法包含的几个“业务步骤”,用业务语言描述(不是技术动作)
3. 按业务步骤将代码拆分成多个私有方法,主方法保留为步骤调用的清单
4. 检查代码中是否存在性能隐患或不规范写法(如循环查库、N+1问题等),一并给出优化建议
5. 每个拆分出的方法名应体现该步骤的业务目的
6. 不要改变任何业务逻辑,只做结构重组
代码如下:
[在此粘贴你的代码]
长方法拆分这件事,说到底是把脑子里的业务流程图,翻译成代码里的方法调用栈。AI在这里的角色不是替你写代码,而是帮你把那堵“代码墙”里藏着的业务步骤,一条一条地标出来。
下次看到一段让你头疼的长方法,别急着骂前任,先把代码喂给AI,让它帮你把主刺挑出来。剩下的肉,自然就好拆了。
以上内容纯属个人折腾心得,不构成任何技术权威指导。重构这件事,本来就是仁者见仁智者见智——有人喜欢一个方法干到底,有人恨不得每三行拆一个函数。代码能跑、不出事故,怎么舒服怎么来。
如果你在重构Java长方法,或者处理复杂的MySQL数据操作时遇到瓶颈,不妨换个思路,让AI帮你理清脉络。毕竟,写出清晰、可维护的代码,本身就是一种重要的设计模式。