10-7.【安全性与稳定性】防御式编程与过度校验的平衡点在哪里?过度使用可能带来哪些副作用?

3 阅读3分钟

防御式编程的本质是风险管理,而过度校验(Over-engineering / Defensive Overkill)则是成本管理失控

两者的平衡点在于:在不可信的边界处严防死守,在可信的契约内保持简洁。


1. 寻找平衡点:边界法则

要找到平衡点,你需要将代码划分为不同的“信任区域”:

外部边界(必须严格防御)

  • 来源:用户输入、网络 API、磁盘文件、Obj-C 混编代码。
  • 策略零信任。必须进行完整的数据清洗、类型校验和逻辑验证。
  • 目标:确保非法数据在进入核心逻辑之前被拦截。

内部边界(依赖契约)

  • 来源:同一个模块内的私有函数、被强类型约束的组件。
  • 策略基于契约(Design by Contract) 。如果函数的参数已经是 Non-optional 或特定枚举,就不要再重复校验其内容。
  • 目标:通过 Swift 的类型系统(如 structenum)来保证安全性,而不是通过 if 语句。

2. 过度使用防御式编程的副作用

过度校验不仅会让代码变得臃肿,还会带来实实在在的技术债务:

A. 隐藏深层 Bug(静默失败)

如果每个地方都用 if letguard 默默返回而不处理错误,程序虽然不崩溃,但可能在错误的状态下运行。

  • 代价:这让调试变得极其困难。你发现数据错了,但不知道是在哪一步被拦截并赋予了默认值。

B. 维护成本激增

当业务逻辑改变时,过度的断言和校验需要同步更新。

  • 代价:代码中充满了“防御性噪音”,核心业务逻辑被淹没在海量的校验语句中,新成员接手代码时很难理清主流程。

C. 性能损耗

虽然单个 if 的开销可以忽略不计,但在高频循环(如每秒 60 帧的渲染循环)或处理海量数据流时,重复的字符串匹配和正则校验会造成明显的 CPU 损耗。

D. “掩耳盗铃”式的代码

过度依赖 ?? "" 或空实现来避开崩溃,会产生大量僵尸逻辑,导致线上出现用户点击按钮没反应、界面数据不更新却没有任何错误提示的诡异现象。


3. 最佳实践策略

为了平衡这两者,你可以采用以下原则:

维度建议做法避坑指南
错误处理对预期外的错误使用 throwsResult避免使用全局 try? 忽略所有错误
解包优先使用强类型属性,让编译器做功课避免在函数内部重复校验已解包的参数
断言在开发环境使用 assert 捕捉逻辑假设失效不要将 assert 用于处理合法的业务错误(如网络断开)
数据流动在入口处解析(Parse),不要只是校验(Validate)避免将原始 Dictionary 在函数间传来传去

金句Parse, don't validate. (去解析,而不是去校验)。如果能把输入解析为一个强类型的结构体,那么后续所有代码就天然拥有了防御能力,无需重复校验。


下一步建议

你可以尝试在项目中引入 SwiftLint。它有一些规则(如 cyclomatic_complexity)能帮你发现那些因为嵌套过多 guardif 而变得过于复杂的函数,这些通常就是防御过度的信号。