二、错误处理哲学:防御性编程与契约式设计、错误处理的黄金法则、金融场景下的零容忍原则

1. 防御性编程:别信任何人,包括你自己

防御性编程,说白了就是「把每个调用者都当成潜在破坏者」。我做了十几年金融系统,见过太多「不可能发生」的错误——空指针、越界、格式错乱,它们总在最不该出现的时候冒出来。

核心思想很简单:不要假设输入是合法的。哪怕是你自己写的函数,也别信。

防御性编程三原则:
  • 检查所有外部输入(网络报文、文件、用户输入)
  • 验证函数参数的有效性(尤其是边界值)
  • 对任何「不可能」的分支也要处理(比如 switch 的 default)

举个例子,解析一个金融协议报文时,我习惯这样写:

// 防御性解析:不信任任何字段
public class FIXMessageParser {
    public FIXMessage parse(String rawMessage) {
        if (rawMessage == null || rawMessage.isEmpty()) {
            throw new IllegalArgumentException("原始报文不能为空");
        }
        if (rawMessage.length() < MIN_HEADER_LENGTH) {
            throw new MalformedMessageException("报文头长度不足");
        }
        // 检查分隔符位置
        int delimIndex = rawMessage.indexOf(SOH);
        if (delimIndex == -1) {
            throw new MalformedMessageException("缺少SOH分隔符");
        }
        // ... 继续解析
    }
}

我在项目中遇到过一个问题:某个交易系统在凌晨三点崩溃,原因是一个字段长度刚好等于缓冲区大小,导致字符串没有以 '\0' 结尾。嗯,从那以后,我对边界值检查格外敏感。

2. 契约式设计:先谈好规矩,再干活

防御性编程是「我不信你」,契约式设计是「咱们先签合同」。Bertrand Meyer 提出的这个概念,在金融协议解析中特别实用。

契约包含三部分:

契约类型 含义 金融场景示例
前置条件 调用方必须满足的条件 报文长度必须 ≥ 20 字节
后置条件 函数执行后保证的结果 解析后的交易金额必须为正数
不变式 对象内部始终成立的条件 订单状态机只能按合法路径转换

我个人习惯在接口文档里明确写出这些契约。比如:

/**
 * 解析ISO8583报文
 * 
 * @前置条件: rawData != null, rawData.length >= 16
 * @后置条件: 返回的Message对象中,mti字段为4位数字字符串
 * @不变式: 解析过程中不修改原始数据
 */
public Message parseISO8583(byte[] rawData) { ... }

你想想看,如果每个接口都这样写清楚,团队协作时能少多少扯皮?

我的建议:防御性编程和契约式设计不是二选一。我通常的做法是——对外部接口用防御性编程(因为你不确定调用方是谁),对内部模块用契约式设计(因为可以控制代码质量)。

3. 错误处理的黄金法则

做了这么多年,我总结出四条黄金法则。它们听起来简单,但真正做到很难。

  1. 尽早失败(Fail Fast):发现错误立刻抛出,不要等到后面才暴露。我记得有一次,一个同事把错误数据传了五层函数才报错,排查花了两天。
  2. 不要吞没异常:catch 了异常什么都不做,这是最坑人的做法。我见过一个系统,catch 块里只写了注释 // TODO,结果线上跑了半年没人发现。
  3. 保持错误信息可追溯:异常信息要包含上下文。比如「解析字段Tag 48失败,原始数据偏移量1024」,而不是「解析失败」。
  4. 区分业务异常和系统异常:业务异常(如余额不足)需要返回给客户端;系统异常(如数据库连接失败)应该记录日志并告警。
避坑指南:我曾经接手过一个项目,所有异常都被捕获后转成通用错误码「-1」。结果生产环境出了问题,根本不知道是哪个环节报的错。从那以后,我要求团队必须保留原始异常链。

4. 金融场景下的零容忍原则

金融系统对错误的容忍度是零。为什么?因为一个解析错误可能导致:

  • 交易金额多一个零或少一个零
  • 订单方向搞反(买变成卖)
  • 清算数据对不上账

这些后果,轻则赔钱,重则被监管处罚。所以金融协议解析必须做到:

原则 具体做法
校验全覆盖 每个字段都要做格式、范围、业务规则校验
拒绝模糊数据 任何不确定的字段值,直接拒绝,不猜测
审计日志 每次解析失败都要记录完整上下文,便于事后追溯
灰度验证 新解析逻辑先在灰度环境跑一周,对比老逻辑结果

说白了,金融系统里没有「差不多」这个词。一个字段解析错了,可能就是几百万的损失。我见过最惨的一次,因为日期格式解析错误,导致某基金公司的净值计算全部偏移了一天,最后花了三周才把账调平。

零容忍的核心:宁可拒绝一笔合法交易,也绝不接受一笔非法交易。这就是金融系统的底线。

5. 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以看到,防御性编程和契约式设计是基础,黄金法则是操作指南,零容忍原则是顶层约束。

错误处理哲学知识体系 金融场景零容忍原则 错误处理黄金法则 尽早失败 | 不吞异常 | 可追溯 | 区分业务/系统异常 防御性编程 不信任任何输入 检查所有边界条件 契约式设计 前置条件 + 后置条件 类不变式 实践落地:校验全覆盖 → 拒绝模糊 → 审计日志 → 灰度验证

这张图我建议你保存下来。每次做协议解析设计时,对照着看看——底层方法论用了没?黄金法则遵守了没?零容忍原则有没有打折扣?

一个小技巧:我在团队里推行「错误处理 checklist」,每次代码评审时逐条检查。刚开始大家觉得麻烦,后来发现线上故障减少了 70%。习惯成自然嘛。

公众号:蓝海资料掘金营,微信deep3321