架构设计参考项目系列主题:千万级规模遗留系统债务度量改造实践

本文转自:InfoQ

目录

架构债务与代码债务

债务度量及偿还实践

总    结


今天跟大家分享的内容是架构债务与代码债务、债务度量及偿还实践相关的内容。

架构债务与代码债务

架构债务和代码债务的度量是什么?请思考一下这个问题:你做了一个架构,一个设计,这个设计如何成年呢?罗马不是一天建成的,架构亦不可能一天设计成,未来 10 年它不可能保持原貌不变,架构一定是不断演进的。想让架构活到成年,就要让架构不断地演进。比如从单体到云化,从云化到 Cloud Native,然后到容器化到 function,再到 function 加 data。

架构如何持续演进

首先,我认为架构演进的关键在于架构师一定要把自己的架构意图真正地在团队中落地。落地说起来非常简单,但我认为世界上最远的距离就是知道和落地,就像敏捷非常好但就是落不了地一样。落地考察的是你的硬功夫,比如个人的魅力。你要在团队中有非常大的魅力,同时也要有一群懂这个且跟你一样有追求的人才可以共同把这件事情做成。

架构意图如何去落地呢?意图不是口号,不是喊喊口号写一些 slogan,画一个 logo,让大家去读、去看就能落地。我们的架构意图选用了 BDD+DSL 的方式,刚才我讲了 BDD(Behavior-Driven Development),那么什么样的 Behavior 是我们需要的 Behavior 呢?

比如我需要架构符合 Clean Architecture,你不能给出一张图跟大家说这个架构就这么做,有的人会有的人不会,结果做得千奇百怪。通过 DSL 的方式可以定义一些类名、包名,你要把这些包名传到 function 里面,我再来计算你传过来的这些包是否符合 Clean Architecture 的定义。

为了简便,不能要求每个团队都去做这个验证,所以我们采用 BDD 框架。虽然这是一个用在测试上的工具,但用在描述架构意图上也非常棒。架构师希望包间无循环依赖, 如果出现循环依赖,开发构建时会出现一个循环依赖的错误提示,从而尽早发现问题。如需要满足 Clean Architecture,只需要配置那些包对应整洁架构中的 infrastructure、domain、application、adapter 就行了。

第二是架构定义,大家一定要对架构定义有一个清晰的认识。可以选择业界的实践,也可以做自己的架构定义。目前,我认为架构的定义是团队的整个架构将来会演进成什么样的标准。例如服务可以通过一个模板生成出来,生成出来就是这些结构。程序员、工程师可以往这些结构里填自己想要的东西。同时通过 DSL 的方式去做架构检查,确保不会把代码写错。

第三是架构看护,在整个过程中通过重构来落地。重构是需要投资的,投资代表自上而下的认可。因为没有一件事可以一次做好,否则重构成为了雷锋行为,加之本身又存在很大风险,出力不讨好就丧失了重构的动机。追求商业价值对于一个企业来说是正常的,投资重构代表企业承认这些在过程中产生的技术债务,并愿意主动去消减,从而让系统持续演进。架构或代码重构需要描述清楚价值和收益以及验收方式,定期去审查重构的目标有没有达成。在审查过程中确实会花费一定的人力物力,但通过这种检查方式可以确保大家把重构的目标达成。

架构该有的样子

由于信息安全,我只能把 Clean Architecture 官网上的图截出来一起探讨。我认为不管服务面向云原生还是面向于公司内部平台都会依赖基础设施层。

第四是建模 entity 模型层次。如果按照这样的模型去做程序更容易编写测试,因为所有的架构都已经做了解耦,内层都是不依赖外层的,内层可以完全去解耦做测试,只需要你简单地去做一个 mork 即可。其次,架构不依赖特定的框架和基础设施。比如今天我们要依赖阿里云,阿里云有整套的基础设施,明天我们要迁移到 AWS 上,理论上我们只需要把最外层的基础设施进行一层适配就可以了,架构设计中需要的是消息中间件,业务代码中不应该感知到具体是 Kafka 还是 RabbitMQ。所以我始终认为架构会沿着一个特定的方式去演进,在过程中需要不断进行检查。

为什么不能一次把代码写好?

技术债务

曾有人问我:“你为什么不能一次把代码写好?为什么写完了还需要重构?重构完了第二年又要重构?如此反复是不是在浪费公司的钱?”那么为什么不能一次性把架构或代码做好呢?

关于技术债务我有一个定义,也许不是特别准确,姑且看之。我认为在编码过程中写下这行代码的一刹那体现了工程师对技术业务和环境的认知,当业务和环境发生变化时债务就产生了。另外,当你自身能力提升以后再回头看以前写的代码,会发现曾经的自己是多么稚嫩。

因此技术债务分为两种,一种是有意的,一种是无意的。

有意的债务

有意的债务是为了满足快速交付的压力匆匆地发布,缺乏思考,这在企业中并不常见,一般情况下企业会给工程师充足的时间去交付。但对于高速迭代的产品来说,出于商业价值的考虑,我们会向版本妥协去做业务压力的 compromise。因此,快速交付之后往往留下许多技术债务。

如果没有管道让这些债务流走,它们会一直留在系统中。久而久之,当债务积重难返,架构师 Tech Lead 往往会主张用新技术把系统重写。做过架构师的人应该知道,程序员更喜欢重写一遍。

无意的债务

另外一种是无意的债务,写的时候自然用自己认为最好的方式去写,如果再学到更好的方式,那么在无意中就留下了技术债务。对于这种无意的债务,需要工程师挖个小渠让债务顺利流走。我们鼓励工程师乐于重构,善于重构,我不认为重构是雷锋行为,做了这件事就要得到相应的好处。有些人总是在检视中发现别人的问题,总是能给别人有建设性的建议,那么这些人就是我们的 Tech Lead。这也是我们软文化的一部分。

不起眼的债务

不起眼的债务

最后,有些债务很不起眼,比如在做全量版本集成的过程中发现集成总出现问题,而单独做时却非常顺利。我们曾做过许多实践,如契约测试,可契约测试对开发人员的要求很高,契约是一种沟通行为,需要事先去敲定契约,然而前期做详细设计时一般很难把契约定准确。

虽然我们是 Consumer Driven Contracts,但 Consumer 往往不知道自己想要什么,前后端的还好,website 知道想要什么样的接口,可以先定出来放着。如果是 service to service 的结构,尤其是异步的 message 结构很难事先确定需求。

集成过程中出现问题导致版本延期在版本层面是不可接受的。那么怎样才能知道在开发过程中工程能力提升了呢?想要提升工程能力就需要把后端的事情放到前端去做,只要放到足够前端,团队的工程实践能力就越强。比如以前在版本集成时才能发现的问题现在在开发时就能发现,此时就意味着团队的工程能力提升了。

那么如何去做呢?怎样在开发过程中就发现代码是有接口不兼容的,是有集成风险的?今年我们落地了谷歌的一个叫 Bazel 的集成工具。这个工具对一般的小公司不太友好,因为需要强大的基础设施支持。我们整个代码 2000 万在一个单体仓里面去运行,本身就是对基础设施的挑战。一旦达成,如果每天提交次数是 2 万次,程序员会提交 2 万次 push,那么每一次 push 都可以跑一次全量构建。那么兼容性问题就可以完全解决。

大家可能会说每提交一次去跑一次构建还是在后端,虽然不是 release 的时候,但是仍然需要在后端验证。通过 Bazel 可以直接依赖源代码而不是二进制,在本地去下一个服务时,会把这个服务以及它所依赖的所有组件及服务全部下到本地。提交代码时需要 pull 一下然后再去 push,所以在本地开发过程中就可以识别到向下依赖的所有的组件是不是有不兼容修改。

那么向上依赖怎么做?改了代码怎样知道有没有影响到别人呢?只能靠单体仓去做。在全世界范围内,Netflix、Facebook、谷歌这种有非常强的基础设施支持的大厂才会选择单体仓。而小团队往往会选择逻辑单体仓,这是一种简单的、变种的实现方式,也是非常好的方案。

在集成方面,一般会交给 DevOps 去做,实际上屡禁不止的主要是因为这已经不是 CI/CD 可以搞定的事情了,这个是一个有挑战性的技术栈。所以搭建 Monorepo 以后,从每次 release 做一次构建,到每天频繁地做构建。

债务度量及偿还实践

接下来是债务度量和偿还实践。

识别技术债务

识别技术债务的逻辑

说到技术债务,怎样去识别团队中到底有哪些债务呢?

比如用 Clean Code 里面的工具进行识别,可它只能识别烂代码,无法识别好代码。好代码无规则,烂代码才有规则。识别烂代码时,Clean Code 这些检查工具(静态代码检查工具)不如程序员 smart。程序员很 wisdom,他们总能想到一些办法可以绕过这些检查,甚至做一些屏蔽的操作,因此通过 Clean Code 很难把技术债务识别出来。

即使识别出来,如何让程序员不做绕过?不做 wisdom?这需要团队的强执行力,比如在检视中一定要看静态代码检查的规范或 Bad Smell 是怎么改的才可以。Clean Code 改得再好,我也不认为这个代码就是好代码。什么样的代码是好代码?我认为能够通过团队中所有人认可的代码就是最好的代码,这是你能写出来最好的代码了。

技术债务沟通

如何让每一行代码都经过团队所有人的认可?代码检视很重要。

技术债务沟通

技术债务不能只有架构师或总工知晓,为了实现高效债务沟通,需要做债务看板。有一个很好用的工具叫 SonarQube,SonarQube 以服务为粒度,可以扫出来服务中的 Bugs——漏洞。它可以把债务变成时间,直接告诉你这个团队的服务中有多长时间的债务。

债务时间的算法可以定制,可以把环境因素,人力因素,工作时间当做 delta。比如团队每天可以投入重构的时间是 2 小时,据此统计出消减完所有债务预估要多少天。如果每天投入 4 小时,就得到另外一个值。通过适合团队的算法统计出来债务的时间,从而更容易在迭代之初给团队下发重构的需求管道。

在统计债务的过程中发现一些很有价值的信息,比如代码规模的统计。团队的规模增长是否和需求的增长成正比?如果今年没做需求代码规模还增长了,那一定是有问题的。如果今年代码规模降低了,是通过什么方式把代码降低的呢?一般来说,如果重构没有管道,代码规模很难降低。有了管道以后降代码就成为了一个 KPI 了,这时你就会发现那些已经过期的 API、过期的代码就会有人有动力进行代码的清理。

当代码规模保持在较小量级或者每年只是缓慢增长,此时你有很多事情可以去联想。比如今年我们在做一些新的功能性需求时复用了老的功能,所以并没有去增加太多的代码,这样可以去牵引大家更倾向于使用公共的能力、公共的技术,而不总是重复地造轮子。

接下来我想聊一聊公共组件的使用率。相信每一个团队都会有基础设施层,这个设施层基本上是团队中最厉害、最牛的人写出来的一些公共的组件。与其自己造轮子,不如去用这样的组件来做事情,比如说分布式的锁、分布式的调度、一些驱动程序,这些都属于公共组件。我们可以通过一个看板来统计团队公共组件的使用率,我认为使用率越高的团队的代码债务是越低的。

另外一个有价值的信息是重构防护网的建设,要进行债务偿还的时候一定要考虑到重构的防护网,如果没有测试的话,一般这个代码是不能够被重构的。那么如何确保重构是有防护网的?以前喊口号时发现喊了两年防护网也没有建出来,一旦给大家拨了管道让大家去建设防护网,我们发现用了两年的时间防护网基本上已经建设起来了。我们之前比较缺乏前端防护网,在两年过程中我们不仅前端补齐了 UT,还有一些基于 CLearning Net Watch 的自动化测试,前端补齐了契约测试,还有一些 snapshot test 等等。

一旦进行了投资,我们的重构防护网都会建设出来,一旦你的重构防护网建设完毕,从逻辑上来讲以后再进行重构基本上不会发生什么大的问题。但是如果没有防护网让大家去做重构,过程中就很容易出现问题——重构了一个模块导致 N 个模块发生了告警。

在整个债务的偿还过程中我们基本上结合了自上而下和自下而上的方式去做规划。首先是自上而下的方式,通过架构师、committer 去识别哪些东西是可以提出来当组件的,哪一些架构是可以优化的。另外一种是自下而上的方式。把管道以及全部投资下放到团队中,每一个人都可以去领一个令牌去做一个重构,经过 committer 的审核就可以去做这个重构。

通过这种方式重构的人不再是雷锋,所有重构的人都会有一个真真正正的需求管道存在,通过这种方式每个季度可以发现团队中谁善于重构,谁重构得好,谁做的重构收益最大,从而去度量整个重构的过程。

避免产生技术债务

避免产生技术债务

刚才讲的是统计架构债务的一种方式,其中使用了 Fitness Function 的概念。大家可以看到图中左边定义了软件的四个层。通过四个层的定义,只要你把它指定到你的某一个包名下,就会自动地去帮你去 check 包之间的依赖关系是不是符合 Clean Architecture 的定义。不同的角色会有不同的期望,把所有的期望都放到 BDD 的框架里面,通过 BDD 的框架来书写期望的架构或者代码。

我们在后端(cucumber 的框架)通过编码实现的方式可以把这句话(除了这个参数)在后台用一个 function 把它实现起来。这样做的好处是所有团队在落地过程中都可以去选择他们期望做的架构检查,然后去落地。

我们有一个 dashboard 系统,哪个团队选择了哪些架构检查要素?他们的检查要素的结果是什么?通过看板全部可以展示出来。通过这种方式我就可以知道某一个团队他们落了哪些公共组件,这个团队的架构是不是符合 Clean Architecture,包间是不是有循环依赖,是不是有 SQL 注入的风险。

通过一次开发可以让所有的团队享受到架构和代码的检查,而且架构和代码检查是在 UT 中承载的,也就是说在编码过程中就可以发现是不是把包之间的依赖写反了?是不是有注入的风险?同样在 CI 的层面还会再进行一次检测。

这样就可以把整个团队的架构定义和对 Clean Code 的定义全部在看板里面体现出来,并且集成到开发的流程中可以尽早发现问题。

总    结

总结

任何一件事团队能否做成完全取决于你受否真正有信心去做这件事,受否掌握了其中精髓。通过一些手段可以识别团队的技术债务、架构债务,识别出来以后一定要通过适合的优秀实践及工具去进行落地。债务的偿还工作是一个体面的工作,而不是一种雷锋行为,如果是雷锋行为,就没有人愿意去做了。

最后我想说,如果有一天你的 Tech Lead 或者团队中的成员过来跟你说,对不起,这个服务 / 模块 / 架构我们要重写了,这时就意味着团队的技术已经破产,已经还不清债务要申请破产了,要破产重组了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值