debug驱动学习------三次debug改变我的技术认知
多数人学技术:看文档→看教程→写Hello World→踩坑。我的路子不太一样:打开项目→设断点→一行一行跟→看不懂就再跟一遍。二十年下来,这成了我最核心的学习方法。不是刻意,是没有别的路可走。
第一次:2001年,debug PB学业务
2001年,我刚入行做社保。PB(PowerBuilder)项目,数据窗口拖来拖去,界面长什么样我知道,但社保业务我完全不懂。
什么叫参保?什么叫缴费基数?什么叫视同缴费年限?什么叫养老金计发?这些名词听着都晕。
没人教。老员工都在赶进度,没空给新人讲业务。文档倒是有,厚厚几本,翻了两页就犯困------写的都是政策条文,不是程序逻辑。
我干了一件事:打开PB的debug,设断点,一笔参保业务从头跟到尾。
跟一遍知道了:参保就是往几张表里插数据,先插个人基本信息,再插参保关系,再扣缴费。跟第二遍知道了:中间有校验逻辑,年龄够不够、重复参保没有、上一次参保状态结清没有。跟第三遍知道了:校验逻辑的顺序有意义,先做轻量校验(格式、非空),再做重量校验(跨表查询、业务规则)。
三次debug,业务不是我学会的,是debug替我学会的。
后来带新人,我发现一个规律:看文档学业务的人,半年还说不清一个参保流程有几个步骤;debug跟过一遍的人,一周就能改bug了。原因很简单------debug看到的是真实的执行路径 ,文档写的是人想让你看到的逻辑。这两件事经常不一样。
PB有个好处,debug体验非常好。数据窗口(DataWindow)是PB的核心控件,本质是一个SQL结果集的可视化编辑器。设个断点,鼠标悬停在字段上就能看到当前值,F8单步走,F5继续跑。比Java的debug早了好几年就做到了这种体验。
那个年代没有Google、没有Stack Overflow,PB的报错信息又直白得要命:"数据库连接失败"、"主键重复"。说不出为什么,但告诉你是什么。碰到看不懂的代码,只有一个办法:设断点,跟。
这一段经历给我定了一个基调:看不懂就debug,debug比看书快。
第二次:2005年,debug Java学规则设计
2005年,我到了一家做医保的公司。项目用Java,我的任务是实现医疗保险待遇计算。
待遇计算是这个项目里最复杂的模块。输入一个人、一个时间点,输出他能报销多少。听起来简单,实际上影响因素有十几个:参保类型(职工/居民)、医院等级(三级/二级/一级/社区)、药品目录(甲类全额纳入/乙类自付一定比例)、起付线、封顶线、统筹基金支付比例、大病保险分段、公务员补助、退休人员倾斜......
这些东西排列组合下来,规则矩阵非常庞大。
我拿到手的不是需求文档,是前人留下的代码。据说这套待遇计算逻辑是1999年左右设计的,已经跑了六年,经过了多次修改。注释很少,变量名是拼音缩写(那时候的中国程序员都这么干),看着头疼。
我干的事和四年前一样:设断点,从头跟。
跟了两三天,看到了一个让我佩服的设计。
他没有用if-else硬编码每一条规则。他做了一张总表 ------数据库里一张配置表,每一行是一条规则,有一个order字段表示执行顺序。程序启动时把这张表加载到内存,待遇计算的时候按order排序,逐条执行。
每条规则长这样:
-
编号:R001
-
名称:起付线扣除
-
顺序:10
-
条件:
{累计费用} > {起付线} -
动作:
{可报费用} = {累计费用} - {起付线} -
编号:R002
-
名称:乙类药品自付
-
顺序:20
-
条件:
{药品类型} == '乙类' -
动作:
{自付金额} = {药品费用} * {乙类自付比例} -
编号:R003
-
名称:统筹基金分段支付
-
顺序:30
-
条件:
{可报费用} > 0 -
动作:按金额分段乘对应比例
(以上是示意,实际字段是拼音缩写,但设计思路就是这个。)
看到这张表的时候,我愣了一下。这不就是规则引擎吗?
后来我接触到Drools,发现原理一模一样:规则表+执行顺序+条件匹配。1999年的那位前辈,用一张数据库表实现了一个土版Drools,跑了六年没出大问题。
如果我不是debug跟进去,而是只看文档或者只看接口定义,我永远看不到这个设计。因为调用者不需要知道内部是规则表驱动的,只需要传参数拿结果。但作为接手的人,理解这个设计直接影响了我后面能不能正确修改它------如果你不知道规则之间有顺序依赖,随口改一条规则的order,可能整个待遇计算就错了。
这次debug给我第二个认知:好的设计不是代码写得多优雅,是让后来的人能改得动。一张order顺序的配置表,比一千行if-else好维护得多。
第三次:2007年,debug反混淆看架构
2007年,我到了一家大型软件公司(就叫某软吧)。他们有现成的社保平台,全国几十个城市在用。我的任务是基于这个平台做本地化定制。
问题来了:平台代码做了混淆。
Java代码混淆大家都知道,就是把有意义的类名、方法名、变量名替换成a、b、c、aa、ab这种无意义字符串。反编译出来能看,但读起来像天书。
java
// 混淆前大概是这样的
public class UserService {
private UserDao userDao;
public User getUserById(String userId) { ... }
}
// 混淆后变成这样
public class a {
private b c;
public d a(String e) { ... }
}
某软的目的是保护知识产权------毕竟卖了这么多城市,不希望客户拿源码自己改。理解他们的商业逻辑,但我的活还得干。
我的办法还是debug。
混淆不影响字节码的结构。类还是类,方法还是方法,调用链还是调用链。a.a(e)你不知道它叫什么,但你知道它接收一个String参数、返回一个对象。设断点跟进去,看它做了什么------哦,查数据库了,那这就是个DAO方法。看它返回了什么------哦,是个用户对象,那这就是个User实体。
跟了大概两周,整个架构被我摸清了:
某软的社保平台本质上是SSH(Struts + Spring + Hibernate)换了个皮。
他们写了一个自己的分发器(Dispatcher),用来集成老版本的代码。分发器做的事情很简单:接收请求→解析URL→找到对应的Action→执行→返回结果。和Struts的ActionServlet做的事情一模一样,只不过他们的分发器加了一层兼容逻辑------老的PB/DELPHI客户端通过Socket发XML过来,新的Web端通过HTTP发请求过来,分发器统一处理。
| 某软叫法 | 对应的标准框架 |
|---|---|
| 分发器 | Struts ActionServlet |
| 业务组件 | Spring Bean |
| 数据访问层 | Hibernate Session |
| 自己写的DAO | Hibernate Template的封装 |
说白了,就是SSH集成了一套遗留系统的入口。设计上没什么花哨的东西,但实用------老的客户端不用改,新的Web端直接接,分发器做适配。
反混淆没用什么工具。就是debug + 反编译 + 记笔记。跟到一个类,记下来"a = UserService,c = userDao,a(e) = getUserById"。跟完一个模块,画一张映射图。两周下来,画了十几张A4纸。
有人可能觉得这样做太笨了。但debug看代码有一个读文档永远比不了的优势:你看到的是实际跑的代码,不是文档里写的那套 。文档告诉你系统有分层架构,debug告诉你某个请求实际跳过了三层直接写了SQL。文档告诉你配置走数据库,debug告诉你有个硬编码的fallback在文件里。真相在断点里,不在文档里。
三次debug的共同点
| 2001年 PB | 2005年 Java | 2007年 反混淆 | |
|---|---|---|---|
| 我不懂什么 | 社保业务 | 待遇计算设计 | 某软架构 |
| 怎么学的 | debug跟执行流 | debug跟规则引擎 | debug+反编译映射 |
| 学到了什么 | 业务就是数据流 | 规则表比if-else好维护 | 架构藏在调用链里 |
| 花了多久 | 一周 | 两三天 | 两周 |
三次的共同点:
1. 不是"先学会再做",是"边做边学"。
2001年我不懂业务,但业务系统就在那儿,debug就是最好的老师。2005年我不懂规则引擎,但代码里有一个人写好的,跟一遍就懂了。2007年我不懂某软架构,但跑起来的系统不会骗人。
2. debug看到的是真相,文档看到的是包装。
文档告诉你系统应该怎么工作,debug告诉你系统实际怎么工作。这两件事经常不一样。尤其是接手别人的代码,永远不要先看文档,先debug跟一遍。
3. 混淆、加密、没注释,都挡不住debug。
代码只要能跑,调用链就在那儿。混淆改的是名字,改不了结构。加密的是源码文件,不是运行时的内存状态。没注释的代码,执行路径本身就是注释。
现在还需要debug驱动学习吗?
有人会说,现在有AI了,直接问ChatGPT、丢给Copilot不就行了?
我的看法:AI是加速器,不是替代品。
AI能告诉你Spring MVC的请求分发流程,但AI不知道你项目里那个自定义拦截器为什么跳过了某些请求。AI能解释规则引擎的原理,但AI不知道1999年那位前辈为什么把那条规则的order设成15而不是10。
debug的价值不在于"看懂代码在干什么",而在于看懂代码为什么这么干。这个"为什么",AI回答不了,因为AI不知道当年的业务背景、不知道那位前辈踩过什么坑、不知道那个硬编码的fallback是因为生产环境出过一次事故。
所以我的建议:
- 新技术、新框架:AI + 官方文档快速上手,省时间
- 接手老项目、排查问题、理解设计决策:老老实实debug,没有捷径
二十年过去了,我学东西的第一反应还是设断点。这个习惯是2001年那台PB的开发环境养出来的,改不了了。
后记
写这篇博客的时候,我翻了一下那个2004年的Java项目(就是《2004年的Java项目翻出来了我哭了》那篇),发现我当年写BusiFunc接口的时候,也是先debug别人的代码,看到"接口+反射+动态加载"这个模式,才照着写的。
你看,debug不只是学习方法,是设计能力的源头。你跟过的好代码越多,你写出来的好代码就越多。反过来,只看书、只看教程的人,写出来的代码总是在"翻译文档",不是在"解决问题"。
debug才深刻,读书隔一层。
相关阅读:
- 《造过轮子的人学框架有多快------我自己写完IOC和AOP,Spring就是换个API》
- 《2004年的Java项目翻出来了我哭了------一个老程序员的回忆杀》
- 《一个DLL适配几十家医院:医保接口的全局开关设计》