怎么保证一个传承几十年的项目可维护

面试有人问这个问题,并没有很系统化的回答了,越来越发现是个有意思的问题,来整理一下。

项目特点

问题中,只提到了项目要延续很长时间。但是,还隐含着不少潜在特征:

  • 通常比较大,代码量不大的项目不需要延续,而是随时重构。
  • 协作规模通常比较大。
  • 有人持续的使用这个项目。

存在的风险

首先是时间长带来的:

  • 技术迭代问题。
  • 信息随时间丢失,特别是奇怪的实现、奇怪的需求、bug fix等异常逻辑。
  • 人员招聘和交接。

其次是大规模协作的问题:

  • 模块拆分以及康威定律。
  • 边界界定和说明。
  • 发布节奏。
  • 权限管理。

然后是交付问题:

  • 如何保证交付节奏
  • 如何保证用户体感不变,有一个很神奇的Hyrum’s Law,放在这里也很对,用户对于所有非崩溃bug都可能会当做feature来用

仍然粗略的想法

最简单的是规模问题

  • 竖向拆分,最简单快捷的拆分方式。利用对于业务的理解,把代码先垂直拆分成子模块(DDD)。先竖向拆分是因为竖向拆分一定是基于业务的,而长期存在的业务通常是有稳定内核的,慢慢在内核上长出来枝叶,比代码要稳定很多。
  • 分层,不论是整个系统,还是单个功能。站在客户端角度,不论是app结构还是gui。拆分最好能参考clean-architecture,核心思路是稳定的不依赖不稳定的内容。分层的目标是:
    • 利用分层规范编码思路。
    • 利用分层继续划分边界,约束思维边界,避免杂七杂八写一坨。
    • 利用分层分配工作,模糊端的概念但强化经验的作用。让资深客户端远离ui,而关注业务逻辑;让非资深后端为前端展示逻辑负责(BFF),而不去做高并发。
    • 利用分层把稳定和不稳定的代码拆开,由不同水平和流动性的人员维护,尽可能保证人和代码的稳定。
  • 按团队拆分模块,但是粒度一定要够小,同时在git(或者其他版本管理工具)上保留原始历史的情况下迁移位置。比较好的办法是多仓;或者平铺目录,给每个目录加上团队归属标识。千万不要用目录结构来描述团队归属,团队调整一下子就没有历史记录了。
  • 边界界定。个人推崇直接做IOC,不论怎么改,靠编译解决信息同步。如果需要文档,则一定要不同文档版本关联到不同的commit range,并且对文档格式(最好内容也)做好自动化检查。
  • 在开发的每一个关键节点尽早发现问题,避免问题污染范围扩大。
    • 需求定义阶段,要给出主流程和异常流程需求、边界条件处理、给出预期规模和性能目标、给出达标标准。
    • UI/UE阶段,要保证切图、语言等资源不求完成,但是要不影响开发(主要是类型、文案长度、图片大小等)。
    • 技术设计阶段(在传承类项目,技术设计文档会是一个非常关键的工具,不能省略),保证正常逻辑和异常逻辑方案、边界、关键性能点等信息完备。
    • 开发阶段,利用lint、编译、IDE插件等在运行前发现问题。
    • 自测阶段,利用debug工具(滴滴的dokit)、tracing工具等在提测前发现性能问题。
    • 测试阶段,有充分的沙箱环境,避免测试功能外泄影响其他feature开发。
    • 合入/预发布阶段,检查所有全局类的修改,避免对其他合作方的影响。比如app中的基础库的依赖升级。
    • 灰度阶段,仍然是隔离为主,一方面在产品内部隔离崩溃等用户不友好的异常行为,另一方面利用正交世界隔离业务间的数据影响。
    • 上线阶段,保证子业务之间的运行时隔离,以及对于所有资源访问的统一收口和抽象。

稍难的交付问题

如果把用户和需求也放到整个开发大图里,仍然是保证边界的清晰和稳定。

  • 外显特征的测试用例化。这里其实一般都不需要到st,it或者ut就足够了。看一下MVC->MVP->MVVM的发展过程,其实越来越多的逻辑(做得好的MVVM其实是所有)都可以在集成测试之前自动化跑完。这里一定要保证覆盖率,落到case主要是为了后续自动化检测所有代码分支都没有逻辑变化。
  • 交付节奏的话,就是把重构和开发尽可能的隔离。重构冻结功能的小部分,集中上线,不要边大重构边开发需求。重构是否成功完全看前面的测试是否能跑过。
  • 保证体感不变的话,最重要的是前期需求文档足够明确,尽可能把边界条件都考虑进来,需求验证也回归需求文档,以代码级别的重视程度传承和迭代文档(可以参考工业软件开发流程)。同时尽可能把需求落到测试用例上,方便后续的检查。

最难的是代际传承

  • 先用好clean-architecture,技术选型越靠近核心层越谨慎,内层一定要做接口实现分离。尽量在核心层仅依赖语言本身的能力,并且使用前后端通用、微服务支持的语言。目标是保证这些代码在不需要修改逻辑的时候自然兼容运行环境和其他语言,不需要因为技术本身迭代进行修改。
  • 模型(狭义Entity)最好不要直接用代码描述,而是保存成一个语言独立的IDL里,用时生成可执行的类定义。保证语言迭代、框架迭代和规模扩展的平顺性。
  • 能用代码传递的不要用其他。这里的核心是保证后续开发者在IDE内直接能看到一切有用的信息,无需切换思路。同时,代码本身一定是表意清晰的。其实文档/注释等都违背了单点修改,同样的逻辑需要用代码和另一种语言描述两遍。
  • 但是很多内容是不能靠代码的。文档,特别是需求文档和设计文档一定要保持内容的准确性,并且随着代码迭代一起迭代和关联。
  • 同时,要有足够好的激励机制,特别是对于核心层开发人员,保证人员稳定。在独立模块内,必须保证一个业务专家存在,能明确说明功能(听起来是废话,但是这个真的能做到且真的很有意义)。
  • 交接也是需要明确和最新版的文档和面对面QA。

misc

  • 把ab、灰度、放量等技术控制手段从代码中完全抽离。对于web和后端,在Nginx 302一切;对于客户端,问题比较大,此时就要想办法把可302的代码粒度拆到非常细(否则duplicate会导致包大小失控),利用小粒度替换实现上述工作,一个技术基础侧的模块factory就能保证拆干净这些杂项,业务逻辑上仍然是干净的。这里的拆分一定是有业务语义的,否则拆分本身必然导致理解困难。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值