查看原文
其他

重点项目却总是腐化,程序员为什么会写烂代码?

李子昂 凌云时刻 2022-05-30

凌云时刻

写在前面:作者李子昂,阿里巴巴集团研发效能部的第一个算法工程师,目前工作主要方向是代码管理和CI。本文探讨的是:从优化研发交付流程的角度,如何根本上提升研发效能。

先说结论

现在阿里主流的分支开发模式,以及研发工具中流水线的设计,导致CI和CD流程是捆绑在一起的,生产环节发布成功,自动将分支合入master,也就是说完成一次CD的同时,才进行一次CI。

这么做也有这么做的好处,你可以保证master上的版本永远和线上代码版本是保持一致的。但是这么做却大大降低了CI的频次,频次越低,体量也就越庞大,单次CI几千行的体量,在阿里内部,倒逼着衍生出了强大的全量回归测试工具,甚至在Code Review中按照依赖链路进行分拆评审的“黑科技”。

根据混沌理论,改变足够小的时候,结果是可以预测的。我发布一个两行代码的改动,一个单侧就是妥妥覆盖。但是当很多个这样的改动叠加在一起时,结果就变得越来越不可知了,连锁反应可能会造成意料之外的故障。这样一个CI上线的过程中,很多时候测试同学将它当成一个黑盒来测试,全量的去检测所有对外提供的接口,导致测试流程繁重,一旦出现问题,折返路径也相当漫长。

众所周知,Google是使用大库模式和主干开发的,每一次修改都会很快地合并到master中。高频的CI和直接合入master的高风险,对CI的质量提出了更高的要求,所以诞生了Google的强制CodeReview和重视代码质量的工程师文化。当然这也和Google只要使用C++和Go,和语言特性有关,所以大库+主干在java中的实践也是未来可能会面对的挑战。

怎么把CI和CD解耦开呢?以制品为分界,解绑CI/CD,释放CI灵活性。在CI流水线的终点,可以把代码的编译产物——制品,存放到制品仓库中。而CD流水线直接选择合适的制品,进行发布。

在一些自动化测试,扫描和代码分析的服务的辅助下,配合CodeReview,用高频高质量的CI提高代码质量,代码要被review,开发对自己的作品自然会更加有羞耻心和owner意识。用这种研发交付流程的改变去降低代码腐化,鼓励重构。

这可能是从研发模式上为系统腐化踩刹车的办法。

研发效能现状

其实大部分团队的研发效能并没有大多数同学想的那么糟糕。尽管需求交付速度上来看并不快,但只关注单指标,难免管中窥豹。

人类是唯一可以长时间奔跑的动物。人的身体和耐力非常适合跑步,但是就算是最顶尖的运动员,随着跑步距离的增加,平均时速也会慢慢下降。这种现象,叫做“折中定律”,提升一方面,就要牺牲另一方面

如果有人让你跑得再快些,你可以答应下来,只要不需要跑很长的距离就好。或者,如果需要长距离选手,那你也可以跑得更远,只要愿意跑慢一些。但你不大可能找到一个既能跑得更快又能跑得更远的人,也没法让一个长跑运动员跑得很快。
同理,我们不难理解,随着软件复杂度的上升,交付速度的下降也符合折中定律,是自然而然的。

我称之为:“码烂故我慢定律”。

从另一个角度使用折中定律,也很合理,如果你让我今天就以速度为第一优先,放弃一些代码质量去奔跑,长期来看对平均时速反而是有损害的。
所以,导致研发速度下降的最主要原因,是软件复杂度的膨胀。谷朴谷子哥有说过:“每新增一行代码对软件系统来说都是负担”。那么如何控制系统复杂度呢?

 代码质量腐化是效能最大的敌人--《Clean Code》书摘

有些团队在项目初期进展迅速,但一两年之后就慢如蜗行,进度缓慢程度严重。对代码的每次修改都影响到其他两三处代码。修改无小事,每次添加或修改代码,都得对那堆扭纹柴了然于心,这样才能往上扔更多扭纹柴。这团乱麻越来越大,再也无法清理,最后束手无策……这就是混乱的代价:

  • 随着混乱的增加,团队生产力持续下降,趋向于零。

  • 花时间保持代码整洁不但有关效率,还有关生存。

  • 制造混乱无助于赶上期限。混乱只会立刻拖慢你,叫你错过期限。赶上期限的唯一方法——做得快的唯一方法 ——就是始终尽可能保持代码整洁。

  • 写整洁代码,需要遵循大量的小技巧,贯彻刻苦习得的“整洁感”。这种“代码感”就是关键所在。

  • 简言之,编写整洁代码的程序员就像是艺术家,他能用一系列变换把一块白板变作由优雅代码构成的系统

随着混乱的增加,团队生产力持续下降,趋向于零。员工工作成就感也随之降低,离职率上升,让生产力进一步下降。当生产力下降时,管理层就只有一件事可做了:增加更多的人手到项目中,期望提升生产力。

可是新人并不熟悉系统的设计,他们搞不清楚什么样的设计符合设计意图,什么样的修改违背设计意图。而且他们以及团队中的其他人都背负着提升生产力的可怕压力。于是,他们制造更多的混乱,驱动生产力向零那段不断下降。

这些代码是谁写的呢?开发者水平太差么?

思考问题使很多人习惯于找一个“替罪羊”,但如果这个现象是通病,这很可能是系统的问题,写代码也只是和你一样可能因为对系统不熟悉,或者工期太赶,以快速满足需求为kpi的程序员。

系统复杂度的无限熵增,让代码改动变得困难,系统迭代变得缓慢,开发成就感降低,负向飞轮一旦转起来就向无尽的加班和越来越低的效率狂奔。

系统本身的复杂性是无法避免的,代码本身需要和现实世界交互,而现实世界本身就是复杂的。但复杂的代码也可以是简洁的代码,好的代码质量可以大大降低系统的腐化。

换句话说,烂代码是研发效能最大的敌人

 怎么做?提高CI质量,形成正向优化的飞轮

对于每一位程序员来说,每一次更改代码,都让代码库比你修改前更整洁。

这句话的思想类似于,勿以善小而不为,勿以恶小而为之。我相信大部分程序员都是有操守的,也愿意护卫代码的整洁。但是如何让这样的努力被看到,让好的代码被看到,让优秀的抽象被认可。

如何形成重视质量的文化呢?让一线同学认可,让管理者看见一线同学为维护质量作出的努力。

来看看Google的答卷。大库开发+主干开发模式。大库开发,指所有的代码放在同一个代码库中。主干开发,指开发不能拉分支,只能尽快将自己的修改推送到master分支上。这样的风险显而易见,一旦一个开发把带有Bug的修改push到master上,所有人rebase的代码都是有Bug的,所以对提交代码的代码质量提出了极高的要求。

Google通过CodeReview文化和强力的CI自动化测试工具支撑了这样的质量体系。每个团队还会有专门的组,一旦有问题的代码被push到了master上,要像处理故障一样回滚和复盘,优化自己的CI质量监控体系。

仔细想想,这样一套系统自有他的合理性,主干模式开发让代码冲突的问题从低频大问题,变成了高频小问题。这样一次需要CodeReview的代码量也不会过多。同时也避免了代码重复的问题,一是大库代码对大家都可见,二是主干模式更新及时避免了出现并行开发雷同代码的情况。

对于研发平台方和业务一号位来说,如何设计出这样的研发流程,和能支撑这样研发模式的研发工具,是形成飞轮的关键。

 解绑CICD,释放CI能力

对于平台方来说:

  1. 提供CI流水线,类似gitlab-runner能力,为研发托管测试devops。让CI与CD解绑,释放CI的频率和能力,通过CodeReview机制,辅以扫描和自动化测试,支撑团队建立适合自己的CI流水线,提升代码质量。

  2. 提供测试托管能力,提供测试集群auto-pilot能力,为CI流水线自动运行测试任务,通过任务托管优化测试资源利用效率。

对于业务团队来说:

找到适合自己的CI研发模式,建立质量保证体系。燎原哥的文章对这一块有不错的讨论,我对团队管理经验完全没有,就不展开了。

 Google的最佳实践-大库研发模式和主干研发模式

并不是说Google的就是好的。但是一个自洽的生态必然是有他的合理性的,就像分支研发模式也有自己的优势,在java场景中,起码在阿里的环境里,分支模式还是有自身的优势的。他山之石,可以攻玉。

特性分支开发模式

特性分支开发模式是指为一个或多个特定的需求或者缺陷创建代码分支branch,在其上完成相应的开发后,把它合并(merge)到主干 / 集成分支的开发模式。通常这种分支生命期会持续从几天到几周不等的一段时间。

优点:

  • 分支互相隔离,可以在宽松的时间内完成对自己分支的测试再合入主干;

  • 冲突合并次数少,只需要在合并主干的时候进行一次合并;

缺点:

  • 分支管理复杂:原因在于大量采用代码分支,且来源分支和合入目标分支各异,操作复杂

  • 合并冲突多、解决难:分支生命期越长,意味着与主干的代码差异越大,冲突概率越高,冲突的解决难度越大,不论哪种开发模式,其实解决的冲突总数是一定的,分支管理减少了解决冲突的次数,自然需要解决的冲突也就越复杂;

  • 多测试环境可能会被不同分支抢占,各个分支互相隔离所以代码的测试也需要分开,会对相应的测试环境有抢占的情况;

主干开发模式

主干开发,是指开发人员直接向主干分支上推送代码。通常,开发团队的成员 1 天至少 1 次地将代码提交到主干分支。在到达发布条件时,从主干拉出发布分支(通常为 release),用于发布。

流程:

优点:

  • 分支模型简单高效,开发人员理解成本低

  • 分支合并、冲突解决更高频但更容易解决

  • 随时拥有可发布的版本

  • 有利于推动高质量的持续集成和持续交付

缺点:

  • 合入到主干的代码若质量不过关将直接阻塞整个团队的开发工作,因此需要高质量的CI测试和Code Review工具已经开发文化的支持

  • 对自动化测试要求高,需有完备单元测试和增量测试能力,确保在代码合入主干前获得快速可靠的质量反馈,大大降低人肉Bug发现和排查的工作量;

 以制品为分界的CI/CD结耦,优化研发交付流程

CI的流程的最后,代码被编译成制品存入制品仓库,CD流程从制品仓库取制品直接部署。以制品仓库为边界,将CI流程和CD流程结耦开。

制品可以是C++构建后打binary,也可以是k8s中可以直接部署的容器副本,贯彻GitOps的思想,是阿里目前可见的未来的研发基础设施基石之一。

目前是阿里巴巴研发效能部在负责这部分工作,云上则有云效团队正在研发同样的云上产品。另外就是可以支撑这样高频高要求CI的CI工具,类似CI流水线。

聊聊 WorkFlow 流水线

 人靠谱么?我靠谱么?为什么要DevOps,为什么要自动流水线

我一直搞不懂为什么要搞那么多权限审批。最近突然一堆权限加到了我头上。我发现,自己心情好的时候,会认认真真的盘问申请原因,提醒申请错了表的同学及时更改。但一忙起来,那是真的闭着眼睛过。之所以说这个事,一部分是我在忏悔我对安全不够敬畏,另一方面这也让我深深的意识到一个问题。

人是系统最大的漏洞。

众所周知,贝叶斯定律是机器学习的基石,好比穿起羊肉串的那根签。比起笨重的机器,人脑才是最擅长用先验概率来偷懒的鸡贼玩意儿。反正之前那么多次都没出事,似乎顺着老路走总没错。估计在座的读者应该都被「休谟的叉子」叉过。记不起来的话我可以举几个例子提醒一下你:换了宿舍,开学还是顺着肌肉记忆走到了之前的寝室。给手机app位置整理了一遍,刚打开的那几次都得找半天。

贝叶斯公式没有错。人的大脑也确实很鸡贼。如果没有这种惯性经验,那这日子可过不下去了,要考虑的事情太多。但是经验会犯错,大概率成功并不是100%成功,有的时候环境发生了细微的变化,但是经验却不能敏锐的感知。其次,经验的传递非常困难(各位如果是第二基地的读心术士那当我没说),为什么高的人员流动会严重加速系统腐化和技术债务?因为太多的信息和知识存在了人的经验里,他已经是保持这个系统正常运转的一个部分了,某个小bug经常会触发,他自己知道某个后门可以定时处理订正,但是因为他觉得太低频就没有做成自动化任务。这种玩意儿一旦转手就变成定时炸弹。

“天猫精灵,放一首《杀死那个石家庄人》”

 WorkFlow 流水线

还是先围绕着权限申请说吧。人一个个看过去听起来很靠谱,一旦量大了,这件事还靠谱么?反倒是,制定一些规则,比如某些部门的人员可以自动赋权。或者他已经有某些高权限了,在申请相关低权限表可以自动通过。

可能你会说这个规则也太粗糙了。但是,规则是可以迭代的,可以成长,变得健壮的。而且一旦迭代到一定程度,自动化带来的便捷和安全稳定可以让同学们从重复劳动中解放出来。

可迭代,自动化,这就是流水线的Key Point。

 Action 组件

组件是一种即是一种接口也是一种实现,是对底层资源的封装,把复杂的一坨事变成一个好用的按钮。

以CI/CD流水线为例,比如部署组建,就是把底层的机器资源封装了一层。

再比如自动化测试组件,替我运行我的测试任务,而我不需要去折腾测试任务的资源和任务的调度,很优雅的设计和封装。

 一个好问题:什么是好的流水线?

相信大部分读者看到这儿应该和我一样犯病了。

犯了程序员的职业病,我称这个debuff为「最佳实践缺乏症」,看到啥问题都渴望看到最佳实践。

可惜我也只是病友,但是有次和燎大师吹逼的时候提过一个观点,我深以为然。

如果我把代码里的一行return true改成return false, 然后我运行CICD流水线,如果它能在发到线上前被拦住,那么这就是一条健壮的流水线。

这句话很理想,很难实现,但是也很好地点出了可爱的流水线的特质。

高度自动化,包括优秀的自动化测试能力,越少的人工,系统发挥越稳定。

可迭代,如果某个问题被系统漏掉了,那么他可以被用来迭代这个系统,直到有一天它不在漏掉这个问题。

单测,回归测试,不仅是对稳定性的保障,也是对外部系统的一种承诺。不管内部代码如何变换,甚至从零重写了,只要之前的单测和天启之类的回归测试可以通过,那对于外部系统来说就没有任何改变,类似闭包和接口的概念。那么对于应用来说,一条稳定的,测试完整的流水线,是重要的技术资产。

 一个更好的问题

你可能会想,那我天天就迭代流水线不得累死,问题是搞不完的,万一它真的漏一个bug到线上,算谁的责任呢?似乎迭代出一个完美的,完全自动化的,所有case都考虑到天衣无缝的终态流水线是一个伪命题。

说到替人做决策,不得不提机器学习。在to B算法产品设计中,有一个原则,我觉得在自动化系统中也适用,套过来可以说:

自动化系统要做到Human In Loop,它辅助人类完成繁琐的工作,但不是替代人,人可以把精力投入到更有价值的工作中去。

我相信,在目前的水平来看,就算是最优秀的机器学习算法,最稳定的自动化系统,精准度并不会比行业精英的决策判断更加准确。但是它可以很好的解决效率问题。比如目前的自动驾驶,在安全稳定的情况下提你开车,但是出现紧急情况还是需要人来判断。

在流水线这件事上,把可以自动化的部分尽量交给自动化工具。在代码设计之类的对复杂场景,使用code review之类的方式,通过人来解决。

可能你会问,这不还是要人来搞么?但你想想,提一个code review,结果对方让你改一个变量名大小写的错误或者代码规约对问题。再提一次,过了cr结果测试挂了一个,改完有要cr。乒乓review,是不是很痛苦。

在人类社会提出机器人劳动保护法之前,还是先把这些繁琐的工作交给电脑吧。

聊聊 CI WorkFlow 流水线

说真的流水线真不是什么新鲜玩意儿了。阿里Aone的流水线相信各位都是低头不见抬头见了,大部分公司都有成熟的流水线用来发布。但是最近gitlab action又又又火了。

 Github action

Action是单一的动作可以执行某些功能,比如部署action,代码合并action之类的,把动作组合编排起来则能完成一个有意义的事情,即Workflow

Action类型分为两大类:

1. Github上的代码库,这里又可以分为3种情况:

- Github pre-built action:Github官方创建的action,主要分为3种类型,一种是运行action需要的组件,如bin,hcl等,另一类是公共云集成,如aws,gcloud,azure,还有一类是例子,以example-为前缀。

Marketpace:action市场,这里才是Github的厉害之处,以强大的Github生态构建的市场,将会出现如雨后春笋般的action被创建出来。五花八门,可以满足各种各样的需求。

- Custom Action:如果上述的action不能满足你的需求时,还可以自定义地创建新的action,这里的action可以是私有库的,也可以是公开库的。一个公开库里面的action如果对其他人有用时,还可以发布到市场里面去。

2. Docker Image,支持两种来源:

- Docker Hub

- 任何公开的Docker Image库

 CI何尝不能是一条流水线

圣诞节收到一个锤子做礼物的小孩,会觉得家里所有的东西都需要敲一敲。

相信各位看完这个github action的介绍,就像拿到了锤子的小孩,想对着自己的日常工作来两下子,让自己过的轻松一些。

来设想一下CI流水线可以有什么能力?

最最基础的,我们团队的CI流程可以被定义了,而且不需要口口相传了。新来的实习生写完了代码,只需要懂怎么触发流水线就行了,不用知道自己要先测那先部分,是发布前给师兄cr还是写完了就给他看?

测试可以左移到CI流程了,这部分可能就依赖Action生态的优秀程度了。但是这种插件化的机制就很有想象力,再也不用等Aone代码组那几个老哥挤牙膏了,开源的扫描任务或者测试任务用action的方式就能接入。尤其是云原生时代有了IaC之后,也不需要像现在的Aone实验室一样还得有专门的镜像才能跑对应的哪几种任务了。外面的Action也可以搞进来用。

离梦到来还有多远?

明显能看到几个技术前置依赖项待解决:

 文件级别的权限管控,现在啥啥都as code了,都往代码里存,这玩意儿可不是谁都能改啊,安全小哥震怒!把关键配置文件,只允许部分同学改动编辑的能力是很有必要的。

 强大简约的WorkFlow框架能力。能云原生化,serverless化,甚至faas化托管action的流水线。看了网上很多对github action的评测文章,发现有的拿来给一些网站做签到脚本,虽然说其实也是在用流水线,我倒觉得更像是在用它的Faas能力。对于测试来说,faas化的测试和扫描,以后一个新的扫描或者测试任务不在需要维护一个单独的集群或者部署一整套应用,而是只要写好业务逻辑,把任务调度交给workflow系统。

 优秀实用的一些关键的基础Action组建,比如优秀的Code Review能力(CI流水线基础中的基础),比如制品仓库的能力(可以将CI和CD流水线结偶的关键),比如自动化测试的组建,以及最最关键的,发布和git操作的能力。

可能的问题

Q:干净的代码也只是降低系统的腐化,要怎么逆转效能的降低,做熵减呢?

A:设计是在信息不完全的情况下做出的决策,所以早期对领域模型的抽象肯定会有不合理性。更好的抽象和重构可以让应用重回青春。

Q:一定要用主干模式替换分支模式么?

A:分支模式也有分支模式的好处,这里可以再用一次折中定律,提升一方面,就要牺牲另一方面。所以什么开发模式其实只是手段,更关键的是如何提升代码质量,建立保证代码质量对研发体系,和形成肯定代码质量的文化。(完)



你可能还想看

1. 钉钉总裁不穷:周末最烦写周报还有被人钉

2. 我在支付宝体验技术部这四年学到了什么?

3. Flink CDC 2.0 正式发布,详解核心改进

4. 详解云原生技术Serverless的起源、发展和落地实践

5. 深度 | EB级规模大数据平台核心技术揭秘(下)

END

关注「凌云时刻」并设置星标✨精彩推送不错过

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存