我终于对禅道14年的代码下手了
原创- 2023-05-29 09:22:50
- 17144
从五月份开始,我们对禅道进行了整体的重构,我终于对禅道14年的代码下手啦。
跟大家说说我们的背景。禅道最新版本是18.4,核心的架构是MVC模式。禅道的第一行代码是2009年写的,至今代码已经迭代了14年。期间的开发人员也从最开始我自己一个人到现在几十个人在维护,虽然整体的结构还算良好,但也不可否认的是代码里的坏味道越来越多。是需要进行一次彻底的重构了。
在对业务代码重构之前,我们先对底层PHP框架和UI框架做了重构。Controller层和Model层我们又分别拆出来了两层:
-
其中Controller层拆出来的一层我们叫Zen,Controller里面拆分出来的单一功能的方法会放在这一层,比如访问权限的检查处理等;
-
Model层拆分出来的一层叫做Tao层,Model层里面拆分出来的单一功能的方法放在这一层,比如一些基础的数据查询计算等。
通过这两层的拆分,我们可以精简Controller和Model里面方法的代码量,使之更加精简易读。
UI框架我们也做了彻底的重构。我们充分利用了当下流行的前端技术,采用了组件化封装的方式。与此同时我们使用PHP对UI组件的调用进行了封装,这一层我们称之为ZIN层。在实现View层代码的时候只需要用类似dtable($params)这样的方法调用就可以了。这样做的好处就是View层就不需要再拼装组件的结构代码,只需要关注业务逻辑和展现逻辑即可。当组件有变化的时候,只需要修改ZIN的封装即可。
禅道整体的架构还是多页面的架构,并没有采用SPA的这种方式。这里面有历史的原因,禅道刚开始立项的时候还没有这些概念。另外一方面也是因为禅道的功能比较丰富,逻辑比较有挑战,SPA的模式也并不适合禅道的这种应用场景。不过这次的重构我们也充分吸收了时下流行技术的一些优点,将它们必要的特性都融入到了我们新的框架中。
在开始业务代码重构过程中,我们也遇到了一些挑战。主要的表现是大部分的同事驾驭不了这种程度的重构。以前在写代码的时候,更多的是对局部代码的修改。这次重构我们需要对整个模块的MVC三层都需要做重构,与此同时我们还转变为TDD的模式。很多同事对这种程度的重构就很不适应。对此我们也采取了一些应对的措施,主要如下:
首先让大家通过写段注释的方式来梳理原来的业务代码。
禅道的功能比较丰富,业务逻辑也比较复杂。要想做好重构,首先的一点是掌握原来的代码逻辑。而掌握原来代码逻辑的一个很好的方式就是写段注释。我在之前的一篇文章里面也讲过做好代码片段的管理。若干行相关的代码可以组成一个代码片段,这个代码片段可以是变量的初始化,可以是某一个逻辑分支判断,也可以是一次数据的查询。通过写段注释的方式可以快速地将一个方法的大逻辑梳理清楚,大逻辑梳理清楚之后就可以更清楚代码如何拆分了。
还有就是小碎步提交代码。
这次重构过程中,我们发现有很多同事会积累很多修改,然后一次提交一个很大的改动上来。这样的改动做代码评审的时候就很难做比对,因为改动点太多了。发现这个问题之后,我们就跟大家讲要学会小碎步提交代码。怎么理解小碎步呢?比如对某一个方法进行了段注释的梳理,就可以做一次commit。因为这一次的修改是确定的,单一的。再比如把某一个逻辑分支里面的代码抽取出来放到Zen层或者Tao层里面,就可以对代码进行一次commit。然后再对拆分出来的这个小方法进行重构优化,再做一次commit。这一次commit做完之后,就相当于完成了一次完整的改动,就可以push到服务器上,方便其他同事合并代码。
正所谓一口吃不了一个胖子,代码的重构也很难说一次完成。这种小碎步提交代码的好处就是每一次的代码提交都是确定的,每一次的改动都是向着更好的方向前进,积小胜为大胜。
我们从这一次重构开始彻底推行TDD。当然实行下来也有很多的问题,我后面会再写一篇文章来聊聊我们做TDD的一些经验和教训。整体来看的话,主要的问题是思维模式的转化。很多同事也按照要求写了单元测试,但一个方法就只有一两条测试用例,完全起不到测试的目的。这就说明这些同事的意识还没有转换过来。我对TDD的理解,它更像是一种思维逻辑的转换方法,通过TDD的方式让大家先来梳理业务逻辑、设计方案以及可能的各种测试场景。通过这些方式强迫开发人员从原来不顾三七二十一就开始撸代码,转换为谋定而后动、通盘考虑、稳扎稳打的方式。不过这种转变需要一个过程,我们再跑一段时间跟大家来分享。
这一次重构我们还充分利用了各种DevOps的工具。最近这几年来禅道一直在做各种DevOps工具的集成,最近我们也推出了自己的集成化、轻量级的DevOps解决方案。大家在做TDD的时候,用的是我们自己开发的ZTF自动化测试框架和我们的ZenData测试数据生成工具。我们还对代码的push做了相应的限制,在push到主干之前,需要先在本地进行代码扫描和单元测试。只有代码扫描和单元测试全部通过之后,才能进行push。在这个过程中,我们也发现了很多DevOps实施过程中的一些具体的问题,也有了很多自己具体的方案,后续都会补充完善到我们的通用版本中。
除了这些之外,我们之前一直坚持的代码规范、结对编程和代码评审等工作还在继续加强。极限编程的各个实践都是密切相关的,每一个实践都会对另外一个实践起到加强的作用。我一直认为敏捷开发一定要做极限编程,工程和管理两者都敏捷,才是真正的敏捷。
期待我们最新的20版本能够早日重构完,期待在新的代码结构基础上再开启一个新的14年系列。