找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4955

积分

0

好友

689

主题
发表于 前天 05:32 | 查看: 23| 回复: 0

上篇文章聊了变量命名的事,有读者私信我说:“名字是起好了,但方法写得太长,名字再优雅也救不回来。”

这话我太有共鸣了。

我之前接了一个离职同事的活儿,他留下的代码里,有一个 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描述这段代码时的引导:

提示词示例:

我有一段采购订单创建的代码,整个方法包含了从校验到落地的完整流程。请帮我:

  1. 先列出这个方法里包含了哪几个“业务步骤”,用业务语言描述
  2. 按业务步骤拆分成多个方法,主方法变成步骤清单
  3. 检查代码中是否存在性能隐患或不规范写法(如循环查库、N+1问题等),一并给出优化建议
  4. 不要改变任何业务逻辑,只做结构重组

AI的分析输出(模拟)

AI首先会列出业务步骤,然后指出循环查库的问题:

业务步骤识别:

  1. 校验供应商下单资格:确认供应商存在且状态正常
  2. 校验物料库存充足性:确认所有订购物料库存足够
  3. 计算订单明细金额:计算每个物料项的总价及订单总金额
  4. 生成并保存采购订单:生成订单号、组装订单对象、入库
  5. 扣减物料库存:订单生效后扣减可用库存
  6. 记录操作审计日志:记录谁在什么时候创建了什么订单

发现的问题及优化建议:

  • 循环内查库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);
    }
}

关键改动说明:

  1. 主方法变成了6行:每一行对应一个业务步骤,读起来像看目录。
  2. 库存校验改成了批量查询:无论订单有多少物料,只查一次数据库。
  3. 每个私有方法只做一件事:方法名就是业务动作的描述。

四、提升点分析:拆分带来的隐性收益

维度 拆分前 拆分后
主方法可读性 需要读60行才能理解完整流程 读6行就能知道业务由哪几步组成
业务步骤显性化 藏在注释里,容易被忽略 方法名本身就是业务步骤清单
单点修改范围 修改金额计算逻辑时,需要在60行里定位 直接定位到 calculateOrderItemTotals
复用可能性 库存校验逻辑无法被其他方法复用 validateInventorySufficiency 可以被其他下单场景调用
单元测试 只能测试整个 createOrder,Mock成本高 可以单独测试每个私有方法的逻辑
Code Review Reviewer需要理解全部60行 可以先看主方法的步骤清单,再深入具体步骤
性能优化 循环查库,N+1问题 批量查询,一次搞定

一个容易被忽略的价值:当主方法变成了“业务步骤清单”后,它天然成为了一份文档。新人问你“创建订单会校验什么”时,你直接让他点进 validateSupplierEligibilityvalidateInventorySufficiency 看就行。不用再补一句“哦对了,还有一个地方也做了校验……”

五、什么时候用AI做这件事效果最好

  1. 接手别人的代码时:把那个80行的 processOrder 丢给AI,让它帮你梳理出业务步骤,你就能快速理解这段代码到底做了什么。
  2. Code Review前自检:写完一个方法后,让AI判断“这个方法做了几件事”,如果AI列出超过3件,就该考虑拆分了。
  3. 加新功能之前:让AI帮你把现有方法按业务语义拆分好,再加新功能时就不会把方法越撑越大。

六、AI提示词模板(可直接复制)

我有一段Java业务方法,请帮我分析并按业务流程进行拆分。

要求:
1. 先用1-2句话概括这段代码的业务目的
2. 列出这个方法包含的几个“业务步骤”,用业务语言描述(不是技术动作)
3. 按业务步骤将代码拆分成多个私有方法,主方法保留为步骤调用的清单
4. 检查代码中是否存在性能隐患或不规范写法(如循环查库、N+1问题等),一并给出优化建议
5. 每个拆分出的方法名应体现该步骤的业务目的
6. 不要改变任何业务逻辑,只做结构重组

代码如下:
[在此粘贴你的代码]

长方法拆分这件事,说到底是把脑子里的业务流程图,翻译成代码里的方法调用栈。AI在这里的角色不是替你写代码,而是帮你把那堵“代码墙”里藏着的业务步骤,一条一条地标出来。

下次看到一段让你头疼的长方法,别急着骂前任,先把代码喂给AI,让它帮你把主刺挑出来。剩下的肉,自然就好拆了。

以上内容纯属个人折腾心得,不构成任何技术权威指导。重构这件事,本来就是仁者见仁智者见智——有人喜欢一个方法干到底,有人恨不得每三行拆一个函数。代码能跑、不出事故,怎么舒服怎么来。

如果你在重构Java长方法,或者处理复杂的MySQL数据操作时遇到瓶颈,不妨换个思路,让AI帮你理清脉络。毕竟,写出清晰、可维护的代码,本身就是一种重要的设计模式




上一篇:半导体介质刻蚀中氟碳气体的核心作用:平衡蚀刻与钝化的化学工具箱
下一篇:vLLM Paged Attention代码深度解析:如何优化KV Cache内存管理提升推理吞吐
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-16 17:01 , Processed in 0.646486 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表