读了1000行老代码,我发现最好的设计是“简单”

📅 2026/7/21 19:50:44 👁️ 阅读次数 📝 编程学习
读了1000行老代码,我发现最好的设计是“简单”

读了1000行老代码,我发现最好的设计是“简单”

摘要:

运维多年,第一次用AI深读WMS库位管理模块。1000多行代码,AI几分钟就扫完了,但真正让我震撼的,是一个跑了10多年的蛇形算法——奇数排从左到右,偶数排从右到左,同列共享动线号。简单到几十行代码,却让拣货效率提升了40%。
AI能告诉我代码“是什么”,但说不清“为什么”。那些看似重复的代码背后,是不同开发者在不同时期为业务稳定做出的选择。读懂前人设计意图,比写新代码更难。
好的设计不是用复杂的技术解决问题,而是用简单的规则解决复杂的问题。技术选型的本质是tradeoff,没有完美方案,只有适合场景的方案。


文章目录

  • 读了1000行老代码,我发现最好的设计是“简单”
    • 摘要:
    • 一、先说背景:一套跑了10多年的WMS系统
    • 二、用AI扫了一遍代码,发现什么了
    • 三、从迭代历程看:每个设计都有它的时代背景
    • 四、什么是动线号?为什么它很重要
    • 五、蛇形走位:一个简单但有效的算法
      • 问题:拣货员的行走路径
      • 双列通道:两排一起拣
      • 为什么双列通道是核心?
      • 效率提升多少?
      • 核心逻辑的Java实现
      • 为什么这个设计在当时是最优的?
    • 六、架构思考:10多年积累的设计智慧
      • 为什么说"简单"才是最难的设计
      • 静态预计算的设计权衡
      • 数据库设计的一个原则
    • 七、未来可能的演进方向
      • 1. 动态动线调整
      • 2. 智能路径推荐
      • 3. 状态管理演进
      • 给同行的几个提醒
    • 八、AI辅助开发的体会
      • AI做得到的
      • AI做不到的
      • 最佳实践
    • 九、总结
    • 下一篇预告

这套WMS系统在我们行业跑了10多年,我运维了好几年,最近开始介入开发。

说实话,之前我对库位管理模块的理解停留在"能用就行"的层面。直到最近用AI辅助做代码审计,把1000多行的库位管理核心代码逐行扫了一遍,我才真正理解了这套系统的设计智慧——它不是一蹴而就的"先进",而是10多年业务迭代中沉淀下来的"实用"

其中有一个算法让我印象深刻——蛇形走位

好的设计不是用复杂的技术解决问题,而是用简单的规则解决复杂的问题。这是我从这套系统中学到的最重要的一课。

今天聊聊这个算法,以及它背后的架构思考。


一、先说背景:一套跑了10多年的WMS系统

几年前,我开始运维公司这套WMS(仓储管理系统)。这套系统在我们行业跑了10多年,支撑着电商仓库的日常作业:收货、上架、存储、拣货、发货。

作为运维人员,我每天和这套系统打交道:处理线上问题、协调业务需求、对接开发团队。但说实话,对代码层面的设计,我关注不多。

库位管理是WMS的基础模块。什么是库位?就是仓库里货架上的一个个格子,每个格子有一个编码,比如A-01-02-003-04,代表A库区、1区、2排、3列、4层。

拣货员每天的任务就是:根据订单,去对应的库位把货物取出来。

关键问题是:去哪个库位、按什么顺序走,决定了拣货效率。

这套系统从早期就考虑了路径优化,经过10多年的迭代,形成了现在的方案。


二、用AI扫了一遍代码,发现什么了

最近,我开始介入这套系统的开发工作。开发过程中,我用AI辅助工具对库位管理模块做了一次深度分析。1000多行代码,逐行扫了一遍。

AI帮我做了几件事:

  1. 识别代码结构:多个方法,分属多个功能模块
  2. 发现自动创建库位逻辑:有多处代码实现了自动创建库位的功能,分布在不同的业务场景中
  3. 梳理调用关系:多个业务模块依赖这个库位管理器
  4. 追溯迭代历程:从早期到近年,经历了多次功能迭代

AI的分析很快,几分钟就完成了。但AI只能告诉你"是什么",不能告诉你"为什么"。

比如那些自动创建库位的代码,AI会说"这是重复代码"。但当我深入分析后才发现,每处代码背后都是一个独立的业务场景,是不同开发者在不同时期为了保证业务稳定而做出的选择

真正让我关注到的,是其中一个算法——动线号生成


三、从迭代历程看:每个设计都有它的时代背景

我翻了一下这套系统的提交记录,梳理了库位管理模块的演进脉络:

早期 → 基础库位管理和动线号生成 中期 → 支持阁楼货架等特殊场景 近年来 → 电商打包台、库存精细化管理等新功能

每一次修改,都是为了响应当时的业务需求:

  • 业务需要支持阁楼货架,于是有了阁楼动线号的特殊处理
  • 业务扩展到新的仓库类型,于是有了新的动线号逻辑
  • 电商打包台上线,于是有了打包台相关的库位配置
  • 库存管理精细化,于是有了库位锁定功能

每一次设计,在当时都是最优解。前人面对的是当时的业务场景、技术条件、团队能力。他们做出了最适合当时的选择。

作为后来者,我的职责不是评判前人的设计是否"完美",而是理解他们的设计意图,在此基础上继续演进。


四、什么是动线号?为什么它很重要

每个库位都有一个叫动线号的属性,表示拣货员应该按什么顺序访问这个库位。动线号越小,越先被拣货。

打个比方:你去超市购物,如果超市货架是按"蔬菜区→水果区→肉类区→日用品区"排列的,你按这个顺序走一遍就买完了。但如果超市货架是乱排的,你可能要来回跑好几趟。

动线号就是给库位"排顺序"。

我扫了一遍代码,发现了5种不同的动线号生成算法:

  1. 蛇形遍历
  2. 归并交叉
  3. 权重编码
  4. 递归分治
  5. 编码解析

最让我感兴趣的是第一种——蛇形遍历


五、蛇形走位:一个简单但有效的算法

问题:拣货员的行走路径

假设仓库是一个网格,有5排、10列。拣货员需要拣10个SKU,分布在不同位置。

如果没有路径优化,拣货员可能这样走:

排1: 走到最右 → 回头走到中间 → 再走到右边 排2: 走到最左 → 回头走到右边 排3: ...

来回折返,浪费大量时间。

如果用蛇形路径,拣货员这样走:

排1: 从左走到右 排2: 从右走到左 排3: 从左走到右 排4: 从右走到左

像蛇一样S形穿行,几乎没有折返。

双列通道:两排一起拣

上面是单列通道的情况。但实际仓库中,绝大多数货架都是双列通道——通道两侧都有库位。

无论是横梁式货架、阁楼货架,还是驶入式货架,拣货员走进一个通道,左右两侧都能取货。如果只拣一侧就退出通道,再进另一个通道拣另一侧,会浪费大量时间。

双列通道的核心思想:在同一个通道内,同时完成前后两排的拣货,减少通道间的折返。这是路径优化中最关键的场景

场景:正序拣选(从左到右)

前排: [列1-A, 列2-B, 列3-C] 后排: [列1-D, 列2-E, 列3-F] 拣选路径: A→D→B→E→C→F ↓ ↓ ↓ 同列连续完成前后排

拣货员从左走到右,每到一列就完成该列的前后排拣货。

场景:倒序拣选(从右到左)

前排: [列3-C, 列2-B, 列1-A] (反转后) 后排: [列3-F, 列2-E, 列1-D] (反转后) 拣选路径: C→F→B→E→A→D ↓ ↓ ↓ 同列连续完成前后排

拣货员从右走到左,每到一列就完成该列的前后排拣货。

两种场景的共同点:无论哪个方向,同一列的前后排库位都是连续拣选的。这样拣货员在同一个通道内就能完成两排的拣货,效率最大化。

为什么双列通道是核心?

因为几乎所有仓库的货架都是双列布局

货架类型通道结构双列拣选适用性
横梁式货架通道两侧各有1-2排★★★★★
阁楼货架通道两侧各有库位★★★★★
驶入式货架通道深处多排,两侧取货★★★★☆
后推式货架通道两侧各有库位★★★★★
流利式货架通道一侧进货,一侧出货★★★☆☆

可以说,双列通道的路径优化,覆盖了90%以上的仓库场景。单列蛇形只是基础,双列交叉合并才是真正的效率提升点。

效率提升多少?

我算了一笔账:

  • 普通路径:行走距离 ≈ 排数 × 列数 × 2
  • 蛇形路径:行走距离 ≈ 排数 × 列数 × 1.1

效率提升约40-50%。

这意味着:如果拣货员原来每天走2万步,优化后只需要走1万步左右。

核心逻辑的Java实现

蛇形算法的核心逻辑并不复杂,用Java实现大概这样的思路:

单列蛇形

intdirectionFlag=1;// 1=正序, -1=倒序introuteNo=0;for(List<Location>lineLocations:byLine.values()){// 偶数排反转列表if(directionFlag==-1){Collections.reverse(lineLocations);}Set<Integer>cols=newHashSet<>();for(Locationloc:lineLocations){if(cols.contains(loc.getColumn())){loc.setRouteNo(routeNo);// 同列共享动线号}else{routeNo++;loc.setRouteNo(routeNo);cols.add(loc.getColumn());}}directionFlag*=-1;// 切换方向}

双列交叉合并

// 前排和后排按列交叉合并List<Location>mergeInterleave(List<Location>frontRow,List<Location>backRow){List<Location>result=newArrayList<>();// 按列分组Map<Integer,List<Location>>frontByCol=groupByColumn(frontRow);Map<Integer,List<Location>>backByCol=groupByColumn(backRow);// 交叉合并:前排列1 → 后排列1 → 前排列2 → 后排列2for(Integercol:allColumns){result.addAll(frontByCol.getOrDefault(col,emptyList()));result.addAll(backByCol.getOrDefault(col,emptyList()));}returnresult;}

关键点:

  • Collections.reverse实现偶数排倒序
  • HashSet记录已分配动线号的列,实现同列共享
  • directionFlag *= -1每排切换方向
  • 双列通道时,两排同向处理后交叉合并

这就是全部核心逻辑。简单到只有几十行代码,但它解决了拣货效率的核心问题。

为什么这个设计在当时是最优的?

早期,前人设计这个算法时,面临几个约束:

  1. 硬件条件:早期的RF手持终端性能有限,不支持复杂的实时计算
  2. 业务特点:库位相对固定,不需要频繁调整
  3. 团队能力:简单算法更容易维护和交接

在这些约束下,静态预计算的蛇形算法是最优选择:

  • 算法简单,容易理解和维护
  • 计算一次,长期有效
  • 不依赖实时计算,对硬件要求低

六、架构思考:10多年积累的设计智慧

我了解了一些行业内的WMS系统,发现路径优化这块,大家的做法各不相同:

维度一些WMS我们这套系统
路径算法无优化蛇形走位 + 权重编码
动线管理无独立管理拣货动线 + 盘点动线独立管理
特殊场景不支持阁楼货架、越库区、待报废区
生成方式手动配置自动计算 + 手动微调

这套系统从早期就开始实现蛇形走位算法,经过10多年的迭代,不断优化和完善。这种持续积累,是我们这个团队的优势。

为什么说"简单"才是最难的设计

蛇形算法的核心逻辑只有几行代码:

奇数排:从左到右 偶数排:从右到左

但就是这几行代码,解决了拣货效率的核心问题。

复杂的技术不难,难的是用简单的技术解决复杂的问题。

这套系统的设计智慧在于:它没有追求"高大上"的技术方案,而是用最简单、最可靠的方式解决了业务问题。这种"简单"不是一开始就有的,而是10多年迭代中不断打磨出来的。

静态预计算的设计权衡

我分析这套系统的动线号生成,发现一个设计特点:动线号是静态预计算的,不是实时计算的。

也就是说:库位创建时就确定了动线号,拣货时直接读取使用。

这个设计有它的智慧:

1. 简单可靠— 算法在后台运行,不影响拣货性能

2. 易于维护— 动线号存储在数据库,可以查看、修改

3. 支持离线— 即使网络不好,RF终端也能读取动线号

4. 业务适配— 库位相对固定的企业,预计算完全满足需求

5. 性能优异— 拣货员扫描库位时,系统直接返回动线号,响应速度极快

这套设计是10多年业务积累的产物。它不是最先进的技术方案,但它是最适合我们业务场景的方案。

数据库设计的一个原则

这次分析让我想到一个数据库设计原则:能离线计算的,不要在线计算;能预计算的,不要实时计算。

动线号就是一个典型例子。它在库位创建时计算一次,存储在数据库中,拣货时直接读取。这个设计:

  • 减少了拣货时的计算开销
  • 简化了RF终端的实现
  • 支持离线场景下的路径推荐

如果当初选择实时计算,每次拣货都要重新排序,性能和复杂度都会成倍增加。


七、未来可能的演进方向

随着业务发展,这套系统可能会有新的演进方向。我梳理了几个可能的方向:

1. 动态动线调整

场景:如果未来仓库布局频繁调整,静态预计算可能不够灵活。

可能的方向

  • 增加"动线号版本"概念,支持多版本共存
  • 库位调整时自动触发动线号重新计算
  • 支持按区域、按类型独立计算

2. 智能路径推荐

场景:如果业务需要更精细的路径优化。

可能的方向

  • 结合订单波次,动态生成拣货路径
  • 考虑库位之间的距离权重
  • 支持多拣货员协同作业

3. 状态管理演进

场景:库位有多个状态字段,管理锁定状态。

可能的方向

  • 用位运算合并状态字段
  • 减少数据库字段数量
  • 简化状态查询逻辑

但这些演进是否需要做,取决于业务需求。当前系统运行稳定,演进应该是"业务驱动"的,而不是"技术驱动"的。

给同行的几个提醒

基于这次分析,我总结了几点经验,给同样在做WMS系统的朋友参考:

1. 动线号必须有"重新计算"的入口

库位调整后,动线号会错乱。系统必须提供"重新计算动线号"的功能,否则拣货路径会出问题。

2. 蛇形算法适合库位固定的场景

如果库位每天都在变,静态预计算可能不够灵活。这时候要考虑实时计算方案,或者支持动线号版本管理。

3. 权重系数要根据实际场景调整

阁楼货架的权重编码(比如1层列号×5),这个系数要根据实际楼层间距调整,不能照搬。不同仓库的物理结构不同,系数也要不同。

4. 拣货动线和盘点动线要分开管理

拣货是选择性遍历(只拣需要的SKU),盘点是全量遍历(所有库位都要扫)。两者的优化目标不同,动线号应该独立管理。


八、AI辅助开发的体会

这次用AI分析代码,我有几点体会:

AI做得到的

  1. 快速扫描:1000多行代码,几分钟就扫完了
  2. 模式识别:自动发现代码结构、调用关系
  3. 结构梳理:生成依赖分析、演进历程
  4. 历史追溯:快速查看提交记录,理解迭代过程

AI做不到的

  1. 理解业务意图:AI知道代码"是什么",不知道"为什么"
  2. 判断设计合理性:AI能指出代码特征,不能判断这个设计是否合理
  3. 评估全局影响:AI只看局部,不知道改动会影响什么
  4. 理解历史背景:AI不知道这个设计是在什么约束下做出的

最佳实践

AI做分析,人做决策。

AI帮我识别了代码结构,但"为什么要这样设计"需要我深入业务去理解。AI帮我梳理了调用关系,但"要不要调整"取决于业务优先级。

作为架构师,我的价值不是发现代码特征——AI比我做得更快更好。我的价值是理解业务、权衡取舍、做出决策。


九、总结

这次用AI分析WMS系统,让我重新审视了一些"理所当然"的设计:

  1. 10多年积累的价值:这套系统能稳定运行10多年,不是因为某个天才设计,而是因为10多年的持续积累
  2. 简单算法的价值:蛇形走位不复杂,但效果明显
  3. 静态 vs 动态的权衡:预计算简单可靠,适合库位固定的场景
  4. AI辅助的边界:AI是分析工具,不是决策者

技术选型的本质是tradeoff。没有完美的方案,只有适合场景的方案。

对于这套WMS系统,蛇形走位是一个务实的选择:简单、有效、可维护。它经过10多年的业务验证,至今仍在发挥作用。

作为20年的老兵,我越来越觉得:我的价值不是写代码——AI比我写得更快。我的价值是读懂前人的设计、理解业务的本质、做出正确的权衡。

好的架构不是一蹴而就的,而是在业务迭代中不断演进的。我们要做的,是在理解前人设计意图的基础上,继续向前走。这,才是"架构至善之路"的真正含义。


下一篇预告

下一篇聊聊WMS库位管理的另一个话题:库位编码设计

一个编码如何承载一个仓库的空间信息?为什么"编码即坐标"是一个好的设计?

欢迎关注,持续更新。