第10章:使用变量的一般事项
第10章:使用变量的一般事项:把变量的含义、初始化、作用域、存活期和用途收束为一条可检查的数据流。
学习目标
- 能解释一个变量的含义、初始化、作用域、存活期和用途怎样共同影响可读性与缺陷风险。
- 能修改一段状态密集代码,使每条读取路径都有明确值,并让变量在最后一次使用后尽快结束。
- 能回答:发现变量跨越多个职责或某条分支漏初始化时,你会保留哪条证据并做出什么修法?
先看一个会“拖着状态走”的问题
想象你在检查一张订单:一个数字先代表商品金额,后来又被拿来表示折扣后的金额,函数结束前还被另一个分支继续读取。短小的代码可能仍能运行,但读者必须记住这个数字在每一行的不同身份。
本章解决的就是这种隐性负担:让变量的定义、赋值、读取和结束尽量靠近,让每条路径都能回答“它是什么、现在是否有值、谁还需要它”。没有这层约束,错误通常不会在声明处出现,而会在某个边界输入或罕见分支才暴露。
核心模型:把变量当作一段可审查的数据流
先写清一个变量的 ↡读者在看到变量时应能说出的业务对象、单位、状态和允许范围,再安排它何时建立合法值、在哪里可见、何时不再需要。一个实用的审查顺序是:定义 → 初始化 → 局部读取 → 最后一次读取 → 生命周期结束。
把“存活期”近似记成 lastUse - firstDefinition 只是提醒,不是让人追求最短的字符距离。真正的目标是减少跨越职责、分支和时间窗口的状态;只要读者能从同一个局部上下文重建含义,变量就更容易修改和验证。
1. 先辨认含义:变量究竟表示什么
第10章 · 数据流审查实验
定义 → 初始化 → 局部使用 → 结束
先猜故障会在哪个节点出现,再切换场景和分步轨迹,观察变量怎样从“可疑状态”回到可复核状态。
第 1 / 5 步 · 先写出变量表示的对象、类型、单位和允许范围。
先预测首个偏离,再用单步或播放检查变量怎样跨过每个生命周期节点。
这张图把“变量是否可读”转成可观察证据:故障场景不是为了制造红色,而是为了定位第一次读取未获得合法值的节点。
目录节点与实践证据
下面按 manifest 中的 17 个目录节点逐项说明。每个节点都落到同一条机制链:先写预期,再用实验切换正常、故障和修复路径,最后在练习中留下可重放证据。
第10章 使用变量的一般事项
本章的总原则是:变量应在含义和类型已知后定义,在所有读取路径前得到合法值,保持最小作用域、最短存活期和 ↡一个变量只承担一个稳定含义,不在同一段流程中轮换职责。这不是格式偏好,而是为了让一次修改只影响一类状态。
10.1 数据认知
先问“这个值代表什么”,再问“它是什么类型”。例如 retryCount 的单位是次数,timeoutMs 的单位是毫秒;两者都可能是整数,却不能因为类型相同就互换。名称、单位、有效范围和所有者共同构成变量的可读含义。
数据认知测试
测试不要只检查最终数字,还要检查数据流是否经过正确节点。为一个变量准备正常样本、恰好边界和单一故障:正常样本验证主路径,边界样本验证范围,故障样本验证系统是否拒绝未定义或越界的状态,并记录首个偏离。
有关数据类型的其他资源
类型能够限制一部分误用,但不能替代含义、单位和生命周期的说明。需要迁移到现代 C++ 时,可对照 C++ Core Guidelines 中关于初始化、窄化转换和接口边界的建议;它是现代实践的核对资料,不是本章原书正文的替代品。
10.2 轻松掌握变量定义
定义位置应该让读者同时看到初始含义和第一批使用者。若变量在函数顶部统一声明,却在几十行之后才第一次有意义,读者必须记住一个尚未参与当前逻辑的名字;把定义推迟到需要它的局部块,通常能缩短认知距离。
隐式声明
↡没有显式写出定义或初始状态,却依赖语言、运行时或旧路径替它提供值 会把一个未写下的假设变成数据流的一部分。修法不是盲目增加默认值,而是显式写出对象、类型、初始值和不适用时的拒绝条件;这样审查者才能区分“合法的零”与“尚未赋值”。
10.3 变量初始化原则
↡在第一次读取之前为变量建立符合合同的明确值,并覆盖所有可达分支 必须发生在读取之前,而不是发生在“通常会先走到”的路径上。若 hasDiscount 为假时没有给 discount 赋值,就应该在定义处提供明确的零折扣,或把计算移动到保证条件成立的局部块。
10.4 作用域
↡变量名可以被看见并被访问的最小代码范围 应覆盖所有必要读取,但不应把无关的后续流程也包进来。缩小作用域会让编译器和审查者更早发现误用,也让变量的所有者更清楚;它不是把代码压成一行,而是减少可见的候选状态。
使变量引用局部化
让变量引用局部化,就是让定义、写入和读取聚在同一个职责内。跨函数传递一个只为中间计算存在的临时值,会扩大接口和测试面;如果确实需要跨边界,就把它变成有名称、有单位和有不变量的结果对象,而不是继续隐藏在一个通用数字里。
尽可能缩短变量的“存活”时间
变量从定义到最后一次读取的区间越长,期间被意外改写、误读或与新职责混用的机会越多。完成一段计算后就结束临时变量,不要为了“以后也许会用”把它保留到函数末尾;若未来真的需要,重新在那个职责的局部范围定义。
减小作用域的一般原则
减小作用域的顺序可以是:先移除不必要的共享,再把定义下移到第一次使用附近,接着把只在分支中需要的值放进分支,最后让结果在最后一次读取后离开。每一步都应运行原有测试,确认行为不变且首个数据流差异可解释。
有关缩小变量作用域的说明
缩小作用域并不意味着所有变量都必须是最深层的局部变量。配置、缓存或跨请求状态有时确实需要更长的寿命;这时要显式写出所有者、并发假设、失效条件和清理动作。原则是让边界与真实职责一致,而不是机械地追求局部化。
10.5 持续性
↡变量在多次调用、多个事件或更长时间窗口中保留值的能力 是一项业务决定,不应由变量恰好放在外层作用域而偶然产生。需要跨事件保存的状态应有明确所有者、更新规则和重置动作;只服务一次计算的临时值则应在计算结束后消失。
10.6 绑定时间
绑定时间指“变量的含义或所引用对象在何时被确定”。尽早绑定能减少运行时选择,但也可能把本应随请求变化的信息冻结太早;延后绑定可以保留灵活性,却必须标明有效窗口和失败处理。审查时要问:这个决定在定义、初始化还是调用边界被固定,是否符合需求。
10.7 数据类型和控制结构之间的关系
控制结构决定哪些赋值和读取路径可达,类型则决定这些值能否表达合法状态。if、循环和早退让“每条路径都初始化”成为路径问题;枚举、结果对象或不可空类型能把部分路径约束前移。二者要一起审查,不能只看类型声明或只看分支覆盖率。
10.8 为变量指定单一用途
单一用途要求一个名字在同一语境里只承担一个稳定问题域角色。不要让 value 先代表输入、再代表中间结果、最后代表错误码;拆成 rawAmount、netAmount 和 errorCode 后,读者不需要依靠时间顺序猜测含义,测试也能分别覆盖它们的边界。
关键点
遇到变量问题时,依次回答五个问题:含义是否明确?读取前是否初始化?可见范围是否最小?存活期是否跨越了不必要的职责?名字是否只有一个用途?修复应保留“故障输入、首个偏离、拒绝理由和重置后轨迹”,否则只是把结果改成绿色,不能证明数据流真的变清楚。
代码对照:把跨越状态收束到局部
下面的草图只演示结构,不复制原书代码。第一段展示容易漏初始化的分支;先预测:当 hasDiscount 为假时,哪一个读取会暴露问题?
double total;
if (hasDiscount) {
total = amount - discount;
}
return total; // 某条路径没有建立合法值更稳妥的改法是让初始值和所有权在读取附近可见,并把一次用途的计算留在局部。对于业务上不允许缺省值的场景,应改为显式返回错误,而不是偷偷塞入零。
const double total = hasDiscount
? amount - discount
: amount;
return total;这段修改同时改善了初始化和单一用途,但仍需用正常、边界和故障输入验证“无折扣”是否真的代表业务上的合法金额。
常见误区
小结
- 先写数据含义、单位和范围,再决定变量的类型与名字。
- 每条读取路径都应在读取前获得明确的合法值。
- 作用域和存活期应覆盖真实职责,不为未来猜测预留共享状态。
- 单一用途让修改、测试和复核都能定位到同一个数据角色。
练习与答案
练习
问题 1:找出首个数据流风险
给出一段含有 total、discount 和 hasDiscount 的结算函数。画出正常、无折扣和缺少折扣输入三条路径,指出每条路径第一次出现不确定含义或未初始化读取的位置,并写出拒绝理由。
问题 2:缩小作用域并保持行为
把一个在函数顶部声明、先计算原始金额、再计算净金额、最后生成日志的临时变量重构为局部变量。说明每个变量的定义点、最后一次读取点和新的所有者,并列出至少一个边界测试。
问题 3:改 Demo 代码并验证复位
打开上方“变量生命周期实验”,先选择故障路径,再把它改成修复路径。写出你预测会先改变的节点、一个能证明修复有效的断言,以及点击“重置实验”后应该恢复的三项状态。
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 数据认知
看到一个变量时,读者能说出它代表的对象、单位、状态和允许范围。
- 隐式声明
没有把定义或初始状态写清楚,却让语言、运行时或旧路径替你做决定。
- 初始化
在第一次读取前给变量一个符合业务合同的明确值,或明确拒绝这条路径。
- 作用域
一个变量名能够被看见和访问的代码范围;越小,越容易看清谁能使用它。
- 持续性
一个值能保留多久、跨过多少次调用或事件;它应该由所有者和重置规则决定。
- 单一用途
一个名字在同一语境中只表示一个稳定的数据角色,不轮换成别的含义。
资料边界
本章用 《代码大全(第2版)》2006 年中文公开试读目录 核对正式单元与 17 个目录节点;Microsoft Press 官方书页 用于核对英文版书目信息与出版范围;变量初始化、类型与边界的现代迁移解释参考 C++ Core Guidelines。这些资料只支持范围、书目和现代实践的边界核对,不冒充原书未公开正文。
本页的机制图、故障路径、代码草图、练习和答案均为围绕 manifest 目录坐标的独立教学重写;它们把“变量含义 → 初始化 → 作用域 → 存活期 → 单一用途”变成可操作的审查顺序,不宣称逐字翻译原书。