不要把配置拼写错误当作默认值:设计原理
一句话总结
测试严格的配置解析、机密剔除和按应用划分的快照。
为什么需要它
把字符串 false 转成 bool,结果变成了 True。生产环境里调试响应被打开了,状态页面还把整个配置字典都输出了出来。环境变量是字符串,所以仅靠类型声明,并不能成为安全的值。读取配置的时间点和公开的范围,同样是应用的契约。
工作原理
端口、超时时间、布尔值和必需的令牌,各自单独验证。配置只从给定的字典中读取一次并复制,非法的值不会被悄悄换成默认值。默认值只用于缺失的情形。应用创建之后,即使修改了输入字典,也不应该改变已创建应用的行为。公开状态中只保留服务名称和 debug 值。
문자열 사전 → 개별 타입/범위 검증 → 설정 스냅샷 → 공개 허용 필드
阅读契约并预测失败的工作表
下面不是让你把整个实现背下来的答案,而是分步骤的代码评审。每个改动片段都故意破坏了契约。请注意,改动之后,正常用例仍然可能通过。在运行之前,先预测观测哪些输入、异常和状态才能看出差异,实现之后,再把这个预测与结果进行比较。
1. 显式解析布尔值
parse_bool(value) 只对忽略大小写的 true 或 false 返回 bool。如果带有空白或不是字符串,就是 ValueError。
判断依据:bool('false') 是 True。请直接比较允许的两个字符串。
待评审的错误改动片段:
bool(value)
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
2. 检查端口范围
parse_port(value) 把只含 ASCII 数字的字符串转换为 int,在 1–65535 范围内时返回。其余都是 ValueError。
判断依据:整数转换成功,并不意味着它是有效的端口范围。
待评审的错误改动片段:
<= 65536
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
3. 让超时时间成为有限值
parse_timeout(value) 把字符串转换为 float,只返回大于 0 且不超过 30 的有限值。其余都是 ValueError。
判断依据:NaN 在一般比较中的表现与预期不同,所以要检查 isfinite。
待评审的错误改动片段:
0 <= number <= 30
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
4. 拒绝缺失的必需机密
required_token(env) 在 TOKEN 是字符串,且 strip 之后不为空时,返回 strip 后的值。缺失或为空值都是 ValueError。
判断依据:不要用示例中的默认值来替代缺失的必需机密。
待评审的错误改动片段:
return value
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
5. 只对缺失的情形应用默认值
load_settings(env) 是一个字典:service=env 的 SERVICE,缺失时为 'api';debug=parse_bool(DEBUG 缺失时为 'false');port=parse_port(PORT 缺失时为 '8000');timeout=parse_timeout(TIMEOUT 缺失时为 '5');token=required_token。空的 SERVICE 是 ValueError。
判断依据:get 的默认值与“值 or 默认值”,在处理空字符串时是不同的。
待评审的错误改动片段:
env.get("DEBUG","true")
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
6. 从公开配置中剔除机密
public_settings(settings) 是只含 service 和 debug 的新字典。不要修改原件。
判断依据:与对令牌值的一部分做掩码相比,更应采用根本不公开该字段的契约。
待评审的错误改动片段:
"debug":settings["debug"],"token":settings["token"]}
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
7. 把外部变更与配置分开
snapshot(env) 返回 load_settings 的结果。调用之后即使修改 env,返回的配置也不会改变。
判断依据:把应用启动时刻的配置,与以后可能变化的输入字典分开。
待评审的错误改动片段:
return env
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
8. 确认启动失败和公开响应
create_app(env) 立即读取 snapshot,如果配置非法,就以 ValueError 使应用创建失败。GET /info 只返回 public_settings。用不同的 env 创建的应用之间不共享配置。
判断依据:为了不让配置错误等到服务器启动后的第一个请求才暴露,要在创建时就验证。
待评审的错误改动片段:
return settings
请与包含该片段的函数的公开契约对照。如果仅凭一个成功用例无法区分,就把应被拒绝的输入或失败之后的状态选作观测对象。
在现场相遇的样子
环境是通过普通字典注入的,所以不依赖真实的进程全局环境。这个示例并不会实现加密的机密存储、密钥轮换和动态重新加载。令牌字符串是学习用的输入,不会把真正的生产密钥放进实验 Pod。
下一项实验要做什么
八个步骤会连成一个可运行的成果。显式解析布尔值 → 检查端口范围 → 让超时时间成为有限值 → 拒绝缺失的必需机密 → 只对缺失的情形应用默认值 → 从公开配置中剔除机密 → 把外部变更与配置分开 → 确认启动失败和公开响应。
每一步检查的不是函数或文件是否存在,而是实际的返回值、异常和状态变化。看过正确答案之后,请故意改动边界比较或清理代码,确认哪些测试会失败。请说明前面的测试为什么在下一步中依然保持有效,并写出一条本实验不能保证的生产条件。