第9章:伪代码编程过程

第9章:伪代码编程过程:先用问题域语言固定职责,再逐层翻译为实现,并用代码检查与重放确认意图没有丢失。

学习目标

  • 能把一个类或子程序的对象、输入、状态、输出和失败边界写成可检查的职责声明。
  • 能先用问题域语言写伪代码,再逐层翻译成实现,并说明每一层新增的细节。
  • 能用正常值、边界值和单一故障重放过程,定位首个偏离并证明修订没有改变原意。

为什么需要伪代码编程过程

从空编辑器开始写语句,往往会让语言语法替问题定义做决定:循环、异常和数据结构先出现,真正的职责却要到测试失败时才被发现。伪代码编程过程把这条顺序倒过来,先用问题域说清“谁负责什么”,再让每一层实现回答一个更窄的问题。这样做不是多写一份文档,而是把不可逆的实现承诺推迟到意图已经稳定之后。

本章的贯穿例子是“计算一笔订单可用的折扣”。先声明输入订单、资格规则、输出金额和拒绝条件,再选择创建一个类还是创建一个子程序;随后以问题域语言写分支,最后才落到具体语言。每次实验只改变一个直接条件,观察哪一个节点先失守。

核心合同:一次只下降一层

一个可靠的 不是代码注释,也不是随意的自然语言。它应当让第二位读者在不看实现的情况下复述职责、输入、输出和失败路径。可把本章合同写成:

implementation=refine(problem intent, one level at a time)implementation = refine(problem\ intent,\ one\ level\ at\ a\ time)

这里的“一个层次”意味着每次只引入一类新决定。例如先回答“订单是否满足折扣资格”,再回答“资格数据从哪里取”,最后才选择比较运算符、类型和错误处理 API。若一次改动跨越三层,测试通过也无法解释是哪一个决定真正造成变化。

第 9 章 · 从意图收敛到实现

伪代码编程过程:先守住职责,再翻译语法

先预测故障会在哪个节点出现,再切换模式或注入故障;图中每个节点都显示它应留下的证据。

创建子程序:问题域 → 伪代码 → 实现 → 证据每一次细化只引入一层实现细节,故障先在它违反的合同节点停住1职责声明对象、输入、结果当前检查2伪代码设计问题域步骤待检查3实现翻译一层一层落地待检查4代码检查合同与边界待检查5收尾复核命名、测试、重放待检查当前观察:职责声明应留下:对象、输入、结果;建模对象:前置条件与后置条件证据闭合:实现细节仍能回指到可读的职责和合同1. applyDiscount:输入订单,返回折扣结果2. 若订单满足条件,就计算折扣并返回新的金额3. if eligible then calculate discount and return result
可以交接:职责声明的输入、产物与检查动作已经说清。
目录节点 13/13
展开本章 13 个目录核对点
  • 第9章 伪代码编程过程
  • 9.1 创建类和子程序的步骤概述
  • 创建一个类的步骤
  • 创建子程序的步骤
  • 9.2 伪代码
  • 9.3 通过伪代码编程过程创建子程序
  • 设计子程序
  • 编写子程序
  • 检查代码
  • 收尾工作
  • 根据需要重复上述步骤
  • 9.4 伪代码编程过程之外的其他方案
  • 关键点

9.1 创建类和子程序的步骤概述

9.1 创建类和子程序的步骤概述 先问“要稳定地拥有哪种职责”,再选择类或子程序作为边界。类适合拥有持续状态和必须一直成立的不变量;子程序适合把一次动作的输入、结果和失败语义压缩到调用边界。两者都要先写问题域合同,不能因为某种语言提供了方便的语法就直接选它。

步骤概述可以分成五个检查点:声明职责,写问题域伪代码,逐层翻译,检查实现,最后收尾并重放。类的路径要额外确认构造后的状态合法、公开操作不会绕过不变量;子程序的路径要额外确认调用者知道参数单位、返回值和副作用。五个检查点是顺序上的护栏,不是要求每个项目都写成相同长度。

创建一个类的步骤

创建一个类的步骤 从状态所有权开始。先写对象代表什么、哪些状态组合合法、哪些操作可以改变状态,再为每个操作给出正常路径和拒绝路径。例如 Order 可以拥有订单行与总额不变量,外部调用者只能通过 addItemapplyDiscount 改变它。若外部代码可以直接写总额,类名再漂亮也没有守住边界。

的验收不是看字段数量,而是看变化是否会先停在对象内部。切换折扣规则时,如果所有调用者都必须理解内部存储,说明类只封装了数据没有封装规则;如果调用者只依赖命名操作和结果合同,变化才真正被隔离。

创建子程序的步骤

创建子程序的步骤 从调用者的意图开始。把动作写成“对什么输入做什么,成功留下什么结果,失败是否有副作用”,再决定参数和返回形式。calculateDiscount(order, policy) 的合同应该说明订单必须已通过基本校验、策略版本如何解释、返回金额的单位以及拒绝时订单是否保持不变。

子程序不应通过隐式全局状态传回关键结果,也不应把校验、写入和通知藏在一个布尔开关后。若多个动作必须按顺序组合,让上层明确组合它们;这样测试可以分别覆盖边界,也能在故障发生时知道哪个责任需要回退。

9.2 伪代码

9.2 伪代码 的价值在于它站在问题域一侧。它可以使用“如果订单满足资格就应用折扣”这样的结构,但暂时不决定 if、异常类型、数据库调用或具体容器。伪代码应足够精确,让人能据此列出样例和不变量;又要足够抽象,让实现者可以比较几种语言方案而不重写业务意图。

可以用一个问题检查:这一行新增了什么读者必须知道的细节?如果答案是“只是为了让某个框架编译”,它属于实现层,不应该提前污染伪代码。伪代码结束时至少应能指出正常分支、边界分支、拒绝条件和输出。

9.3 通过伪代码编程过程创建子程序

9.3 通过伪代码编程过程创建子程序 是把上面的合同落实为一条可重放的工作流,而不是把伪代码当成一次性草稿。先写动作与边界,再给每一步一个可观察产物;实现后回到原伪代码,检查命名、控制流、状态变化和测试是否仍然表达同一件事。

设计子程序

设计子程序 时,先区分查询、转换和改变外部状态的动作。查询应让返回值足以支持调用者判断;改变状态的动作应明确副作用、幂等性和失败后状态。参数列表应携带单位、范围、所有权和空值含义,避免把几个同类型数字交给调用者猜。

写在入口, 写在出口。二者之间是实现可以替换的空间:换一种循环或容器不应改变合同。如果前置条件无法检查,先缩小接口或把未知项显式化,不要把猜测埋进实现。

编写子程序

编写子程序 时按伪代码的顺序一次下降一层。先把“判断资格”翻译成命名的判断,再把“计算金额”翻译成带单位的运算,最后接入错误处理和外部依赖。每一步都可以编译或测试,但编译只证明语法和类型暂时一致,不证明原来的职责没有被新细节替换。

对于类,编写阶段还要检查构造器与公开操作是否共同守住不变量;对于子程序,要检查每条返回路径都符合后置条件。若发现实现需要越来越多例外分支才能解释,退回伪代码重新拆分职责通常比继续加条件更便宜。

检查代码

检查代码 不是只找格式问题,而是让另一位读者沿着合同复述实现。检查对象、输入和单位是否明确,正常分支与边界分支是否都能追踪,错误是否在最近的责任边界被解释,测试是否能够定位首个偏离。对类检查状态不变量,对子程序检查调用者是否能只读接口而不读内部。

代码检查还应把“实现比伪代码多出的每个决定”列出来:数据来源、循环终止、异常传播、资源释放和日志。若某个决定没有对应的意图或测试,就把它标为待确认,而不是用代码已经运行过来替代审查。

收尾工作

收尾工作 把一次成功实现变成可以交接的产物。核对名字是否表达动作、对象和单位,删除只服务于旧草稿的临时分支,补上正常值、恰好边界和故障测试,并记录版本、输入、观察窗口和复位方式。收尾不是“把代码格式化得更漂亮”,而是确认实现与问题域合同仍能互相翻译。

根据需要重复上述步骤

根据需要重复上述步骤 意味着一个复杂职责可以在新的边界上再次展开,而不是把所有细节一次性写完。若某一步仍含有两个不同的变化理由,就回到职责声明;若伪代码仍无法给出可执行样例,就回到问题域;若实现已经清楚但测试发现状态残留,就回到检查和收尾。重复的是缩小问题的动作,不是复制模板句子。

9.4 伪代码编程过程之外的其他方案

9.4 伪代码编程过程之外的其他方案 并不等于“所有项目都必须先写长篇伪代码”。测试驱动开发、示例驱动设计、状态图或类型驱动设计,也可能承担同样的意图外化作用。选择替代方案时要问:它是否让职责、边界、正常路径和失败路径更早可观察?如果只是跳过思考直接生成代码,就没有获得本章过程的核心收益。

替代方案也可以组合使用:用示例固定可见行为,用类型表达一部分不变量,再用短伪代码说明尚未被类型承载的业务分支。判断标准是证据是否可交接、故障是否能定位、复位后是否能重放,而不是文档形式是否叫“伪代码”。

先预测,再用五节点机制链验收

先预测:把“语法提前故障”打开后,你认为五个节点中的哪一个会先变红?再选择“类”或“子程序”,点击对应阶段,观察当前节点的输入、产物与合同文字;最后复位,确认模式、阶段和故障状态全部回到初始值。每一步都嵌入同一张专属机制图,文字只负责解释图中的证据。

分步1 / 3

1. 先声明职责与对象边界

第 9 章 · 从意图收敛到实现

伪代码编程过程:先守住职责,再翻译语法

先预测故障会在哪个节点出现,再切换模式或注入故障;图中每个节点都显示它应留下的证据。

创建子程序:问题域 → 伪代码 → 实现 → 证据每一次细化只引入一层实现细节,故障先在它违反的合同节点停住1职责声明对象、输入、结果当前检查2伪代码设计问题域步骤待检查3实现翻译一层一层落地待检查4代码检查合同与边界待检查5收尾复核命名、测试、重放待检查当前观察:职责声明应留下:对象、输入、结果;建模对象:前置条件与后置条件证据闭合:实现细节仍能回指到可读的职责和合同1. applyDiscount:输入订单,返回折扣结果2. 若订单满足条件,就计算折扣并返回新的金额3. if eligible then calculate discount and return result
可以交接:职责声明的输入、产物与检查动作已经说清。
目录节点 13/13
展开本章 13 个目录核对点
  • 第9章 伪代码编程过程
  • 9.1 创建类和子程序的步骤概述
  • 创建一个类的步骤
  • 创建子程序的步骤
  • 9.2 伪代码
  • 9.3 通过伪代码编程过程创建子程序
  • 设计子程序
  • 编写子程序
  • 检查代码
  • 收尾工作
  • 根据需要重复上述步骤
  • 9.4 伪代码编程过程之外的其他方案
  • 关键点

最小可重放实现

下面的草图固定“订单满 100 元、策略版本 v1、有效折扣规则”作为基线。它不是某种语言的完整实现,而是把观察顺序写成可以交接的合同:

baseline = calculateDiscount(order_v1, policy_v1)
assert baseline.status == "accepted"
assert baseline.amount >= 0
 
rejected = calculateDiscount(order_at_boundary, policy_v1)
assert rejected.status == "rejected"
assert rejected.orderWasChanged == false
 
resetEnvironment()
assert calculateDiscount(order_v1, policy_v1) == baseline

如果实现把拒绝压成一个没有原因的 false,调用者就无法区分非法输入、策略暂时不可用和已经部分写入。检查时保存原始输入、版本、返回结果、首个偏离和副作用;复位后用完全相同的基线重跑,才知道修订修的是合同还是仅仅让最终数字看起来正确。

关键点

关键点 是把实现顺序交给意图,而不是把意图交给语法。创建类时先拥有不变量和操作,创建子程序时先拥有调用合同;伪代码固定问题域步骤,逐层翻译只增加当前必须的细节;代码检查追踪每个实现决定,收尾复核命名、测试和重放。若某次改动无法说明它改变了哪一层,就先停下来重新声明职责。

练习与答案

练习

问题 1:区分类步骤与子程序步骤

为“订单折扣”分别写出创建一个类的步骤和创建子程序的步骤。类至少列出两个不变量与两个公开操作;子程序至少列出两个前置条件、一个后置条件和一个失败结果。先预测两种边界在机制图中会在哪个节点产生不同证据。

问题 2:把一段实现退回一个抽象层次

把下面的实现改写成两层伪代码,并标出每层新增的决定:if (order.total >= 100 && policy.version == 1) return order.total * 0.9; else return 0; 说明为什么不能一开始就把 if、版本号和小数常量当作完整设计。

问题 3:用单一故障保存重放证据

在本章 Lab 中先固定“子程序”和正常输入,再打开“语法提前故障”。写出你预期的首个偏离、应保存的三份证据,以及重置后判断修复有效的条件;逐项用 13 个官方目录核对点说明证据落在哪里。

术语复核

名词解释

本章出现的专业名词,用大白话再讲一遍。

伪代码

用接近业务语言的步骤描述程序要做什么,先不决定具体编程语言怎么写。

职责边界

一个类或子程序负责的范围,以及外部必须通过什么入口与它合作。

抽象层次

一段说明离具体实现还有多远;逐层细化就是每次只补当前需要的细节。

前置条件

操作开始前必须成立、并且可以在入口检查的输入或状态事实。

后置条件

操作成功结束后,调用者可以依赖并检查的结果、状态变化或不变量。

首个偏离

实际轨迹第一次不再符合预期的节点;找到它比只看最后的错误更容易修复原因。

本章依据 《代码大全(第2版)》2006 年中文公开试读目录 核对第 9 章的正式单元和 13 个目录节点;Microsoft Press 官方书页 用于核对英文书目信息与出版范围;C++ Core Guidelines 用于补充现代工程中合同、边界和可检查实现的迁移说明。公开目录与样章不能证明未公开正文的具体措辞、案例或作者判断,本页内容是基于目录范围的独立教学重写。

讨论

评论区加载中…