AI Library · 知识库

# 简洁与显式:删掉一行是否删掉了契约

学习目标:区分源代码行数、需要掌握的概念和行为可见性,判断哪些简化保持语义。前置阅读:库存判断、字符串转端口号;只需理解分支与失败调用方。本篇是基于现有代码的设计分析,不新增程序,也不把简洁程度作为语言排名。

# 案例与变化

原库存规则接收已经校验的非负整数:0 显示 sold out,1~5 显示 low stock,其余显示 in stock。现在产品将低库存阈值从 5 调整为 10,但 0 的提示保持不变。

这个变化只有一个业务维度,不需要引入策略对象。必须保持三条互斥路径;新的边界观察是 0、1、10、11。把三段代码压成“库存有值吗”的判断并不等价:非零库存仍然有两个区间。把阈值判断放到零判断之前,也会把 0 错归为低库存。

另一个常见的“简化”是把端口解析的格式检查删掉,只保留数字转换。这里需求并未放宽:"80x"、" 80" 和 "80" 仍须失败。代码更短但接受集合变了,不属于保持契约的重构。

# 实现需要改变什么

八种语言的库存完整程序都只需调整 stock <= 5 的阈值与边界用例,第一分支和输出文本不变。Rust 与 Kotlin 能用 if 产值,并不意味着必须把三个打印分支改成嵌套表达式;Go 没有三元表达式,也不值得为这个变化创建映射或闭包。

机制事实:Rust 的 if 可以作为表达式,分支结果需要类型兼容;Kotlin 的 if 用作表达式时需要完整分支。分别见 Rust 控制流与 Kotlin 条件表达式。这些规则描述可用机制,不规定哪种布局最易读。

端口例子则有不同层次的显式性:

现有位置 看得见的规则 删除后的代价
JS/TS 的长度与 /[^0-9]/ 检查 1~5 位 ASCII 数字 Number 的接受范围不再受协议限制
Kotlin 的两次 require 格式与业务范围分别成立 toInt() 只承担解析,不承担端口业务规则
C++ 的 ptr != end 必须消费完整输入 一般解析逻辑可能误收前缀
Go 调用方的 if err != nil 错误值先于成功值消费 返回的 0 可能被误当端口

机制事实:ECMAScript 的 parseInt允许读取数字前缀;因此把现有 JS/TS 函数改成单独调用它,会令 "80x" 变为 80。C++ 示例已经预先限制字符,后续停止位置检查在本契约内不是新的可达失败原因;保留它是沿用完整解析结果的消费方式,不应夸大为本例唯一安全屏障。

# 保证与责任

工具反馈:把库存阈值写成 10 通常仍是类型正确的程序,编译器不会知道产品究竟要 5 还是 10。Rust、Go、Java、Kotlin 对条件的布尔约束能排除部分语法或类型误用;JS/TS、Python、C++ 的条件可采用相应真值转换。但这些机制都不证明分支顺序符合库存业务,参见原例关键差异。

运行契约:库存需要观察新边界,而不是只保留 stock = 3 的成功输出。端口需要保留拒绝输入表,而不是只验证 "8080"。类型检查与少量中间值都无法发现接受集合被偷偷放宽。

调用方责任:库存调用方仍负责拒绝负数;端口调用方仍须在失败分支停止使用该结果。现有 JS 驱动只消费 RangeError,其他异常继续抛出;把它简化成无条件打印“invalid port”会隐藏程序错误。异常路径虽然增加行数,却使失败归属可见。

再看报告输出:Python 的 with、显式 UTF-8、newline="" 和关闭后字节断言各自表达不同责任。删掉它们不能只记为“减少样板”;必须先说明资源时序、编码和行尾由谁继续保证。这正是保证由谁承担要求追问的边界。

# 取舍与适用条件

设计分析:当分支只是从两个值中选择日志级别,表达式可让“只初始化一次”更明显;当每条分支包含写入、失败处理或资源操作,语句块更容易暴露时序。简洁应减少读者必须同时记住的规则,而不是把规则移到隐式转换、默认值或远处文档。

本次阈值变化不值得引入“库存状态策略工厂”。若阈值由配置提供、多个界面共享且需要统一更新,再把阈值和展示规则集中到函数或配置边界才有真实收益。代价是新增参数或共享依赖,而不是免费减少复杂度。

不要从单个短函数推导性能结论。Kotlin 原例避免每次构造正则,表达的是具体实现选择;八门语言的运行时间、分配或编译成本并未在此测量。

# 验证与相关阅读

本文未修改原程序,阈值变化及反例是设计推导,没有新增可运行代码。语言基线见主题首页,已有程序执行证据与未覆盖范围见验证记录;不能把原阈值用例的通过状态扩大到新需求。

相关阅读:库存判断的迁移误区 · 端口解析的失败消费 · 保证由谁承担。