Uber架构重构的十二条军规(完整篇)
确定重构的目的和必要性
看起来这个规矩有些多余,但是请不要忽略。每一次架构的重构都是“伤筋动骨”,就像做手术一样,即使再成功,也会伤元气,所以决策者们首先要分析架构重构的理由和其他备选方案,明确重构的目的是为了满足业务需求,并且是不得不做的最佳方案,然后再考虑其他问题。 有时候,经过分析就会发现,也许还有其他解决方案,比如增加计算资源,或者重构的目的不是为了业务需求,那就没有必要做了。
检查清单:
架构重构的原因是什么,是为了满足业务的需要还是只是觉得架构不好看?
除了架构重构之外,还有其他备选方案吗?是否都分析过这些方案的利弊?
定义“重构完成”的界限
如果确定要重构,那么要把目标明确下来,也就是重构的边界条件,怎么才算是“完成”了重构,目标要有数据量化,或者有能够测试的办法。这也是一个需求分析的过程,如果需求不明确,那么规格说明书没法写清楚,负责重构的团队也没有明确的目标,不能以重构的时间或者主观的判断为结束的依据。前几天和一朋友聊天,他最近在负责系统的性能优化,也要做一些重构的事情,开始的时候团队的目标不明确,大家不知道优化到什么程度,所以不敢下手。如果目标是提高10%,那么可以从细节处着手;如果是提高50%,那可能要搞大动作才能实现了。后来目标明确之后,团队才找到合适的办法。
检查清单:
重构的目标可以量化,或者说可以测试吗?
重构完成的标准是什么?得到业务部门或者领导的认可了吗?
渐进式重构
现在软件研发最流行的就是快速迭代、持续交付、尽早反馈。这同样可以用在架构的重构上,重构过程的难度不亚于构建一个新产品,所以在设计重构的时候,要引入持续交付的流程,每一个重构步骤或者模块都要快速部署并得到反馈,以便评估重构的效果,及时作出策略调整。有的读者会说,我们的架构重构是釜底抽薪型的,没法渐进,只能一蹴而就。如果是这种情况,可以考虑在另外一套拷贝的系统中做重构,经过谨慎测试之后,将数据和业务迁移过去。
检查清单:
把重构过程分成小的迭代,每一次改进都能尽快得到反馈?
重构过程中的效果能够定期展示给业务部门或者领导吗?
确定当前的架构状态
在启动重构之前,团队要对当前的架构状态有清晰的了解,也就是设定好基准,以便评估重构的效果。据我的经验,负责重构的架构师或者开发者,往往还没有搞清楚现有的架构设计,就开始重构了,结果经常出现这样的情况:重构到某个阶段,发现行不通,然后一拍脑袋说,哦,原来这块的架构是这个样的,是为了达到某某业务需求啊,这块不能动,得想别的办法。类似的例子在研发团队中时有发生,也提醒我们要慎重小心。记得有位哲人说过,了解别人很容易,了解自己很难。
检查清单:
你了解当前的架构设计吗?它的设计初衷和之前的选型方案知道吗?
你能给架构设定一个基准状态吗?
不要忽略数据
数据的重要性不言而喻,业务都是以数据流为载体的,所以架构重构的本质就是对于数据流的重构。数据对重构的重要性主要体现在两个方面:在重构设计时,需要考虑业务数据的需求,重构之后的系统对于数据的存储、处理、分析等功能是否有影响;在重构过程中,考虑依靠数据甚至是实际的数据来验证重构的效果,提供评估的支持。
检查清单:
业务数据的需求在重构设计中有体现吗?
重构过程中能否通过实际数据来验证效果?
管理好技术债务
技术债务在平常的软件研发过程中也是比较突出的问题,现在单独拿出来强调是希望提醒开发者们:架构重构往往是为了偿还技术债务,所以请不要在偿还技术债务的过程中制造技术债务了。技术债务就像信用卡一样,会有很高的利息率,就如同给团队留下了大量的帐务开销。组织应该培养一种保证设计质量的文化。应当鼓励重构、同时也应当鼓励持续设计以及其它有关代码质量的实践。在开发时间中应当专门抽出一部分以解决技术债务。如果没有合适的照料,那么真实世界中的代码会变得越来越复杂难懂。
检查清单:
团队对技术债务有跟踪和备忘录机制吗?还是开发人员可以随意的产生债务?
针对技术债务有定期的培训、回顾机制吗?
远离那些虚荣的东西(例如使用“热门”的技术栈)
架构的重构过程应该是以目标为导向,换句话说“注重实效”。对于技术人来说,一个经常被轻视的问题在于,喜欢追逐新鲜的热门技术,这其实是个好事情,说明技术人勇于创新,不断接受新技术。但是对于架构的重构这样的关键性任务来说,是不是新技术并不重要,重要的是能不能实现重构的目标。对于新技术来说,虽然热度大,但是人才储备还不足,大家踩过的坑还不多,积累的失败教训和成功经验还不够,在这种情况下,建议大家不要头脑一热就上马新技术,应该客观冷静地评估新技术和成熟技术对架构重构的影响和效果,以数据和经验来说话,而不要追赶时髦。
检查清单:
重构的技术选型是否有详实的数据和专家评估?
选用的技术是否有良好的人才积累和足够的经验支持?你是不是实验小白鼠?
在技术选型时,是否至少有两个方案待评估?有没有成熟的技术方案?
做好准备面对压力
这条军规更像是对架构师们的心理建议,软件开发过程中,压力无处不在。对于架构重构来说,压力来源于多个方面:管理层、团队成员、同级部门等等。说白了,架构重构对个人来说往往是一件出力不讨好的事情。和做一个新产品能够取得很高的赞赏相比,重构的成绩往往并不受领导重视,而且出了问题还要承担很大的责任。从软件开发角度看,做新产品是从0到1,而架构重构是从-1到1,复杂性和难度通常更大。因此,重构的负责人要提前做好心理准备,舒缓压力的一个技巧是,设置好里程碑,将重构的成果量化,并且和业务的变化关联起来,定期向利益相关各方同步状态,得到大家的理解和支持。
检查清单:
架构的重构是否得到了管理层(特别是最高管理层)的支持?他们是否对重构的时间、任务量有直接的认识?
你的重构计划中是否包含了一些可以量化的成果?是否定期向管理层展示这些成果?
了解业务
虽然看起来像是一句废话,但是我想Raffi Krikorian特意把这条提出来一定是有理由的。架构重构的最终目的是改进业务,所以对于业务的了解将有助于架构师和技术人确定重构目标的优先级和关键路径。比如,我们需要知道哪些关键业务的架构是不能碰的,哪些业务之间是互相关联的,哪些业务的架构是需要优先重构的.....等等。除了了解业务本身,我们还需要了解“人”,表面上管理层是重构目标的裁决者,但实际上业务部门的人才是。技术人需要了解他们的业务需求,并将其转化为重构目标。通过这种方式,架构重构的意义才能得到具体的体现。
检查清单:
是否与业务部门就架构重构所能实现的业务目标进行过充分的讨论和确认?
是否对关键业务和优先重构的业务进行了确认?
做好面对非技术因素的准备
恩......这又是一个不那么让人舒服的建议。不管你是否愿意相信,技术在架构重构(以及其他很关键的公司决策中)的影响因素中并不是最高的,我们还会涉及到商业利益、管理层偏好、大客户影响、办公室zhengzhi、站队问题等等,对于架构师和技术人来说,这些因素往往不是他们所能掌控的。我们能做的就是,与利益相关者设定重构目标,然后,根据不同的影响因素,调整目标。请记住,不要死扛这个目标,当有人提出不同的意见时,要坦诚地和他们交流,并告知他们如何采纳意见,那么重构目标会有变化,然后让其他利益相关者也知道这些变化。非技术因素的影响是客观存在的,而且从商业层面来说也是合理的,所以对于技术人来说要学会适应。
检查清单:
当非技术因素影响架构的重构时,你是否对目标做了调整并告知了利益相关各方?
你是否准备以开放而不是抵制的心态来对待非技术因素的影响?
对于代码质量有所掌握
这和上篇中所提到的“管理好技术债务”有异曲同工之处。架构的重构对代码质量要求很高,一方面是重构过程对bug的容忍性比新产品的研发更低,另一方面也决定了下一次重构的难易程度。关于代码质量的书籍和文章已经有很多,在这里只想提醒大家一点:代码审查是一个非常好的办法。代码审查是软件开发过程中的必要步骤,既可以帮助被审查者提到代码质量,又可以让审查者加深对产品的理解。不论团队多忙,一定要保证代码提交之前,是经过其他成员审核过的,短期来看会占用团队的时间,长期来看是事半功倍的好事。
检查清单:
团队成员是否对代码质量有足够的重视?是否有奖惩措施?
团队内部是否有代码质量的标准文档和审查流程?
让团队做好准备
这是Raffi Krikorian列举的最后一条军规,是对之前所有建议的总结,我在这里不做解读了,请大家自我感觉吧。
技术债务来自于对金融债务的比喻,它指的是在程序设计与开发过程中,做出的错误或不理想的技术决策,由此带来的后果,逐步累积,就像债务一样。
技术债务的产生,可能是有意的,也可能是无意的。有意产生的债务,一般是根据实际项目情况(资源与期限)做出的妥协。而无意产生的债务,一般就都是经验缺乏引入的。不管怎么说,只要程序员在不断的生产代码,他们就是在同时创造资产与债务。
还债的名声不太好听,所以我们喜欢叫:架构升级。架构升级除了还债,还是为未来铺路 —— 当然,前提是要有未来,如果未来还能迎来更大的业务爆发增长,架构升级就是为了在那时能消化更多的长短期债务
战略债务,老码农说是为了战略利益故意为之,并长期存在。我理解就是在公司或业务高速发展的阶段,主动放弃了一些技术上的完备与完美性,而保持快速的迭代与试错性。这个阶段公司的战略利益是业务的抢占,所以这个阶段的公司都有一些类似的口号,比如:先完成,再完美;优雅的接口,狗屎的实现。战略债务的特点是,负债时间长,但利息不算高且稳定,只要保持长期“付息”,不还本金也能维持下去。
战术债务,一般是为了应对短期紧急情况采取的折衷方法。这种债务的特点就是高息,说高利贷也不为过。举个例子,曾经做电信项目时,系统处理工单,主流程上有缺陷,对某一类工单处理会卡住。这时又不太方便停机更新程序,就基于系统的动态脚本能力去写了个脚本临时处理这类工单,可以应对当时业务经营的连续性,缺陷是资源开销大,当超过一定量时 CPU 也就满了。这样的技术方案就属于战术债务的应用,当天半夜的业务低谷,就重新修复了程序,归还了这笔短期临时债务。
疏忽债务,这类债务一般都是无意识的。从某种意义上来说,这就是程序员的成长性债务,随着知识、技能与经验的积累,这类债务会逐步减少。
对于不同的角色,关注的债务分类与形态也不太一样,比如架构师关注的更多是战略债务,保持系统能够健康长期演进的债务平衡。作为架构师,就像 CFO,需要长期持续的关注系统的资产负债表。战略债务可能更多体现为架构、设计与交互方面的形态。而具体某个功能实现层面的代码债务,则更多落在相关开发工程师的关注范围内。测试工程师,会关注质量方面的债务,而一到交接时,各种文档债务就冒出来了。
还债时,我们主要考虑债务的大小和还债的时机,在不同的时间还债也许研发成本相差不大,但机会成本相差很大。而按不同债务按大小,又可以分为大债务和小债务。一般,我把需要以周为单位计算的债务算作大债务,而只需一个程序员几天时间归还的债务算作小债务,所以这不是一个精确的定义。
小债务的归还,基本都属于日常的重构活动,局限在局部区域(模块、子服务)的实现层面。而大债务 —— 架构升级还的一般都是大债务 ——的归还,需要仔细的考虑和分析机会成本与潜在收益,所以大债务归还要分三步走:
- 规划:代表愿景,分析哪些债务需要在什么时间还,机会成本的损失与预期收益
- 计划:代表路径,细致的债务分期偿还计划
- 跟踪:真正上路了,确认债务的偿还质量与到位情况
如今微服务架构的流行,基本把小债务锁定在了一个或几个微服务体内。即使碰上没有信用的程序员把自己负责的微服务搞烂了,形成债务破产,这时的还债方式无非就是完全重写一个,在微服务拆分合理的情况下,一个服务的重写成本是完全可预期和可控的。
做一个有信用的程序员的关键是:知道何时引入债务解决紧急情况,之后立刻还债;无意识引入的负债,当时看不见,也许多年后你成长了,看见了,但别假装看不到啊