架构治理

若干思考或原则

一、治理的必要性

代码和服务是需求的实体化表现,是由研发活动的产物。需求的变化和研发活动的复杂性决定了软件熵增的事实。治理动作是减熵的过程,目的是为了在更长期的范围维持需求的迭代效率。

例如,有些需求是偏定制的,则必然给代码引入新的判断逻辑,提高其复杂度;有些功能已经废弃了,其逻辑在代码却并未被删除;需求的变化,也导致代码逻辑的不连贯,理解和修改成本高;从人的角度来看,人员的变动、经验的不同、开发习惯不同、团队文化不同、组织奖惩机制不同都会在代码中引入更多的复杂度。另外,研发人员依赖的工具,如CI/CD、基础组件、自动化平台工具等都会反向影响编码行为。

如果不做治理,放任其自然发展,则软件复杂度必然会随时间逐渐变大。治理时间越滞后,治理成本越高。例如,某客户端和服务端的模型出现每月新增70+字段的线性膨胀问题;迭代时间较长仓库中会出现一些超高认知复杂度的函数,且仍处于频繁变更状态;更多的需求的仓库,也包含了更多的废弃代码和废弃实验。这些历史债务多多少少都被开发者感觉到了,但是其迫于需求迭代效率,上线压力,结合修改风险,往往选择忍耐和不治理,只满足当下的需求上线即可。

研发对于软件劣化的趋势的容忍是可以理解的,当与他们沟通时他们表示有意愿去重构,但是迫于上面的原因往往选择延宕重构时机。要解决这个问题,推动治理需要具备这几个条件:1)认知到治理的必要性,给研发和组织以治理的动机;认识治理的必要性在概念上,研发和组织应该很容易达成一致,难点在于治理成本和收益;2)治理不冒进;我们虽然提倡治理,但是反对不顾成本的治理;例如一个生命周期快结束的服务,就没有必要投入大成本去治理;多服务共用的模型治理,就要考虑多业务方的资源和协同,定制从增量到存量的递进策略;多服务涉及数据库表多写的架构改造,就要结合风险高低和治理成本,考虑是否优先考虑治理增量的方式;多服务域之间的跨域访问,也要结合跨域风险和建设切面管控的治理成本,进行合理的落地策略。3)治理不”机会主义”,不要因为存在困难,而处于”等等看”,”后面再说”的消极,惰性行为中。要认识到治理的越早,其成本越小。

二、架构度量指标

架构问题有时不受重视可能就像“漏水的地下室”,不是不治理,而是不知道有问题。通过架构度量指标体系,可以帮助我们时时监测我们关注的指标,及时发现和预警问题,提供指标数据,驱动架构改善。

2.1 问题先于指标

架构度量指标初始定义了23个指标,并耗费了近1年的时间来建设完整的指标体系,最后只有1个指标(服务开发耦合指标)不经修改的被用于架构优化专项中,转化率较低。初衷是期望通过定义指标来发现问题,这体现在定义的指标数量多、覆盖广,导致人力成本和时间成本居高不下。当不知道要解决的问题具体是什么时,就陷入了高成本的数据采集、指标定义中。

从治理效率以及是否能更好的落地来看,更好的方式是先发现问题(区分问题和现象、具体的问题、可解决的问题),对问题进行定义和诊断后再制定相应的指标来衡量问题的解决状态。也就是问题先于指标:发现问题→定义问题→定义指标→度量问题。问题需要是具体的,有具体的解决方案的,而不是抽象的,概念性的。这里要区分问题和现象,问题是缺陷和需要解决的,现象不一定是缺陷和需要解决。

例如,1)“MR影响接口数”指标期望通过MR涉及的方法及其调用链路得出影响的对外接口数,影响的接口数量多就说明接口间耦合大。其中接口耦合的概念就是抽象的,无法联想到具体的问题,也就没有具体的解决方案。尝试通过指标去发展问题,就容易陷入把现象当做问题的窠臼。接口耦合度高的仓库归因来看大都是修改了较低分层所致,如工具类,dao 层。2)另外,“服务开发耦合”指标期望通过需求中服务的共现情况来发现耦合开发的服务组。服务开发耦合也是一个较抽象的概念,归因后发现是受微服务架构影响,单个微服务的功能粒度较小,一个大的需求势必要覆盖更多的服务数量,共现开发只是一个现象,而不是一个问题。

2.2 警惕指标陷阱

output.jpeg

《指标陷阱》详细列举的过度迷信指标带来的危害,本节强调以下3点:1)指标可能带来短期主义,大的目标往往很难去量化,能量化往往是局部的、短期的,如果组织只通过能量化的指标去考核,那么会导致那些无法量化的任务无人关心、无人去做,致使大目标的失败;2)指标可能会导致忽视经验价值;例如,美国的越战指挥官使用敌人尸体数量衡量战争局势,忽略了军队士气的决定性作用;3)指标可能会带来作弊;例如,医院如果考核医生的手术成功率,就会导致疑难杂症无人诊治;

我们发起的架构优化项目,就针对这些指标陷阱做了相应的对抗措施:1)明确告诉参与改造的研发,修改不是强制的,指标不是唯一的;要结合研发自己的判断来决定是否需要修改,我们重视指标结果,同时也重视研发的经验。2)允许研发对那些认为不需要修改的函数进行标记和说明,同时也会避免出现指标作弊,出现应付式修改。研发结合自己的判断,对那些修改成本过高、不再迭代、计划下线、废弃函数、监测异常、业务合理的函数标记为不修改,在优化项目组看来是可接受的,也是合理的。3)持续跟踪打标过程、Review打标结果、Review改造结果,防止研发在打标和改造过程中进行指标作弊。

2.3 不要迷信指标

迷信指标可以说是指标陷阱的一种,指标是对需要度量的事物的有限的表达,不一定准确和完整,它可以帮助做决策,但不是比不可少。

1)要明白指标只是从某一个维度去衡量好坏,而要度量的对象往往多维度的,一个软件往往是在一定条件约束下产生的,是多方妥协统一之后的结果。一个指标可能是劣化的,而对立的另一个指标可能是优良的。例如,单体服务和微服务他们在某些指标来看可能既好又坏,如伸缩性指标,开发耦合指标。

2)指标衡量的可能是一种现象,至于这种现象是不是问题,需要细分和观测,反过来确定指标对问题发现的置信度。例如,多个服务的开发耦合指标高,只能说明他们存在一起开发耦合的现象,需要具体分析是否是必要耦合,如是否是为了运行时伸缩、隔离做的妥协,是不是微服务拆分后大需求带来的必要耦合。

3)指标可能无法真实反应现实。要根据指标指标发现问题的准确性,来适时调整指标。如果指标无法真实反应真实情况,那么考虑下掉他。

4)有些创新型的项目早期也很难有足够数据去构建指标,此时指标就不是必须的。另外要评估Oncall的成本,亲自去值班体验下可能会得到比指标更多的信息;

2.4 定义好的指标

1)准确的和置信的。 如果一个指标是为了发现问题,那么通过指标发现的集合里的问题比例就代表指标的准确性。例如,我们通过服务开发耦合次数作为指标去评估服务间的耦合度可能就不是一个好的指标,因为服务开发耦合可能是因为“微服务架构下服务的功能粒度较小,一个大需求确实需要修改多个微服务”,这往往被认为是正常的;诚然,会有一些因为新增响应字段导致服务级联修改的非必要耦合,但是如果这个问题场景占比很小的情况下,这个指标就失去了准确性和置信度,也就变得没有那么重要和有意义。

2)简单的可理解的。 一个好的指标不在于说运用了很多数据概念和公式,而在于简单直接可理解。例如,归一化指标不一定受[0,1]区间的约束,而非把一个指标运用加权、缩放等公式放到该区间内。公式能被直接的理解,更能让研发直观感受到要度量内容的好坏程度。

3)在精不在多。 指标数量多了会带来较高的收集成本、分析成本和改造成本。这给指标使用者、监测者带来了更多的工作量。这时候要考虑缩减指标数量:a.需要分析下是否存在同质化的指标他们都在衡量同一个内容;例如服务端架构度量指标中的函数行数指标、函数认知复杂度指标、函数圈复杂度指标三个指标就是同构的,那么就考虑只保留一个,减少建设成本。b.需要分清主次,那些是核心指标,那些是非核心指标,哪些是必要的,哪些是非必要的;c.需要明确指标作用域,哪些指标是对外,哪些是对内。对那些同质化的、次要的、非必要的指标进行删除或减少。

4)是需要改进的。 指标建立只是第一步,建立后需要制定策略去改善他。如果一个指标制定后不去改善他,那么请把它删掉。关注不需要改善的指标,没有实际意义。如果确实需要观测一些无法改变的指标,那么请明确把它标记为“观测性数据”,与指标做区分。例如架构度量指标中的MR指标,只用来观测MR变化趋势和数量,这就是观测数据,而不是指标。

2.5 验证指标和警惕自证倾向

指标定义完成后,需要找试点服务进行有效性验证。验证其是否能有效发现问题(如发现问题比例)、置信度。指标验证过程需要避免自证倾向,别提前假设指标一定是正确的,它一定可以发现问题,然后带着预期去进行验证,这样会产生证实性偏差。例如架构指标在试点阶段,就没有避免自证倾向,前置认定指标是有效的,模糊了试点服务验证的目标,导致无效指标没有在试点阶段被拦截,造成后期推广阶段的人力开销。

2.6 架构指标和人效关系

在治理推广过程中,经常有研发会问到”这个指标改善后会提升多少人效?”。实事求是来说:大部分的架构指标(如函数认知复杂度、服务扇入扇出等)都无法得出与人效的量化关系。代码优化和人效收益之间的逻辑链路太长,不确定性和需要做的假设太多,无法做出有意义的量化。只能定性而非定量的去看他们的关系:架构优化对人效的收益是长期的,也只是影响人效收益的一个因素,整体而言可定性为有正相干关系。

架构指标使用的目的更多的是发现架构薄弱点从而去改进它。其作为观测指标来驱动架构改进,业务只需要关注和改进它发现的问题即可。

三、专项治理到长期治理

3.1 专项治理/运动式治理的优缺点

1)专项治理适合短期、临时、重大的事项推进,不适合常规性事务。代码治理是一项长期的工程师文化践行事务,不能完全寄希望于专项,而抱着毕其功于一役的想法。从更长期的角度来看,需要建设一套更为易用、自动化的机制浸入到研发活动中,使其作为研发活动的一部分。

2)专项虽然无法解决根本问题,但是专项有一个优点是自上而下的关注度、投入度,以及对架构优化的认可度。这时对那些权责不清楚、历史负债大、平时畏难的重点问题可以得到治理。

3.2 长期治理的思路

考虑到不同的团队有不同的工程师文化,那么代码治理的常态化方案如果要持久有效和长效运作,就需要最大限度的减少研发的治理成本。尤其是代码治理中很多治理项无法很好的量化收益的情况,降低治理成本尤为重要。

治理成本具体而言包括问题修改成本、验证成本和承担的变更风险。例如,优化高复杂函数任务,从2024Q3-2025Q2期间的存量治理和增量卡点治理来看,结果都没有达到预期。其本质原因是研发的治理成本很高(修改成本+验证成本+承担的风险)。现在看来通过LLM进行治理,结合单测覆盖和集成测试,研发只负责确认,这可以大大减小治理成本,提高修改率。

所以,长期治理的思路是:

1)让工具/平台承担大部的治理成本,研发仅负责复核和采纳治理结果。

2)易用、易理解、易操作的流程,减少研发的认知和操作负荷。

四、理想架构和实际架构的距离

我们用存储耦合指标发现了存在多服务多写同一张表的问题,而当我们与这些服务的负责人沟通时,大都表示”这是个问题,但是目前不会去改”。这表明在架构治理过程中经常会出现的一个现象,即便大家都认可什么是好的架构,以及架构的优化方向,但是在实际的迭代活动中,靠研发自发的去优化和改善是不现实的。原因大概有以下几点:

1)收益难以量化:架构优化类任务收益偏长期,且难以量化。无法量化的工作,在研发的工作表就会始终锚定在重要但一点也不紧急的区域内。用户价值 = (新体验 - 旧体验) - 替换成本中的用户价值小或者感受小,难以驱动优化过程。

2)改造成本高:在代码治理早期的推改中,主要依托于“平台识别问题 → 推送给研发 → 研发修改”的治理流程。改造成本主要在研发侧,具体包含修改成本、验证成本和承担的变更风险。若改造成本高,那么研发的修改意愿必然降低。

3)考核/奖励机制:架构优化在各团队的不同投入度,也反应出leader对该工作的看法。如果Leader在绩效考核时认可架构优化工作的价值和业务功能建设是等同的,那么才能激发研发的优化动力。

4)部分标准模糊/对于好的架构无法达成一致:在一些模糊地带,如什么是好的架构在软件界没有明确清晰的规则,另外随着软件规模,该标准也会变化。这就很难引导研发去做出正确优化。且在实践中,不同的部门实践也可能大相径庭。

5)理想架构脱离实际:有的理想架构偏学院派,可以应付小而美的Demo程序或小规模软件,但是无法应用到复杂的软件中来。理想架构如果仅仅是好的、符合审美的是不够的,还应该是易懂、易用。就如秦始皇强制推行视觉美的小篆失败,最终人们用脚投票选择简单易用的隶属最终完成书同文。所以,如《毛选》所说,要从实际出发,而不是从定义出发。

五、治果还是治因

无论是通过度量指标还是通过专家诊断发现潜在问题,在治理时一般都会拆分为存量治理和增量治理,其各自对应着治果和治因。

1)治果。治理已经发现的问题,通过指标的变化来衡量治理效果。

2)治因。根据对问题的理解不同、对原因可能无法达成一致,甚至会模糊直接原因和根本原因。例如,如果一个团队的超复杂函数比例比较高,其直接原因可能是需求压力大、没有code review、没有CI检测和卡点,根本原因则可能是基建能力不足、团队工程师文化缺乏。当然,有时直接原因和根本原因确实很难达成一致,这里区分直接原因和根本原因的目的在于使用5WHY(上游思维)来发现更上一层的原因,从更上游解决问题的成本更低收益更大(系统性思维中的正反馈回路)。

治理方案必然要结合治理成本及其收益来综合评估,选择一个多方可接受的方案。

六、如何驱动业务参与架构治理

从2024年Q3和Q4的架构优化专项来看,这种非自上而下的、非强制的架构治理专项,能有效推动业务参与治理主要有两点:

1)治理收益明显,业务愿意投入成本去治理;

2)治理成本比较低,业务可以用较少的成本完成治理;

那些治理收益不明显或投入较高的事项,业务治理意愿不高,往往会搁置任务不做处理。例如,服务开发耦合治理仅有1/30的改造率,远远低于预期。

这给我们一些启示:

1)选择何时的治理项:从用户视角出发,选择合适的治理内容,避免自嗨:痛点 > 爽点 > 痒点。

2)减小研发治理成本:提高工具能力和自动化水平,让工具承担更多的治理成本,减小研发的治理成本。例如,让工具承担修改成本和验证成本,这样研发只需要承担很小的确认成本和风险。

评论