复杂度来源:可扩展性
扩展性是指系统在应对未来需求变化时,能够进行扩展,允许在新需求出现时通过少量修改或不修改代码来支持,而不需要大规模重构或重建系统。
在软件设计领域,面向对象思想和设计模式是解决可扩展性问题的主要工具。设计一个具备良好扩展性的系统有两个关键条件:正确预测变化和合理应对变化。
预测变化
软件系统在发布后可能会经历不断的修改和演进,意味着新的需求会不断涌现。如果能够通过少量甚至不改代码来实现新需求,扩展性就显得尤为重要。然而,预测变化的复杂性主要体现在以下几个方面:
- 不能在每个设计点都考虑扩展性。如果每个点都考虑扩展性,架构设计将变得异常复杂和庞大,难以实现。
- 不能完全不考虑扩展性。如果完全不预测变化,可能系统上线后即面临重构需求,之前投入的工作可能被浪费。
- 所有预测都有可能出错。如果预测的需求迟迟未到或被否定,那么基于预测做的设计将失去价值。
因此,一个经验法则是:仅预测未来2年内的变化,而不是5年或更久的未来。在变化快的行业,预测2年已足够;在变化慢的行业,预测5年和预测2年的结果差异不大,因此大多数情况下,2年法则是适用的。
应对变化
即使预测准确,如何设计系统以应对变化仍然是一个复杂问题。应对变化的常见方案有两个:
1. 提炼出变化层和稳定层
这一方案的核心是将稳定部分封装在独立的稳定层,将易变部分封装在变化层(或适配层),通过变化层隔离变化。
无论是变化层依赖稳定层还是稳定层依赖变化层,都可以根据具体业务需求进行设计。例如,系统需要支持XML、JSON、ProtocolBuffer三种接入方式时,架构可以如图所示(形式1);
如果系统需要支持MySQL、Oracle、DB2数据库存储,架构则可以采用形式2。
关键挑战在于如何进行分层拆分以及各层接口设计:
- 明确变化层和稳定层的拆分依据:需要清楚哪些属于变化层,哪些属于稳定层。
- 设计变化层和稳定层之间的接口:稳定层的接口应尽可能保持稳定,而变化层在多种实现方式中需要找出共同点,确保新增功能时对现有接口的影响最小。
2. 提炼出抽象层和实现层
这一方案通过提炼出抽象层和实现层,使抽象层的接口保持稳定,并基于此实现统一的处理规则。实现层可以根据具体需求开发不同的实现细节,因此,当新需求出现时,只需修改实现层,新增实现细节,无需修改抽象层。典型的实践包括设计模式和规则引擎。
总结
设计系统时,需要在过度设计与不可扩展之间找到平衡。长期预测代价高且不确定性大,可能项目还未落地,业务就已改变;但完全不预测,又可能刚上线就面临无法支持新需求的困境。2年预测法则是基于这种权衡的经验值。如果业务在2年内发展良好,架构升级的资源和投入就会有保障;若未能持续发展,则长期预测的意义不大。
在具体方案设计时,可以考虑短期、中期、长期策略的结合。短期策略侧重快速实现,修改少,见效快;长期策略则注重远期扩展性,但成本较高且可能预测不准。如果决定采用短期策略,需要提前评估未来变化的发生可能性,并考虑系统演化为长期策略的代价,确保必要时能够顺利切换。
此外,重构应当采用小步快跑的方式,逐步隔离变化层,而不是一开始就过度设计与抽象,应在业务迭代过程中逐步演进。
- 感谢你赐予我前进的力量