← 全部文章

研发必须懂业务,产品不必学会开发

今天继续思考企业级系统里的分工。我的判断是:做面向大型企业的复杂系统,让研发懂业务是一项 must have 的能力;但是试图让业务或产品通过学习开发,成为「半个工程师」,通常是一条投入产出比很低的路。

这不是说产品不需要技术常识。产品当然需要理解一个需求可能带来的工程成本,知道灵活性、稳定性和长期维护之间存在取舍,也应该能够与研发讨论系统边界。但「理解工程后果」和「掌握开发实现」是两件不同的事。产品需要前者,却没有必要通过学习写代码来承担后者。

理解业务,本身就是一件很难的事

研发懂业务,并不是记住几个名词,或者把需求文档里的规则复述一遍。大型企业的业务知识往往分散在不同角色、不同国家和地区、历史惯例以及大量例外之中。业务人员描述的通常只是当前做法,却不一定知道这项做法最初为什么产生,也未必能够区分哪些是不可破坏的原则,哪些只是过去某一次决策留下来的配置。

因此,研发必须追问实际发生过什么:这个场景多久出现一次,过去如何处理,什么结果绝对不能发生,谁最终承担责任,系统以什么事实作为判断依据。就像 Partner 系统入场审批里的「允许回退」开关,表面问题是如何限制审批人不能退回到错误的节点;继续追问以后才发现,这个功能从未被使用,也没有真实诉求。此时最好的系统设计并不是增加一套更精细的回退权限,而是不要为一个不存在的问题制造复杂度。

更难的是把复杂业务变成稳定架构

即使理解了业务,事情也只完成了一半。工程师还要从复杂而不断变化的规则中,识别出相对稳定的概念、关系与边界:哪些应该进入领域模型,哪些适合成为配置,哪些只是临时方案,哪些变化值得在架构上留下扩展点,但不应该提前实现。

企业软件的架构不能只是业务规则的逐句翻译。否则,业务每增加一个例外,代码里就多一个条件;当例外积累到一定程度,系统看起来支持了所有场景,实际上却已经没有人能够说清一次修改会影响什么。

一个逐级审批的例子

假设业务提出一个看起来很简单的需求:员工请假超过 10 天,需要沿汇报线逐级审批到部门负责人;超过 20 天,再增加 HRBP 审批。

如果只看需求表面,一名工程师很容易写出下面这样的代码:

只按需求表面实现的逐级审批
def approval_chain(employee, leave_days)
return [] unless leave_days > 10

chain = []
manager = employee.manager

while manager && !manager.department_head?
chain << manager
manager = manager.manager
end

chain << manager if manager
chain << employee.hrbp if leave_days > 20
chain
end

这段代码没有明显的语法问题,甚至可能顺利通过第一批测试,但它隐含了许多未经确认的业务假设:「10 天」指自然日还是工作日?分两次提交的假期是否累计?员工存在多个直属上级时,应该选择其中一人还是同时审批?审批过程中汇报关系发生变化,应该使用发起时还是当前的组织关系?部门负责人本人请假时从谁开始?找不到直属上级或 HRBP 时,是跳过、失败,还是进入人工处理?

如果研发不理解这些问题背后的业务含义,就无法判断架构应该如何抽象,也无法区分哪些规则是长期成立的本质约束,哪些只是特定时期、特定场景下的临时配置。最终的代码只能随着需求不断增加条件判断:中国区一种逻辑,美国区另一种逻辑;普通员工走汇报线,特殊岗位走岗位线;单直属上级串行审批,多直属上级并行审批。代码越来越「灵活」,系统却越来越脆弱。

更合理的做法,是先把稳定概念显式表达出来。例如,逐级审批至少包含路由方式、组织关系的取值时点、终止条件、附加审批人、异常处理和审批计划快照:

把稳定概念与变化规则分开
policy = ApprovalPolicy.new(
applies_when: LeaveDurationGreaterThan.new(
10,
unit: :working_day
),
route: ReportingLineRoute.new(
relationship_at: :submitted_at,
cardinality: :single
),
stop_condition: RoleReached.new(:department_head),
additions: [
ConditionalApprover.new(
condition: LeaveDurationGreaterThan.new(
20,
unit: :working_day
),
participant: ParticipantRole.new(:hrbp)
)
],
missing_participant: :manual_review
)

plan = policy.build_plan(application)
application.save_approval_plan!(plan)

这里的重点并不是这些类名本身,而是代码开始表达真实的业务决定。applies_when 把逐级审批的触发条件独立出来;relationship_at: :submitted_at 表示审批链路使用发起时的组织关系,避免流程运行期间因组织调整而漂移;unit: :working_day 明确了时长的计算口径;missing_participant: :manual_review 表示缺失审批人不能被系统静默跳过;保存审批计划,则让一次审批在事后可以被审计和复现。

cardinality: :single 也明确表达了当前的产品边界:按汇报线逐级审批只支持一个直属上级。如果未来真的出现多直属上级的主流场景,可以重新讨论树形审批的产品语义和实现成本;在此之前,系统应该明确拒绝或进入人工处理,而不是为了抽象上的「完整」提前实现一棵没有真实诉求的树。

在这个设计里,「审批链路必须可审计」「参与人缺失不能被静默忽略」「同一次流程使用一致的规则版本」属于本质约束;20 天的阈值、审批到哪个角色、某项政策适用于哪些地区,则更可能是可以变化的配置。稳定架构的价值,不是预测所有未来需求,而是让稳定的部分保持稳定,让真正会变化的部分能够在清晰的边界内变化。

为什么让产品学习开发解决不了这个问题

即使产品学会了 Ruby,会写循环和条件判断,也无法因此回答「10 天如何计算」「组织关系应取哪个时点」「多个直属上级是否应该形成树形审批」这些问题。它们不是编程语法问题,而是业务事实、产品边界与工程后果共同构成的判断。

更危险的是,懂了一点开发以后,人很容易把复杂系统理解成许多简单功能的叠加,认为新的诉求不过是「增加一个开关」或者「再加一种配置」。Airtable、低代码和今天的 AI 编程都在降低生成代码和搭建界面的门槛,但它们没有自动提供对用户场景的理解,也没有替人承担数据一致性、权限、迁移、异常恢复和长期维护的责任。

所以,业务应该把真实场景和责任关系说清楚,产品应该判断问题是否值得解决,并理解不同选择的工程代价;研发则必须深入业务,识别规则背后的稳定结构,并为最终形成的系统承担工程责任。协作的目标不是让每个人都学习对方的职业,而是让关键知识流向最终需要做出判断、承担后果的人。

这也是企业级工程师真正稀缺的地方:既要穿过大量规则和例外,理解一个复杂的业务世界;又不能把这种复杂原样复制进代码,而要把它压缩成一套稳定、清晰、可以持续演化的系统结构。只懂技术、不懂业务,容易设计出结构漂亮但语义错误的系统;只理解业务现象、缺少架构抽象,又会把系统变成无法维护的规则集合。

业务负责定义真实世界,产品负责在价值与成本之间做取舍,研发负责把复杂现实压缩成能够长期运行的系统。研发懂业务之所以是 must have,正是因为架构抽象从来不是纯粹的技术问题。