在软件工程领域,"让无效状态不可表示"(Make invalid states unrepresentable)作为一种设计原则,曾被广泛推崇为良好实践的核心。它强调通过严格的类型系统、数据库约束和状态机设计,避免系统中出现任何非法或矛盾的状态,从而提升代码的健壮性和可维护性。表面上看,这样的理念无可厚非,毕竟,避免错误和不一致性,是软件设计的基本目标。然而,现实的软件开发经验却揭示了一个重要事实:过分追求"无效状态不可表示"的理想,可能导致系统灵活性不足,难以应对复杂多变的业务需求和突发状况。本文将深入探讨这背后的原因,剖析何时应该放宽限制,允许一定程度的无效状态存在,并分析这种做法的优势与潜在风险,从而为开发者提供更为全面的设计思考视角。 传统设计理念强调软件代码应该与其领域模型高度一致,尽可能通过类型约束、数据库外键和状态机等手段,阻断任何偏离业务规则的无效状态。
这种做法的确能够带来更简洁清晰的逻辑和更易于推理的系统行为。例如,将状态限制在"草稿""待审""审核通过""发布"等几个固定节点,通过定义明确的状态转换,帮助开发者快速定位和理解状态流转,使系统更具可预测性和安全性。 然而,现实中的业务场景远比理想模型复杂。新增或变更需求时,经常会出现原有状态机无法涵盖的边缘情况。例如,官方内部开发的特殊应用跳过正常审核流程直接发布,关键合作伙伴的应用被误判拒绝,需要绕过正常操作进行快速恢复,甚至在某些情况下需要防止某些状态的反复切换。这些都难以通过简单增加状态节点或动作来优雅支持,否则会导致状态机图变得臃肿复杂,逻辑变得难以维护。
正因为此,许多经验丰富的工程师建议采用更加宽松的设计策略,保留对状态的灵活控制,允许系统暂时进入某些"不规范"的状态,并通过程序逻辑或人工干预来修正。这种灵活设计能够大幅降低系统因严格约束导致的修改成本,提升应对变化的速度和能力。虽然这意味着系统可能会容忍部分无效状态存在,但换来的是适应现实业务需求的能力和更快的迭代节奏。 同样的道理也适用于数据库设计中著名的外键约束。理论上,通过外键约束保证数据完整性,是阻止无效关联和断链的利器。假设帖子记录必须关联一个合法用户ID,外键约束会直接阻止任何指向不存在用户的帖子数据出现。
然而,很多大型互联网公司选择放弃外键约束,转而在应用层进行一致性校验和修正。这是因为随着业务规模扩大和需求复杂化,外键约束不仅带来性能开销,还极大限制了数据迁移、分库分表等架构调整的灵活性。而且面对业务人员对数据的多样化操作需求,例如保留帖子即使关联用户已被删除的情况,硬性约束会导致更多不便和潜在风险。 此外,在序列化和通信协议设计领域,谷歌的Protocol Buffers从初始版本的"必填字段"到后续版本取消该特性的演进,也印证了灵活性优先的理念。将字段设为必填,看似能够保证消息的完整性和合理性,但在大规模分布式系统中,要求所有生产者、消费者和中间处理服务按照严格顺序升级新协议,极易引发系统不稳定和服务中断。相反,将所有字段设为可选,允许部分数据暂时缺失,虽然增加了逻辑处理的复杂度,却极大提升了系统的兼容性和演进的安全性。
可见,软约束和硬约束之间有着不可忽视的权衡。硬约束,比如数据库模式约束和通信协议的必填字段,虽能带来更严格的保证,却也带来了难以回退和调整的高成本。软约束,如程序中简单的校验逻辑,更易于修改和撤销,适合快速迭代和应对不确定性。尤其对于面向用户的应用系统,业务需求往往会出现各种突发情况和例外,完全避免违反领域模型的无效状态是极其困难甚至不现实的。 因此,设计者应当明白,领域模型本质上只是对现实世界事务的一种抽象,而现实中的业务规则常常是模糊、变化且多样化的。软件设计中若将领域模型当作"绝对真理",并试图以不可逆的技术手段严格锁定业务规则,将不可避免地带来技术债务和业务阻力。
更合理的做法是,容许系统支持一定程度的不完整或无效状态存在,通过灵活的代码逻辑、监控告警和人工干预机制,保障系统整体的健康和业务目标达成。 这并非否定所有的约束设计价值,而是强调约束的设计需根据业务上下文灵活权衡。约束应是促进系统合理行为的工具,而非阻碍变革的枷锁。约束得越"硬",其代价就越大,出错后调整也更为困难。软件架构师应兼顾易用性、灵活性及可维护性,谨慎设计那些极难回退的限制。 总的来说,"让无效状态不可表示"作为原则,虽是对软件质量的积极追求,但绝不应成为一条僵化的铁律。
现实世界的复杂多变要求软件设计具备一定的包容度,允许某些无效状态的出现与存在。合理的设计策略是在尽可能保证系统正确性的同时,留有弹性以适应业务的动态变化。只有如此,软件系统才能真正服务于业务需要,在不断演进中保持活力和竞争力。 。