写代码和做工程有什么区别:局部实现与系统责任

作者:

在

作者:lihao技术文章发布:工程师方法论 · 第 02 期

从一次格式转换扩展到可维护交付,说明错误传播、可观测性、版本、发布与恢复为何必须进入工程完成标准。

函数返回正确,任务就结束了吗

一个转换函数在正常样例上输出正确,但程序把结果写到一半时磁盘满了,下次运行又误把半成品当成完整文件。函数的局部计算可能没有问题,用户却已经失去可信的结果。

写代码是实现行为的重要手段;做工程还要承担该行为进入完整环境后的责任。二者不是职业高低之分,也不是说程序员不做工程,而是看讨论范围:从局部输入输出,扩展到运行、交付、变化和失败。

本篇延续上一篇的假设性文件转换任务,所有事故情境都是构造案例,没有虚构真实项目经验。

先画出完整路径,再决定代码边界

用户选择输入→解析→转换→校验→写入→通知完成。每一段都有接口,数据、错误和资源的责任必须沿路径传递。解析器拒绝未知编码,调用方就不能把拒绝当成空数据继续写“成功”。

局部函数可以承诺合法记录转成合法记录;系统还要保证用对版本、实际读到全部输入、输出未损坏。单元测试覆盖局部性质,集成与端到端检查覆盖连接后的行为;任何一层都不能自动替代另一层。

NASA关于产品实现的讨论把验证与最终用途确认分开,也强调集成与转移过程。[1] 这里借用的是责任分解方法,不把一个小工具冒充航天工程的复杂程度。

失败发生时,结果应该处于什么状态

一个候选设计是先写临时产物,校验通过后再提交为正式结果。若写入失败,保留原输入,标记失败并避免半成品被误读。实现时还要检查文件系统与平台语义:单个文件重命名的保证不能直接推广为整个目录树的事务。

重试也不是“再跑一次”那么简单。若操作会发送通知或覆盖已有输出,必须考虑重复副作用;若恢复点记录不可靠,断点续跑可能跳过未完成的记录。应明确一次任务的身份、已完成范围与重复运行的规则。

这是一份设计评审候选,不是已实现并验证的恢复机制。停止条件包括:无法确认输出完整性、输入版本发生变化、校验规则不兼容;此时应停止提交,而不是用成功提示遮住不确定状态。

可观测性,是让故障变得可定位

只写“转换失败”通常不够。可以记录任务版本、输入标识、处理阶段、已完成数量与错误类别;涉及私密文本时,不应为了诊断把完整内容写进公开日志。诊断信息也有最小化与权限边界。

用户需要知道结果是否完成,维护者需要知道在哪里失败。两者的输出可以不同:界面给出明确下一步,内部日志保留定位所需细节。暴露堆栈不是完整用户体验,隐藏所有原因也不是稳健设计。

指标同样要和目标对应。总耗时、失败比例、峰值内存可能有用;若只记录平均值,少数严重卡顿就可能被掩盖。选指标的目的是检验承诺,而不是装满一个仪表盘。

修改与发布会创造新的责任

格式从v1变成v2时,要知道旧输入能否继续读取、新输出由谁消费、是否需要迁移、怎样验证兼容和回退。代码在开发机运行,不代表依赖、权限与配置在部署环境都满足。

Google SRE的Release Engineering章节讨论可重复构建、发布流程与配置管理。[2] 对本例可缩减为:记录源码版本与依赖;用固定样例核对产物;先在隔离输出目录运行;保留可重新执行的上一版本与原输入。

回退也有边界:旧程序未必能读取新版本已经生成的数据。若迁移不可逆,回退代码本身不够,还需经过验证的数据恢复方案。不能写下“有回滚”三个字,就宣称恢复已验收。

把错误处理写成状态机,遗漏就更容易看见

为转换任务定义Created、Running、Validated、Committed与Failed五个状态。Created尚未处理,Running可能有临时输出,Validated表示约定检查已通过,Committed才允许把结果对外标为完成,Failed记录失败原因与可恢复范围。

正常路径是Created→Running→Validated→Committed。解析或写入错误可从Running进入Failed;校验错误可从Validated之前进入Failed。若还没校验就直接写“完成”,系统绕过了完成条件;若进入Failed后仍暴露临时目录为正式结果,错误状态就没有隔离副作用。

状态机不要求必须引入复杂框架。一份清晰枚举与几条转换规则就可能足够。重要的是每条转换的守卫条件与持久化时机:程序崩溃后,仅凭内存里的“已校验”标志,下一次运行未必能安全恢复。

例如先把临时文件替换成正式结果,再写入 Committed 记录。若替换已经发生、记录写入前进程崩溃,重启后只看状态记录会把这次任务误判为未提交;盲目重跑又可能重复通知或覆盖结果。把顺序倒过来也不自动正确:若先写 Committed、再替换失败,就会宣称一个并不存在的结果已完成。

设计必须规定正式产物、任务身份与完成记录之间怎样对应,以及恢复时怎样核对并消除这个不确定窗口。这里指出的是协议责任,不提供适用于所有文件系统和外部副作用的原子提交实现。

它也帮助定义不变量:原件始终不被写入;只有Committed结果可供下游消费;相同任务身份不能同时产生两个互相矛盾的正式结果。验证可以围绕这些性质设计,而不只是逐个函数覆盖。

重试、幂等与“恰好一次”不是同一个承诺

如果某个步骤执行完,但确认消息丢失,调用方无法仅凭超时判断它没发生。直接重试可能重复写文件、重复记账或重复发送通知。超时描述的是等待结果的状态,不能自动推出远端没有副作用。

对本例,可以给输入清单、转换版本与参数形成任务身份,并把正式输出与该身份绑定。相同任务再次到来时,若已经存在可验证的完成记录,就返回已有结果;若只有临时产物,就按恢复策略处理。输入内容改变而路径相同,不能误用旧结果。

幂等表示重复操作在规定语义下不会增加新的效果;它依赖具体操作与状态,不是接口名字里加一个request_id就成立。“恰好一次”若覆盖多个外部系统,还需要讨论原子性与恢复边界,本篇不靠一个本地示例承诺它。

这种分析说明工程责任为什么会超出局部实现:函数可能正确完成一次调用,而完整协议仍然在重复、丢失与中断时产生错误。

从补丁到可交接版本,还应留下什么

交付记录可以保持简短,但至少包括:源码与构建身份、依赖与配置、输入输出格式、支持范围、启动方式、验收证据、失败处理及回退限制。任何一项若只存在于开发者脑中,接手者都要重新支付发现成本。

变更评审还要看兼容方向。新版本能读旧输入,是向后读取兼容;旧版本能否读新输出,是另一件事。只升级程序而改变持久化格式,可能让回退失败。发布时需要说明迁移是否可逆,以及旧数据和配置在哪里保留。

有些证据可自动取得,有些必须人工确认。自动化适合重复、明确的性质;真实使用目的、可理解的诊断与组织责任仍需要评审。测试通过率不能代替完整验收,也不能因不能自动化就放弃记录。

工程完成不等于永不失败,而是当前承诺有依据、失败可被识别、变化有路径。对一次性任务可缩减流程,但不能把不可逆风险留给用户在故障时才发现。

可靠性投入,也需要停止条件

给一个一次性、可重建的只读转换任务引入多机调度、复杂事务与完整监控平台,可能比原问题更难维护。工程责任不是无限增加防护,而是识别失败的概率、影响与恢复成本,再选择足够的措施。

可以先列故障模式:输入非法、磁盘满、进程中断、版本不匹配、重复运行。对每一项问用户会失去什么、能否重新运行、能否检测错误、恢复需要多久。若原件保留且输出可重建,临时输出隔离与清晰失败报告可能已经承担主要风险。

若任务进入多人长期使用、写入不可逆外部状态或承担更高影响,再提高审计、隔离与恢复证据。停止条件可以是:剩余风险已被明确接受,新增机制不再降低主要损失,或维护成本超过可避免损失。没有实际概率与成本数据时,应说明这是定性判断,不编造精确ROI。

因此,写代码与做工程的差异不在文档厚度,而在是否对实际使用后果负责。一个很短的实现也能有清楚契约;一个流程繁多的项目也可能仍没回答失败由谁发现、谁来恢复。

怎样防止工程变成无止境加流程

最强异议是:这些事项会不会让一个小工具迟迟不能交付?答案取决于风险。一次性、可重建、原件只读的任务可以保持简单;长期服务、多人使用或不可逆写入需要更严格的证据。流程应随影响范围调整。

一个合适的完成标准是:正常路径满足需求;关键失败有明确状态;版本与运行方式可重现;接手者知道支持边界;恢复方案与未验证项分别列清。若某项不适用,就说明理由,不机械补表。

工程责任的目标是降低使用与变化的总成本。写出功能让工具开始有用,把条件、证据与恢复说清,才让它更可能长期可用。

来源、证据与阅读路线

  1. NASA Systems Engineering Handbook,第5章,集成、验证、确认与移交
  2. Google Site Reliability Engineering,Release Engineering,构建、版本与发布流程

证据范围:任务状态、重试与恢复场景为构造的设计分析;尚未实现所述转换工具,也未验证文件系统持久化、并发或故障恢复行为。

版本1.2 · 2026-10-18:完成事实、条件与引用复核,逐篇明确证据方法;有实质缺口处补齐推导、反例或原题。原网址与首次发布日期保留。

lihao
Author: lihao

喜欢这篇文章?登录后即可点赞,或先注册账号。

评论

0 条评论

欢迎交流与勘误。登录后可评论,提交后公开;请勿发布隐私、广告或无关内容。

写下你的评论