为维护而设计:架构设计的首要原则

2021-11-09 15:56:20 浏览数 (1)

软件开发总成本 = 开发成本 维护成本;软件维护成本 = 理解成本 修改成本 测试成本 部署成本。—— Ken Beck

在设计框架、系统架构时,可扩展性是人们想追求的特征之一。从技术社区的文章上,我们可以看到大量的相关字典,诸如于“通过配置和定义进行可扩展”,又或者是“业务流程”的可扩展,还有各类的“插件 ”以及“可扩展的点” 等等的话术。

真是呢,从我们所经历的大部分项目来说,这些系统的可扩展性并没有真的那么好。有些,可能是在设计系统时,它可以满足于当前的需要,不适合未来的场景 —— 这是另外一个故事了。有此,则是架构师在设计系统的时候,缺乏对于边界的限定的考虑。对于边界限定的另外一个解释就是,系统实现了开发时的可扩展性,但是忽视了维度时期带来的问题。而软件的开发周期中,维护成本往往占据了成本的主要部分,如开头 Ken Beck 所说。

这时就不禁让人又开始思考起来,没有为维护设计的系统,真的是可扩展的吗?

架构真的是可扩展?

在诸多系统里,开发时正如它的设计那样,具备着良好的可扩展性。只是在运行和维护时,它的可扩展性可能会失去效应。

一个 DDD 系统腐化的传闻

这是一个未严格经验证的 DDD 传闻。

在《领域驱动设计:软件核心复杂性应对之道》一书中,Eric Evans 根据在项目上的重构经验,给出了领域驱动设计的系统化方法,并融合了一系列的领域特定相关的实践。我们相信 Eric 在重新构筑这个系统的架构时,经过了一系列的良好设计。而到了十几年后,这个系统的架构已经变成大泥球般,难以看出当初的精心设计。人员的流动,知识传承的流失,使得系统一步步走向腐化 —— 这几乎是大部分系统的共同问题。

OSGi 模块化的回归测试

OSGi 是一个颇为有趣的模块化、插件化方案,Eclipse 是最具备知名度的一应用场景。在可扩展性上,OSGi 有非常多的优点:诸如于动态加载、更新和卸载模块而不用停止服务,可以实现系统的模块化、版本化。

在微服务架构流行之前,它的插件化能力对于大型系统来说非常有吸引力。发布部分新功能时,我们不需要发布整个系统,只需要发布其中的一个 bundle 包(插件)。

只是基于 OSGi 构建的 Web 应用会变成一个可怕的单体,每个 bundle 可能会被构建成“微服务”,bundle 之间存在相互调用 —— 在微内核架构里,我们不允许这样的存在。如此一来,一旦我们更新 bundle 时,会倾向于发布整个系统,而不是单个的 bundle 包。

在这时,我们依旧需要对整个系统进行回归测试。

低代码生成的遗留代码

这是的“遗留代码“ 是指难以测试的低代码平台。这里的低代码平台是指通用的低代码平台。

在那篇无代码编程 ,DSL 被视为核心要素,一个经过精心设计的领域特定语言/类编程语言(非 JSON DSL)。基于 DSL 设计,能为系统提供良好的可测试性和持续集成能力,这一点是普通 JSON 所不具备的。而一旦,我们生成的低代码是不可测试的,那么它就可能变成遗留代码 —— 它取决于平台所构建的自动化测试机制,以及自动化版本迁移的设计。

“复杂度同力一样不会消失,也不会凭空产生,它总是从一个物体转移到另一个物体或一种形式转为另一种形式。”

如果框架本身不考虑可测试和质量的问题,那么总有人得重新去考虑它们。

灵活性的另外一面

过去,PL/SQL 是我见过最灵活的系统,“人们可以不写代码,就搞定一切”。我们可以将其看作一种 DSL,它和我们遇见过的配置化系统是类似的,只需要简单地 “配置”,就可以快速地实现。

这种灵活的“配置”,非常有意思。我们可以在测试环境里,通过人工的方式,反复地、有预见性地对测试它们。在我们追求自动化和稳定性的今天,这种不稳定性变得异常的可怕。在移动端,消息推送是通过配置化和接口的形式进行的,我们经常可以接收到测试人员向生产环境发送消息推送。

从意义来看,这种灵活性更多地应该被视为补救措施,而非系统设计的核心部分。

反思

软件开发是一项团队活动。

无节制的甜头蔓延

越是在大型系统中,在破窗效应愈加的明显 —— 一旦有一个人没有按照规范来实施,那么将会有越来越多的人违反了规范。这个问题起源于,相关的规范没有通过流程或者工具有固化。诸如于,没有严格的代码检视流程,缺乏自动化的架构守护工具。

诸如于为了单一团队的原因,临时性修改了底层库的接口,开了一道的口子。导致了后续其它团队,会要求新的口子,导致底层的库偏离了原先的设计。因此,在添加新的临时性接口之前,考虑一下系统迁移和演进时会遇到的问题。

我们允许这种临时性地方案存在(紧急地上线总是会存在的),更应当在发生之后,重新设计和填补相关的问题。

自动化保障的缺乏

在不考虑调试的情况下,对于配置化等具备灵活性的系统,它们实现功能的难以自动化测试,也无法进行持续集成与持续部署。对于中大型的软件系统来说,它将会成为维护(开发 运维)工作的一个恶梦。

特别是,一旦配置化的系统缺乏版本化管理机制,将会对软件的回滚造成新的挑战。

软件开发所需求的一系列因素,都应该在可扩展 灵活性的情况下,再次进行相关的分析。

本地测试环境的缺失

在遇到一些复杂的 bug,不论是 Serverless ,还是声明式、配置化的方式,都需要与其它开发系统进行联调。我们需要构建一个本地的环境,以快速复现出 bug,才能进行修复。基于现有的平台场景之下,需要在云端进行调度与测试。

不过,这种混合高度的模式在《云端开发时》流行之后,会有所改观。

面向维护构建有序

在最近编写的《 Architecture 3.0》时,我和我的同事 @NoaLand 一直在讨论架构的有序性,以期待构建有序性模型解决部分维护问题。在现今的这个场景之下,除了解决上述的问题,可能还需要如下的几个方面。

正视问题的复杂性

如你所见,问题本身是复杂的,只有正视它,能会真正带来突破。

周期性的传达设计思想

如我们所知,软件开发团队成员的流动性会影响系统的稳定性。每隔几年内,团队的成员便会刷新一遍 —— 诸如于在互联网行业,三年便是个老员工。系统的相关知识会随着人员的流动,而被遗忘在某个角落里,新进来的团队成员不懂得系统原先的设计。

在我们尝试过的诸多方法中,一种颇为有效的方式是:在新员工进来一段时间后(如三个月),让他们讲述一下系统的架构。过程中,纠正他们对于系统的一些认知偏差。

适时解决技术债务

在实施的过程,需要持续地为技术债务腾出时间 —— 一个老生常谈的问题。在一系列的解决方式里,持续更新依赖是一个非常简单而有效的策略,与详细可以见《管理依赖的 11 个策略》。

当然,还有其它的技术债务了。

其它

To be continue...

0 人点赞