逆向PRD:从祖传代码中挖掘真实业务需求的方法论

逆向PRD祖传代码业务规则挖掘 本内容遵循CC 4.0 BY-SA版权协议
lnstagram优选 lnstagram优选 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要

1. 项目概述:当老系统只剩代码,PRD从哪来?

“代码会说真话”——这句话不是修辞,是我在给三家传统制造业客户做系统迁移时,被逼出来的血泪共识。没有需求文档、没有业务流程图、没有UML模型,只有一套运行了12年、用VB6+SQL Server 2000写的ERP子模块,连源码注释都写着“此处逻辑待优化(2008年)”。老板拍桌子:“下个月必须上线新系统,旧数据要全量迁移,业务不能停。”——这时候,你手里的“需求文档”,就是那台还在跑Windows XP SP3的服务器上,一个名为legacy_order_calc.dll的二进制文件。

这个项目标题直击的是企业数字化转型中最隐蔽也最危险的断层:业务知识沉淀在代码里,而非文档中;系统生命力靠运维人员的口头传承,而非可复用的规格说明。它不针对某个编程语言或框架,而是面向所有经历过“祖传代码”维护的工程师、产品经理、测试负责人和架构师。如果你曾为搞懂一段嵌套7层的if-else而通宵反编译、曾因一个字段“看起来是金额但其实是折扣率”导致财务对账差37万、曾被业务方一句“我们一直这么用的”堵得哑口无言——那你就是这个项目的天然读者。

核心关键词“逆向PRD”,不是把代码翻译成Word文档那么简单。它是一套以代码为唯一信源、以业务可验证为唯一标准、以迁移落地为唯一目标的实操方法论。它要求你同时具备:读代码像读小说的耐心、拆解业务规则的抽象能力、与一线操作员聊出真实场景的沟通技巧,以及把混沌信息结构化输出的文档工程能力。我试过用AI自动提取注释生成PRD,结果产出一堆“用户点击按钮后执行SaveOrder()方法”这种废话;也试过让业务部门填需求表,他们填了三天,最后发现表格里90%的字段在旧系统里根本不存在。真正有效的路径,是从代码出发,倒推业务,再正向验证。这不是妥协,而是对现实的尊重。

2. 逆向PRD的整体设计思路:为什么必须放弃“先写文档再开发”的幻想

2.1 老系统逆向的本质是“考古式建模”,不是“翻译式抄录”

常规PRD编写流程是:业务访谈 → 流程梳理 → 规则抽象 → 文档输出 → 开发实现。但在没有原始文档的老系统面前,这条路径直接断裂。你面对的不是缺失几个章节的文档,而是整个知识体系的物理性湮灭。这时候强行套用标准流程,只会陷入三个死循环:

  • 访谈失真循环:业务人员记忆偏差大(“我记得折扣是按数量算的”),操作员只知其然不知其所以然(“点这里就行,系统自己算”),管理者描述宏观却忽略细节(“订单要审核才能生效”——但没说谁审核、什么条件触发审核、审核失败怎么回滚)。

  • 代码盲区循环:直接读源码,会掉进技术细节陷阱。比如看到CalculateTotal()函数,你可能花两天研究它怎么调用GetTaxRateByRegion(),却忽略这个税率计算只在“出口订单”且“客户等级为VIP”时才生效——而这个分支条件,藏在另一个叫CheckOrderType()的函数里,而该函数的调用入口,在UI层一个叫btnSubmit_Click的事件处理中,且仅当txtCustomerID.Text.StartsWith("EXP")时才触发。

  • 验证失效循环:即使你拼凑出一份看似完整的PRD,若缺乏可验证的锚点,它就是一张废纸。新系统按这份PRD开发完,一上线就发现:旧系统能处理“同一客户同一天下5个订单,第3个订单含赠品,前2个订单的运费自动合并”,而你的PRD里只写了“订单独立计费”。

因此,逆向PRD的设计起点,必须是以代码为唯一可信源,构建可验证的业务规则闭环。我的做法是:把整个老系统当作一个黑盒,输入一组已知数据,观察输出结果,再反向追踪代码中产生该结果的所有路径,提取其中的判断条件、计算逻辑和状态流转。这本质上是软件测试中的“逆向工程+等价类分析+边界值挖掘”的组合应用,只不过对象不是新功能,而是沉睡多年的旧逻辑。

2.2 三层穿透法:从代码表象到业务内核的剥茧路径

我将逆向过程拆解为三个递进层次,每层解决一类问题,形成漏斗式收敛:

  • 第一层:数据流穿透(What flows)
    目标:厘清核心实体(如Order、Customer、Product)在系统中的完整生命周期。不关心怎么算,只关心“从哪来、到哪去、中间改了啥”。工具上,我坚持用数据库日志+代码搜索双验证。比如查Order表,先看所有INSERT/UPDATE语句的来源(SQL Profiler抓取生产环境真实操作),再在代码里搜INSERT INTO Order,比对两者是否一致。曾发现一个关键字段OrderStatus,数据库日志显示它有5种状态值(0-4),但代码里只处理了0/1/2,状态3和4是十年前一个未完成的促销模块遗留的“幽灵状态”,至今无人知晓其含义——这直接避免了新系统错误地将状态3映射为“已取消”。

  • 第二层:控制流穿透(How decides)
    目标:定位所有影响业务结果的关键决策点。重点不是函数名,而是if/else/switch块中的布尔表达式。我建立了一个“决策点清单”,每条记录包含:代码位置(文件:行号)、触发条件(如if (order.TotalAmount > 10000 && customer.Level == "VIP"))、执行动作(如applyDiscount = 0.15)、关联的业务术语(如“VIP大额订单专属折扣”)。这个清单不是静态文档,而是动态可执行的。我会用Python写小脚本,把条件表达式转成可运行的逻辑,输入历史订单数据,验证输出是否匹配实际结果。一次,脚本跑出17个订单的折扣率与生产环境不符,追查发现是customer.Level字段在2015年一次数据库迁移中,从VARCHAR(10)被误改为CHAR(10),导致末尾空格影响了字符串比较——这种细节,任何访谈都问不出来。

  • 第三层:状态流穿透(When changes)
    目标:还原业务状态变迁的完整规则。老系统里,状态变更往往不是单次操作,而是多步骤、跨模块、带时间约束的复合过程。例如“订单取消”:UI层点击“取消”按钮 → 中间件校验库存是否已锁定 → 若已锁定,触发ReleaseInventory() → 再调用RefundPayment() → 最后更新OrderStatus。但代码里,RefundPayment()函数还隐含一个条件:只有当支付方式为“在线支付”且退款时效在24小时内才执行,否则走人工流程。这个条件不在取消按钮逻辑里,而在支付模块的回调函数中。我用Mermaid语法(仅用于内部梳理,不输出到PRD)画出所有状态节点和转移边,然后逐条验证:每个转移是否都有明确触发事件?是否有前置条件?失败后如何回滚?有没有超时机制?这张图最终成为新系统状态机设计的唯一依据。

这套三层穿透法,把抽象的“逆向”变成了可分工、可验证、可追溯的具体任务。它不追求一步到位写出完美PRD,而是确保每一条写入PRD的规则,都有至少两个独立证据源(代码+日志/数据+脚本验证)支撑。这才是“能用”的底线。

2.3 为什么拒绝“文档先行”,而选择“代码即PRD”的渐进式交付

很多团队试图先花两周时间“集中攻坚”,写出一份50页的完整PRD,再启动开发。我见过三次这样的尝试,结果全部失败:第一次,PRD交稿后,开发刚写完订单创建模块,业务方突然想起“哦,对,还有个特殊客户类型要走加急通道”,PRD推倒重来;第二次,PRD里定义的“订单超时自动取消”规则,开发实现后测试发现,旧系统其实只对“未付款订单”生效,已付款订单超时后转为“异常待处理”,这个差异直到联调才发现;第三次最惨,PRD基于2018年的代码版本编写,但生产环境偷偷打了3个热补丁,其中一个是修复了税率计算的精度bug,PRD里的规则还是错的。

因此,我彻底放弃了“一次性交付完整PRD”的幻想,转而采用以代码为基线、以迭代为节奏、以验证为门禁的渐进式交付模式:

  • 基线确定:选定一个稳定、可部署的代码版本(如Git tag v2.3.1-release),所有逆向工作以此为准。绝不允许边看代码边改代码,那是灾难的开始。

  • 迭代切分:按业务价值和风险高低,将系统拆分为最小可验证单元(MVP Unit)。例如,第一个迭代只做“订单创建+基础信息保存”,不碰价格计算、不碰状态流转、不碰关联单据。目标很明确:新系统能接收和存储与旧系统完全一致的订单主数据,并通过数据比对脚本100%验证。

  • 门禁验证:每个迭代交付物,必须通过三道门禁:

    1. 代码门禁:PRD中描述的每条规则,在基线代码中找到对应实现,并标注行号;
    2. 数据门禁:提供100条真实历史订单数据,新旧系统输入相同数据,关键字段(如OrderID、CustomerID、CreateTime)输出完全一致;
    3. 行为门禁:录制旧系统关键操作视频(如“VIP客户下单→修改地址→提交”),新系统必须能复现相同界面响应和后台日志。

这种模式下,PRD不是一份静态文档,而是一个活的、版本化的、与代码提交记录绑定的需求集合。每次Git commit,都关联一个PRD变更集(Change Set),里面只包含本次迭代新增或修正的规则。开发人员拿到的,不是厚厚的Word,而是一个Markdown文件,开头就写着:“本变更集基于commit a1b2c3d,覆盖订单创建模块,共3条核心规则,详见下方”。这极大降低了沟通成本,也杜绝了“你说的PRD和我看到的代码不是一回事”的扯皮。

3. 核心细节解析与实操要点:从代码里挖出业务规则的七种武器

3.1 武器一:数据库Schema即业务词典——别信注释,信字段名和约束

老系统的数据库,往往是业务规则最诚实的载体。我从不依赖sys.extended_properties里的中文注释(它们通常过时或为空),而是把CREATE TABLE语句当作第一份需求文档来精读。重点抓三类信息:

  • 字段命名本身即规则:Order.IsPaid是布尔值,但Order.PaymentStatus是枚举。后者更值得深挖——查sys.types或INFORMATION_SCHEMA.COLUMNS,确认它的取值范围。曾在一个系统里发现PaymentStatus有值'PENDING_APPROVAL',但代码里从未处理过这个状态,一查日志,发现这是2012年一个废弃的审批流程遗留,实际已无业务意义。PRD里果断将其标记为“废弃状态,迁移时忽略”。

  • 约束即硬性规则:CHECK (TotalAmount >= 0)意味着金额不可能为负,这是铁律;FOREIGN KEY (CustomerID) REFERENCES Customer(ID)说明客户必须存在,这是前提。这些不是“建议”,是系统运行的基石。我专门写了个SQL脚本,遍历所有表的CHECK约束和DEFAULT值,自动生成一份《数据库级业务规则清单》,作为PRD的附录。它比任何访谈都可靠。

  • 索引暗示高频路径:CREATE INDEX IX_Order_CustomerDate ON Order(CustomerID, OrderDate)表明“按客户查历史订单”是高频操作,PRD里必须包含这个查询的精确响应时间要求(如“1000条以内订单,响应<500ms”),而不仅仅是“支持查询”。

提示:用sp_help 'TableName'(SQL Server)或\d+ TableName(PostgreSQL)快速获取字段、约束、索引详情。别用图形化工具点来点去,命令行输出可复制粘贴进PRD,效率翻倍。

3.2 武器二:日志是沉默的证人——用生产日志反推真实业务流

代码是设计意图,日志是实际行为。我坚持在逆向初期,就接入生产环境日志(需脱敏)。不是看ERROR日志,而是盯INFO和DEBUG级别的业务日志。关键技巧:

  • 聚焦“动词+宾语”模式:日志里反复出现的[OrderService] Created order 12345 for customer ABC、[PaymentGateway] Refunded $200 to order 12345,这就是最真实的业务事件序列。我用Python脚本提取所有这类日志,按时间戳排序,生成一份《典型业务事件流》,它比任何流程图都准确。

  • 识别异常模式即隐藏规则:某次分析发现,日志里大量出现[InventoryService] Failed to release inventory for order 67890: stock insufficient,但业务方坚称“库存不足时订单应自动取消”。一查代码,原来release inventory失败后,系统会重试3次,第3次失败才触发取消——这个“重试机制”在任何文档里都没有,但它直接影响新系统的幂等性设计。

  • 时间戳是状态变迁的刻度尺:同一个订单的日志,从Created到Paid平均耗时2.3秒,但从Paid到Shipped平均耗时47小时。这说明“付款完成”和“发货”之间存在人工干预环节,PRD里必须明确这个环节的SLA(如“财务确认付款后,仓库需在2小时内生成发货单”)。

注意:日志分析必须结合代码。看到Failed to release inventory,立刻去代码里找release inventory的实现,确认失败后的处理逻辑。日志告诉你发生了什么,代码告诉你为什么会发生。

3.3 武器三:配置文件是未公开的开关——那些被遗忘的feature toggle

老系统常有app.config或web.config里的<add key="EnableVipDiscount" value="true"/>。这些键值对,是业务规则的“软开关”。我有个习惯:把所有配置项按业务域分类,逐个测试开关效果。

  • 方法:在测试环境,修改一个配置项(如EnableTaxExemption设为false),然后跑一套标准订单流程,对比开关开启/关闭时的输出差异。差异点,就是该配置项控制的业务规则。

  • 案例:一个系统里有<add key="UseNewPricingEngine" value="false"/>,代码里对应一个if (Config.UseNewPricingEngine)分支。当设为true时,价格计算多了一步“会员积分抵扣”,但这个功能从未在生产环境启用过。PRD里必须注明:“新系统默认启用积分抵扣,但保留配置开关,上线初期可设为false以保持与旧系统一致”。

  • 风险点:有些配置项是“全局开关”,有些是“租户级开关”。必须查代码里读取配置的方式——是ConfigurationManager.AppSettings["X"](全局),还是tenantConfig.Get("X")(租户级)。混淆这两者,会导致PRD规则适用范围错误。

3.4 武器四:UI控件是业务规则的具象化——从像素里读出逻辑

老系统的界面,尤其是VB6或Delphi写的,控件属性就是业务规则。txtDiscount.Enabled = False意味着折扣字段在某种状态下不可编辑;cmbPaymentMethod.Items.Add("Alipay")说明支付宝是支持的支付方式;btnCancel.Visible = (order.Status == "CREATED" || order.Status == "PAID")直接定义了取消按钮的可见条件——这比任何文字描述都精准。

我的做法是:用Spy++(Windows)或Accessibility Inspector(macOS)工具,抓取关键窗体的所有控件属性,导出为JSON。然后写脚本,把Enabled、Visible、Text等属性,转换成PRD里的“界面交互规则”。例如:

JSON
{
"control": "txtDiscount",
"property": "Enabled",
"condition": "order.Status == 'CREATED' && order.CustomerLevel == 'VIP'",
"business_rule": "VIP客户在订单创建阶段可手动输入折扣率"
}

这样生成的PRD,开发人员可以直接当代码写,测试人员可以直接当用例写。

3.5 武器五:单元测试是尘封的说明书——哪怕它们早已失效

很多老系统有单元测试,只是没人维护。我从不删除它们,而是当成“考古现场”。即使测试用例失败,它的Assert语句也揭示了开发者当初认为正确的业务规则。

  • 案例:一个Test_CalculateShippingFee用例,Assert.AreEqual(15.0m, result.Fee)。虽然现在跑不通(因为税率变了),但它证明:当订单金额为1000元、地区为“华东”时,运费应为15元。这就是一条黄金规则。我把它记入PRD,并标注“基准值,新系统需兼容”。

  • 技巧:用git blame查测试用例的最后修改时间。如果一个测试三年没动过,它反映的就是三年前的业务规则,很可能已过时;如果它上周刚改过,那它大概率是当前有效规则。

3.6 武器六:SQL脚本是业务逻辑的快照——那些写在存储过程里的秘密

老系统最爱把复杂逻辑塞进存储过程。usp_ProcessOrder可能有800行T-SQL,里面藏着所有价格计算、库存扣减、通知发送的规则。我把它当作核心需求源。

  • 解析策略:不用逐行读,而是抓SELECT、UPDATE、IF EXISTS、CASE WHEN这四类关键语句。SELECT告诉你数据来源;UPDATE告诉你状态变更;IF EXISTS告诉你分支条件;CASE WHEN告诉你计算逻辑。

  • 实操:把存储过程导出为.sql文件,用VS Code装上SQLTools插件,开启格式化。然后,用正则表达式IF\s+\(.*?\)匹配所有条件,UPDATE\s+\w+\s+SET.*?WHERE.*?匹配所有更新,逐一分析。曾在一个usp_GenerateInvoice里发现,发票金额计算时,对OrderType = 'SAMPLE'的订单,会额外扣除5%的样品费——这个规则在UI里没有任何提示,业务方也忘了,但代码里清清楚楚。

3.7 武器七:网络抓包是跨系统协作的真相——API调用暴露集成契约

老系统常调用外部服务,如支付网关、物流接口。用Fiddler或Wireshark抓包,看它实际发了什么请求、收了什么响应。

  • 关键信息:请求URL(如https://pay-gateway.com/v1/refund)、请求体结构({"order_id":"123","amount":200.00})、响应成功码(HTTP 200)、失败码(HTTP 400及错误消息{"code":"INSUFFICIENT_BALANCE"})。这些就是集成契约,PRD里必须原样复现。

  • 陷阱:注意重试机制。抓包发现,支付回调失败后,系统会每5分钟重试一次,最多3次。这个重试间隔和次数,就是新系统对接第三方服务的SLA依据。

4. 实操过程与核心环节实现:一个订单模块逆向的完整 walkthrough

4.1 第一步:划定逆向边界与基线版本(耗时:0.5天)

以“订单创建”模块为例,我做的第一件事不是打开代码,而是:

  • 确认基线:git checkout tags/v3.2.0(这是上个月生产环境部署的版本,有对应的部署包hash sha256:abc123...)。

  • 划定MVP:只包含OrderHeader表的INSERT、OrderDetail表的INSERT、以及OrderStatus初始值设为'CREATED'。不包含价格计算、不包含库存校验、不包含邮件通知。

  • 准备验证数据集:从生产库导出100条最近订单的OrderHeader和OrderDetail原始数据(脱敏),存为seed_orders.json。

这一步看似简单,却是成败关键。曾有团队跳过此步,直接看最新develop分支代码,结果开发完发现,最新代码里加了一个IsTestOrder字段,但生产环境根本没有,导致数据迁移失败。

4.2 第二步:数据流穿透——绘制订单实体地图(耗时:1.5天)

目标:搞清OrderHeader和OrderDetail在代码中从哪来、到哪去。

  • 入口定位:全局搜索INSERT INTO OrderHeader,找到唯一入口:frmOrderEntry.btnSubmit_Click()。

  • 数据组装:跟踪btnSubmit_Click调用链,发现它调用OrderService.CreateOrder(orderDto),而orderDto由frmOrderEntry的控件值组装。关键发现:

    • orderDto.CustomerID来自cmbCustomer.SelectedValue
    • orderDto.OrderDate来自dtpOrderDate.Value.Date
    • orderDto.CreatedBy硬编码为CurrentUser.LoginName(不是CurrentUser.ID!这是个坑)
  • 出口验证:在OrderService.CreateOrder里,找到INSERT INTO OrderHeader语句,确认插入的字段与数据库Schema完全一致。特别注意:OrderDate字段在SQL里是CONVERT(date, @OrderDate),而@OrderDate参数类型是datetime——这意味着旧系统会丢弃时间部分,只存日期。PRD里必须写明:“订单日期为日期类型,不包含时间”。

  • 关联表:OrderDetail的INSERT在同一个函数里,但有一个隐藏逻辑:当chkHasGift.Checked为真时,会额外插入一条ProductID='GIFT001'的明细。这个chkHasGift控件在UI上,但业务方访谈时没人提过“赠品”功能——代码不会撒谎。

输出物:一份《订单数据流图》,用纯文本描述,不含图表:

TEXT
订单创建数据流:
1. UI层:frmOrderEntry窗体
- cmbCustomer.SelectedValue → orderDto.CustomerID
- dtpOrderDate.Value.Date → orderDto.OrderDate (经CONVERT(date, ...)处理)
- txtOrderNo.Text → orderDto.OrderNo (非空校验在UI层)
2. 服务层:OrderService.CreateOrder()
- 构造OrderHeader实体,字段映射见上
- 若chkHasGift.Checked为True,则向OrderDetail插入ProductID='GIFT001',Qty=1,Price=0
3. 数据层:执行INSERT INTO OrderHeader + INSERT INTO OrderDetail

4.3 第三步:控制流穿透——提取3条核心业务规则(耗时:2天)

基于数据流图,聚焦CreateOrder函数里的决策点:

  • 规则1:订单号生成逻辑
    代码:orderDto.OrderNo = "ORD" + DateTime.Now.ToString("yyyyMMdd") + "-" + GetNextSequence();
    验证:查GetNextSequence()实现,发现它读取Counter表的OrderSeq字段,每次+1。PRD写:“订单号格式为ORDYYYYMMDD-NNNNN,NNNNN为全局递增序列,起始值1,永不重复”。

  • 规则2:客户ID合法性校验
    代码:if (!CustomerService.Exists(orderDto.CustomerID)) throw new Exception("Invalid Customer ID");
    验证:CustomerService.Exists()查询Customer表主键。PRD写:“客户ID必须存在于Customer表,否则创建失败,返回错误码CUSTOMER_NOT_FOUND”。

  • 规则3:赠品逻辑开关
    代码:if (chkHasGift.Checked) { /* insert gift detail */ }
    验证:UI上chkHasGift控件的Visible属性为true,但Enabled为false——它永远不可操作!进一步查frmOrderEntry.Load事件,发现它根据CurrentUser.Role设置chkHasGift.Enabled。只有Role='ADMIN'时才启用。PRD写:“赠品功能仅对管理员开放,普通用户界面隐藏该选项”。

这三条规则,每条都附带代码行号、验证方法、PRD原文。开发人员拿到就能写,测试人员拿到就能测。

4.4 第四步:状态流穿透——定义订单初始状态机(耗时:1天)

OrderStatus初始值设为'CREATED',但这只是起点。我画出从CREATED出发的所有可能转移:

  • CREATED → PAID:当PaymentService.ProcessPayment()成功返回。
  • CREATED → CANCELLED:当OrderService.CancelOrder()被调用,且订单状态为CREATED。
  • CREATED → EXPIRED:当OrderService.CheckExpiry()定时任务发现OrderDate < GETDATE()-1(1天过期)。

关键发现:EXPIRED状态没有UI入口,只能由定时任务触发。PRD里必须注明:“订单创建后24小时未付款,自动转为EXPIRED状态,此状态不可逆转,需人工干预恢复”。

4.5 第五步:验证与交付——用数据比对脚本关门(耗时:0.5天)

写一个Python脚本validate_order_creation.py:

PYTHON
import json
import pyodbc
from new_system_api import create_order
 
# 读取种子数据
with open('seed_orders.json') as f:
seed_data = json.load(f)
 
# 对每条种子数据
for order in seed_data[:10]: # 先测10条
# 调用新系统API创建
new_resp = create_order(order)
# 查询旧系统对应订单(用OrderNo)
old_conn = pyodbc.connect(old_db_conn_str)
cursor = old_conn.cursor()
cursor.execute("SELECT * FROM OrderHeader WHERE OrderNo = ?", order['OrderNo'])
old_row = cursor.fetchone()
# 比对关键字段
assert new_resp['OrderNo'] == old_row.OrderNo
assert new_resp['CustomerID'] == old_row.CustomerID
assert str(new_resp['OrderDate']) == str(old_row.OrderDate.date()) # 注意date()处理
print("✅ 10条订单数据比对全部通过")

脚本跑通,意味着PRD第一条规则“订单号生成”、第二条“客户ID校验”、第三条“日期处理”全部正确。此时,我才把这3条规则和验证脚本,一起打包进本次迭代的PRD交付物。

5. 常见问题与排查技巧实录:踩过的坑,比教科书更有用

5.1 问题1:代码里有“死逻辑”,怎么判断它是否还在用?

现象:CalculateTax()函数里有一段if (order.Country == "CN") { ... } else if (order.Country == "US") { ... } else { taxRate = 0; },但生产日志里,order.Country永远是"CN",US分支从未触发。

排查技巧:

  • 查Git历史:git log -S "order.Country == \"US\"" --oneline,看这段代码是什么时候加的。如果是2010年加的,而系统2015年就停止服务美国客户,那它就是死逻辑。
  • 查数据库:SELECT DISTINCT Country FROM OrderHeader,确认是否真有非CN值。
  • 查日志:grep "Country.*US" production.log | head -10,看是否有相关记录。
  • 终极验证:在测试环境,手动插入一条Country='US'的订单,看系统是否崩溃或报错。如果一切正常,说明逻辑有效;如果报错,说明它已被废弃。

经验:不要轻易删死逻辑。PRD里写:“支持US国家订单,税率为0%,暂未启用,保留接口”。新系统留着,但默认不走。

5.2 问题2:不同环境(DEV/UAT/PROD)配置不一致,PRD该信哪个?

现象:DEV环境app.config里<add key="EnablePromotion" value="true"/>,UAT里是false,PROD里是true,但代码里没做环境判断。

排查技巧:

  • 代码优先:查ConfigurationManager.AppSettings["EnablePromotion"]的使用位置。如果它只在CalculateTotal()里影响一个计算,那就以PROD为准——因为计算结果必须和生产一致。
  • 日志佐证:查PROD日志里是否有Promotion applied字样。有,则启用;无,则禁用。
  • 业务兜底:问业务方:“如果促销关闭,客户会投诉吗?”如果答案是“不会,我们早就不用了”,那PRD里就写“促销功能已下线,新系统不实现”。

经验:配置不一致,本质是运维管理问题。PRD只记录“生产环境实际生效的配置”,并注明“该配置由运维团队统一管理,新系统需支持相同配置项”。

5.3 问题3:业务方说“这个功能我们不用了”,但代码里还在跑

现象:UI上有个“打印发货单”按钮,业务方说“现在都用电子面单,这个功能早停了”。但日志显示,每天仍有20次点击。

排查技巧:

  • 查日志详情:grep "PrintShippingLabel" production.log | tail -20,看点击者是谁(IP、UserAgent)。发现全是仓库管理员IP,且集中在每天早上9点。
  • 查代码:btnPrint_Click调用ShippingLabelPrinter.Print(labelData),而labelData来自GetShippingLabelData()。一查GetShippingLabelData(),发现它还被另一个叫AutoGenerateLabels的定时任务调用——这个任务每天9点自动生成PDF发邮箱。
  • 真相:业务方说的“不用”,是指UI操作;但后台定时任务还在用,且是刚需。

经验:永远相信日志和代码,而不是口头承诺。PRD里写:“支持自动生成发货单PDF,每日9:00执行,发送至仓库管理员邮箱”。

5.4 问题4:PRD写完了,开发说“代码看不懂”,怎么办?

现象:PRD里写了“折扣率=Max(5%, Min(15%, Customer.Level * 0.5))”,开发反馈“Customer.Level是字符串,怎么乘0.5?”

根因:PRD用了业务语言,没给出代码级实现。Customer.Level在数据库是VARCHAR,但代码里customer.Level是int,因为Level字段存的是"VIP"、"GOLD"、"SILVER",代码里用字典映射:{"VIP": 3, "GOLD": 2, "SILVER": 1}。

解决方案:

  • PRD里补充技术细节:“Customer.Level字段值映射关系:'VIP'→3, 'GOLD'→2, 'SILVER'→1,折扣率计算公式为:Math.Max(5, Math.Min(15, levelValue * 5)),单位为百分比整数”。
  • 提供伪代码:“int levelValue = levelMap[customer.Level]; int discountRate = Math.Max(5, Math.Min(15, levelValue * 5));”。

经验:PRD不是给业务看的,是给开发、测试、产品三方共同使用的契约。它必须同时满足:业务可读(用业务术语)、开发可写(有代码级实现)、测试可验(有明确输入输出)。

5.5 问题5:逆向花了两周,业务方说“你们漏了最重要的那个报表”

现象:PRD交付后,业务方突然提出:“还有一个‘月度销售TOP10’报表,老板每天早上要看!”

应对策略:

  • 立即响应:不争论,马上查。grep -r "TOP10" .,找到报表SQL和调用入口。
  • 快速评估:这个报表只读Sales表,不涉及复杂逻辑,属于低风险MVP。把它加入下一个迭代。
  • 建立机制:在PRD首页加一行:“本PRD覆盖范围:截至[日期],已验证的模块列表见附录A。新增需求请提交至‘逆向需求池’,按优先级排期”。

经验:老系统需求是流动的,不是静止的。PRD不是终点,而是持续演进的起点。设立一个透明的“需求池”,比争论“谁该负责”有用一百倍。

6. 工具链与效率提升:让逆向不再靠人肉,而是靠体系

6.1 我的逆向工具箱(全部免费/开源)

  • 代码导航:Visual Studio(C#/VB.NET)或JetBrains Rider(Java/.NET),配合ReSharper,Ctrl+Click直达定义,Find Usages查调用链。比任何IDE都快。

  • 数据库分析:DBeaver(免费),Database → Generate SQL → DDL一键导出Schema;SQL Editor里写脚本查约束、索引、外键。

  • 日志分析:Logstash + Kibana(ELK Stack),或轻量级的lnav(命令行日志查看器),支持正则高亮和字段提取。

  • 配置提取:Notepad++ + TextFX插件,批量替换<add key="为|key|,再用Excel分列,10分钟搞定几百个配置项。

  • 自动化脚本:Python + pandas(数据比对)、pyodbc(数据库查询)、requests(API调用)。所有验证脚本都版本化,放在

相关推荐

基于Python+Open CV的手势识别算法设计

伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大方向;其中,手势识别是人机交互领域的一项关键技术,自此手势识别也迎来了一波高潮,近几年无论是在消费领域、电子领域、数码领域、家电领域甚至汽车领域上,都能或多或少的见到手势识别的身影。 手势识别在设计智能高效的人机界面方面具有至关重要的作用, 目前手势识别已应用到手语识别、智能监控、到虚拟现实等各个领域,手势识别的原理都是利用各种传感器(例如红外、摄像头等)对手部的形态进行捕捉并进行建模,形成一个模型信息的序列帧,而后将这些信息序列转换为机器能够识别的相对应指令(例如打开、切换菜单、移动等)来完成控制。 采用Python的集成开发环境Pycharm进行本次课程设计,在Pycharm中进行需要库(模块)的下载,调取电脑摄像头,按帧读取摄像头采集到的头像,形态学处理,图像旋转(由于摄像头采集到的图像是镜像,需要用cv2.flip将摄像头采集到的图像进行处理),选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。

努力学习前端的小陈

ROS-CV-ImageConverter:将ROS图像转换为OpenCV(cv :: Mat)类型的图像

ROS-CV图像转换器 依存关系 cv_bridge(ROS软件包)

Willis Wang

计算机视觉图像处理-CV2与图像插值算法

用最近邻插值与双线性插值原理完成图像缩放 最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输。缺点:使用最近邻插值算法处理图像放大时,在图象中可能出现明显的块状效应 双线性插值,在两个方向上做三次线性插值。使用双线性插值算法处理图像放大时,在图象中可能出现马赛克 代码 import cv2 img = cv2.imread(’./xiabang.jpg’, cv2.IMREAD_UNCHANGED) #缩放比例 scale_percent = 0.3 width = int(img.shape[1] * scale_percent) height = int

weixin_38697274

基于水平集CV模型的图像分割(Matlab代码)

基于水平集CV模型的图像分割(Matlab代码),全套代码

小宇QAQ

导向滤波(guide_filter)的python实现

导向滤波(guide_filter)的python实现,可以结合暗通道;理论实现比较好的图像去噪效果。

zachzhan_g
返回顶部
lnstagram优选
博客等级 码龄9年 42粉丝 8,369原创