现在国内很多互联网公司的项目都维持了五年左右,美国老牌公司如IBM的项目持续维持了十五年。但是,这些项目的维护成本完全不同。年底后的维护成本仍然没有启动期那么大,可以保持相对高效的迭代速度。但是,在一些项目中,即使改几个文案也会导致上线事故,研发效率会越来越慢。根据笔者的经验,尝试总结一些持续维护项目变得难以维护的原因,以及如何设计维护良好的可维护性。精读心态如果不真诚的对待自己的项目,其实是很难做到好的可维护性的,所以首先要有一个好的心态。作为项目经理,一旦将一个项目交给同学开发,一定要充分信任同学的能力,因为实际上你不可能实质性地影响开发细节。可能有人会认为一个好的流程或者CodeReview事后可以发现一些问题,但这始终是九牛一毛。比如下面的例子:小张接到了一个开发数据透视表的任务,要求这个数据透视表要有良好的开发经验,做好单机测试。那么如何做到单机测试有效,同时又如何保证开发体验呢?不同的人会有不同的想法,会有不同的结果。对于有主人翁意识的小张来说,对于一个有一定经验,真正对项目感兴趣的小张来说,开发过程可能是这样的。首先,我们先写主要功能。例如,在考虑数据模型和绘制技术方案后,我们决定以图形语法的方式定义数据结构。经过一系列高性能的前期考虑,我们很快做出了一个原型,包括表格渲染、操作、翻页、冻结等等。但是随着需求的深入,小张发现在下钻排序的时候,列冻结的功能不知为何受到了影响,代码结构其实问题不大,抽象的也很好。主要原因是缺少代码调用的一些细节。丢了,只要补上,任督二脉立刻打通,整套机能又顺畅起来。但是不知道下次做树状展示结构的时候,之前的功能会不会再次受到影响。这始终是一个隐患,于是小张开始考虑在继续开发功能之前加入单测。因为有一小部分问题场景是大量操作后意外导致的,普通的功能单测无法保证全面覆盖,所以小张决定做单测录制功能。他先把对表格的所有操作都打成Actionjson,这样一套json就可以描述所有的用户操作,然后在本地开发界面做一个单测记录功能,也就是当表格功能被拖拽的时候并放到页面上,会实时生成这组用户操作json,然后记录当时的页面结构和内部状态作为对比依据,还原这组json和benchmark对比即可状态为一个单一的测试。小张很快记录了很多原子操作的单测,比如表格的各种空数据状态,单行单列渲染,列冻结,行冻结;然后结合一些混合功能的场景,比如列冻结时排序,翻页,最后下钻;最后,将一些随机的、复杂的功能组合在一起,形成一些日常生活中容易出现问题的特殊单机测试用例,比如表格单页后突然清除数据,然后强制冻结第二列,以及然后填入3列数据,对第2行进行排序,然后解冻列,翻到第4页。以后每当遇到边界情况,小张就会在单元测试中记录问题情况,验证操作是否失败,以及然后修复它,直到包括本单元测试在内的所有单元测试都通过验证。开发不被视为完成。对于打工的小张来说,对于以生活为生的小张来说,发展过程可能是这样的。首先,让我们编写主要功能。在完成了各种表函数之后,我也遇到了同样的边界大小写问题。这时,小张本来想一个个修,没想到领导让他写单测。我觉得还不错,创建单个测试目录。如何编写单个测试?首先,小张修复了他遇到的问题。毕竟谁也不想自己手上有太多的BUG,但是单测记录又太麻烦了。反正这个案子大家都不知道。如果修好了,永远出不来,那就加几个领导要求的基础功能单测,看覆盖率达到硬指标。大团队的代码总是容易出现混乱。假设你是领导,你不知道你团队里的小张是老板小张还是工人小张。尝试通过codereview来统一提升团队的代码质量,是否真的可行?如果你不幸遇到了工人小张,他在codereview中展示的代码结构并不是一个可以做整体单元测试的抽象。只能看着单元测试文件强行提一些“多加单元测试,多想情况”的建议,其实完全达不到主角小张的效果。这背后的原因是影响代码质量的因素太多了,比如Action,比如各种极端情况的入口,比如整个流程的单测形式,这些对于代码来说都是质的变化,但是codereview时看到的代码只是不够抽象,不够动作化,推翻改写代码是不可能的,只能在已有代码的基础上提供优化建议,这时候大神们可不行把工人小张的代码优化给主角张小张,除非推翻重写。这就是心态的影响。能把一个项目做好的细节很多,细节还是环环相扣的。要想设计好,有多少人在codereview的时候能想到这个?想着这个时候再提可能也来不及了,一切都已成定局。这些年,笔者看到了很多历史悠久的代码,因为大公司有大量的开发人员在维护同一个项目,每个人开发时的心态都不一样。你会发现,你总能以工人的心态看到那些模块是做出来的,而如果你想彻底优化它,只能完全重写,但由于工程量大,时间不允许就只能在打工的路上继续写下去了。所以以良好的、积极的或积极的主人翁态度编写代码,通常可以很好地维护复杂的项目。解耦复杂项目的复杂性意味着什么?是不是意味着更多的功能?其实并不是。如果仅仅从它的众多功能来判断这个项目复杂,那么我们所处的社会就是最复杂的系统,但社会中的每一个参与者并不觉得吃、穿、住、行难。核心原因是了解我们使用的是什么。场景只需要少量的知识,采取行动以获得正确的结果并没有太大的影响。比如你出去买菜,只需要坐公交车到菜市场,扫描二维码就可以完成交易,不需要对城市公交有什么深入的了解菜市场背后的系统和金融系统。你不需要了解公交车从哪里来,菜农手里的蔬菜是从哪里采购的。但是代码世界很有趣。在代码世界买菜可能会导致世界毁灭。这就导致每一个项目开发商,哪怕是去买个菜,也要接受总统培训,为各种国家大事做出正确的计划。为什么会这样?因为代码世界的逻辑是由不同的开发者编码的,所以在实现世界底层逻辑的时候可能会埋下耦合的种子,让你不禁疑惑为什么买菜会引发这么严重的事件。例如,如果更改副本导致系统崩溃,原因可能是某些错误的自底向上逻辑从字面上判断副本,如果更改副本,则此判断将失效。有些程序员是相当困难的。要想在这种项目环境中生存,每一步修改都必须慎之又慎。这个问题的解决方案是解耦。这里就不详细说怎么解耦了,因为每个场景的解耦方法都不一样。我们只需要明白,几乎所有的业务逻辑都可以通过解耦的方式来完成。只要按照这么大的思路去设计系统,不管路径是什么,最终都能设计出漂亮的系统级方案。比如在做一个BI系统的时候,好像有各种复杂的模块可能会相互影响,比如数据处理、仪表盘构建、大屏构建、图表、GIS地图等等。在设计之初,有必要假装其他模块不存在,考虑每个模块的必要输入是什么。例如,布局仅用于布局画布。为了保证布局系统完全解耦,项目必须支持在无布局的环境中运行。为了做到这一点,需要让布局真正“只是做布局”,而不存储当前的画布结构,这样在移除布局系统时,组件的联动不受影响,因为组件需要使用画布结构API。层列表也可以和布局解耦,因为层列表只关心画布的组件树结构,而不关心布局是如何实现的,所以画布的组件树结构就像生活中的钱,大家可以用它来交易,而不关心它去了哪里以及谁在使用它。数据逻辑与画布结构无关。只需要关心维度度量、聚合方式的表达方式和用户的配置,以及图表本身的特性即可查询sql拼接。唯一用到的公共资源是canvas的组件树修改后需要更新当前组件实例信息。社会也是基于这种底层身份,所以可以这样解耦。所以,在复杂的项目中,一定要有一个大家认可的底层概念。能买菜就麻烦了),贯穿整个商业逻辑(现代社会任何交易,金钱都是必不可少的媒介)。许多项目被批评难以改变,往往是因为他们没有遵循这个逻辑,强行耦合可能不相关的概念。例如,当某个过滤条件发生变化时,对某个组件进行特殊操作。在这种情况下,可以执行控制反转。当该组件接收到某些过滤条件时,它会自行执行特定的操作。因为对于BI系统来说,filter的输出应该作为图表绘制的输入。在这个底层框架下,不需要再去开发一个filter去关心具体图表的逻辑。综上所述,维护一个复杂的项目是非常困难的。这次分享了两个在实践中很有用的解决方案。第一个是设计具有主人翁意识的代码。一定要在设计之初就考虑清楚,不要把希望寄托在那些没有设计好的人身上。系统做缝纫和修补。二是深刻理解现代社会运行为何巧妙,尽可能将法典结构组织在一定程度上映射到社会运行机制上。目前社会最适合代码学习的方式就是解耦,然后利用庞大的分工协作网络做一些人做不到的事情。讨论地址为:Jingdu《维护好一个复杂项目》·Issue#454·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号
