代码质量提升的推手-----没有捷径、银弹,主要是回归人性、聚焦技术扎实落地

代码质量提升的推手

                                                                                                                               -----没有捷径、银弹,主要是回归人性、聚焦技术扎实落地

【内容摘要】通过对谷歌实践和华为现状的分析,给出对代码质量工作的方法和建议。

1)   代码质量氛围:聚焦人性,构建以“模块级代码重用”为核心的软件工程师文化、创新文化。

2)  代码架构:以架构为核心,以文件夹为载体,实现业务、架构、技术、组织统一&扁平。

3)  代码开发过程:Committer E2E承重,确保最终代码质量。

4)   工程工具:研发工具起源于,软件工程师20%的技术创新活动。

 

【正文】

推手,明明是一套专业拳法,但是却被很多人理解成一个管理手段。我们在代码质量提升工作上似乎也是这样。引用编程大师Kent Beck的问题:你们是在简化管理,还是在简化开发?

高质量的代码必需经过“编程思想”、“编程规范”、“编程实践”的洗礼才能铸成。如何才能做到?下面将结合谷歌的案例,我们一起做一次深入思考。

6年以来,我一直负责代码质量提升工作,也在洞察业界的做法。谷歌作为业界传说,有人认为谷歌有最牛的、最自觉的软件工程师,谷歌有高效的工程环境、有自由&开放的工作氛围,所以他们就能搞好代码质量。真的是这样简单吗?通过我对谷歌软件开发工作的逐步了解,包括与谷歌一线开发技术骨干的深入交流,我得到了不少启发。下面就从我了解到的谷歌的7个故事开始:

A. 高效类

故事1:XX工程师接到一个开发任务,原计划是1.5个月完成,通过“代码重用”,实际4个小时就完成开发。

故事2:涉及10多个特性、模块的需求,0联调、维护阶段0联合定位。

谷歌观点:

ü 不良代码增加了演进和维护成本,导致生产力持续下降。

ü 减少软件维护工作比减少实施工作更重要。

B. 高质类

故事3:几乎所有产品的所有代码(20亿)共仓,任何工程师都可以修改任何一行代码,不用担心会引入问题。

故事4:代码提交环境,Committer审视出的问题数只是个位(其中多数还是优化建议),大量问题前端解决。

谷歌观点:

ü 开发对质量负责

C. 过程类

故事5:X特性因为提交的代码没有通过代码Review,无法入库,最终推迟发布。

故事6:Python语言的发明者在入职初期因为代码质量不合格被训战17次。

故事7:X工程师(新员工状态)提交1K代码1个半月,才通过代码Review。

谷歌观点:

ü 质量更像是一种预防行为,而不是检测,质量是开发过程的问题,不是测试问题。

 

这些故事让我们理解到:谷歌聚焦演进、维护等开发成本降低;E2E开发效率提升;问题前端解决、修改代码不引入问题等长远目标,牵引代码质量提升;共仓、代码重用等工程方法,使编码工程化,构建了软件工程扎实落地的基石和脉络;开发对质量负责,提前解决缺陷,促进了高质量代码的高效产出;严格的过程管理了无论是业界大牛,还是小白,入职谷歌后都会受到技术、质量约束,不能为所欲为。可见,代码质量提升在谷歌是一套系统化工程,绝不是简简单单几个KPI晾晒等管理手段,就能做到的。

这些故事,也正是我们一直追求的梦想,但是为什么我们的代码质量工作却成为了一次次运动,不能长治久安?下面我们通过谷歌的相关实践一起来探讨、追寻答案。

1         代码质量氛围:聚焦人性,构建以“模块级代码重用”为核心的软件工程师文化、创新文化

人性:现代社会学家认为,世界上只有一个人类,只有一种人性。这是不同民族之间能够交流、达成理解的前提。人性中存在对生存的渴望、对胜利的渴望,对伴侣的渴望、对同类间自己地位的关心,及对同类帮助的冲动。

谷歌正是聚焦人性,将工程师的注意力专注到“技术影响力”,才构建了真正的技术氛围:

1)    工程师文化:谷歌树立了以“模块级代码重用”为核心的工程师文化,组织上认可重用和被重用都有高绩效。

谷歌为什么要这么强调代码重用?

从代码设计来说,支持可维护性的同时,提高代码的可重用性是核心问题。

代码重用,我们通常称为代码复用。使用代码重用技术可以减少代码开发活动中大量的重复性工作,这样就能提高软件生产率,降低开发成本,缩短开发周期。同时,由于重用的代码都经过严格的质量认证,并在实际运行环境中得到校验,因此,重用代码有助于改善软件质量。

把一个功能写成一个模块,以便当再次需要相同功能的时候,可以直接使用,而不用重新开发,这是 “模块级代码重用”。

模块级代码重用遵守了“开闭原则、单一职责原则、组合/聚合原则、里氏替换原则、迪米特法则、依赖倒转原则、接口隔离原则”这些原则,就能使代码在更高层次上提供代码的可重用性—可维护性的模块级代码重用。它有别于“代码的剪贴重用”、“算法的重用”、“数据结构的复用”等这些重用传统的代码重用方案。传统的重用方案有一个致命缺陷就是重用常常是以破坏可维护性为代价的。因此,遵守了设计原则的模块级代码重用,能提升代码重用性的同时,提升代码的可维护性。这是绝大多数程序员、软件组织所追求的。

a)         工程师角度:“模块级的代码重用”促使所有软件工程师不断思考,我写的代码是否可以被别人用,从而驱使他输出的代码不断被抽象&内聚&封装、保持代码规范一致性(不可读的代码,用得不放心)等,从而促使代码质量的持续提升和自己的编程技术水平不断突破,展示出自己的技术实力。另外,软件工程师在实现需求时,会思考是否可以重用别人的代码,从而使他的开发聚焦非重用部分的代码实现,减少了质量保证的范围,质量活动更有针对性,从而使他的代码质量更好、开发效率更高,最重要的是这种实现方式更受组织认可。

b)         组织角度:“模块级的代码重用”使组织看到了代码重用带来的质量提升、开发成本下降、开发效率提升等工程能效,这些利益不断驱使组织思考如何让员工更好地重用/被重用代码。因此,组织在构建软件工程能力、工具环境、组织&导向等方面持续投入。如:设立了编程语言专家角色和组建了专业团队,深入研究设计模式、编程语言、LLT等编程实践,持续牵引和帮助软件工程师提升编程能力、加强组织的软件工程能力;基于模块依赖的自动化可视化CI,使每一次提交获得快速反馈。

而我们几乎不谈重用,就是提重用也是传统的重用方法(“代码的剪贴重用”、“算法的重用”、“数据结构的复用”等),以牺牲代码的可维护性为代价。因此,我们这些年倡议的“好”代码工作,丢失了“代码重用”终极技术目标和追求,没有建立好代码与组织个人之间的利益链条,因此,很多人(组织)有疑问:我们为什么要写好代码,有什么好处?这些人(组织)从本质上就失去了搞好代码的意识和动力,一切都是满足组织的考核要求。这种情况下,就出现了各中代码质量KPI,追求KPI的同时,给开发带来了更大成本投入,而得到收益非常有限。例如:

案例1:XX老员工说我们以前把代码写了,功能交付客户没有问题,不是挺好吗?现在这么折腾有必要吗?

案例2:A员工说:“这块代码又有告警了,慢慢清吧!” B员工闻讯,过来看了看,向A说:“怎么代码老出这个问题,记住了下次别这么写就不用改了。另外,下面这个函数写得也有XXXX问题。”A说:“先清了就行,其他的后面再说。下面这个XXX问题,没有报警,就先不改了,免得改错,以后再说。”

案例3:N年前,公司聘请的XX顾问,评估公司代码后,结论之一是代码重复率85%。这样重复的代码带来的最直接问题就是一个Bug很多地方都有,只要有一处漏改,可能会产生一个严重的质量事故,这些年网上问题中出现不少。

案例4:XX产品在消除代码重复率重点工作下,将很多特性都会用到并且自己用代码实现的一个功能,提取成了公共组件。重点工作结束时,该项目因为XXX行代码下降等一系列收益获得了组织嘉奖。但组织上没有像谷歌一样设立重用和被重用的更受认可的绩效导向,而是行政命令安排一个特性组维护这个公共组件。时间消磨一切,当领导不再关注这件事时,该特性组就越来越排斥其他特性组提出的新增和修改需求,协同开发变得异常困难,最后有些特性组复制一份代码又开始自行演进。

2)    创新文化:谷歌的20%创新设立,来源于员工利用工作之余的时间创造了新的产品和特性(百度搜搜就能知道)。但在全公司推行以来,发现能做到产品创新的人很少,慢慢的20%创新发生了演变,大多数工程师通过技术创新获得技术影响力。有了技术影响力才能获得更多升级、加薪的机会。

               而我们更多的还是关注产品、业务创新,对于代码实现更多关注功能是否满足交付要求、网上不要出问题。但似乎效果一般,广大软件工程师在产品和业务创新上乏力,而网上的问题又不断,不少问题的解决还往往需要N多组织的人拉通、封闭定位N天,才能找到是谁的责任,什么时间能解决还看是否在责任田找到真正合适的人来修改。为什么不能在前端给大家提出一个更高技术的要求、制定一个明确的技术导向、提供一个更宽松的技术环境,将问题消灭在前端。

我们中还是有想要应用新方法新技术新工具等抗住压力、创造性解决代码问题的人,我们在日常聊天时都谈到了,但是他们没有“力量和勇气”到能够让他们真正的付出行动,因为这种想法一次次被“交付计划”给压制。组织没有给大家创造一个“创造性解决问题的环境”,使大家没有资源、时间、得不到周边的理解等等。例如:

案例1:XX软件高手负责的一个责任田,问题多得下面的兄弟都受不了了,他主动找主管请求用XX设计和XX方法重构这个模块。主管权衡再三,决定先放放,把那几个需求交了,后续再说。结果需求实现后,就没有后续了。高手再次找到主管说:“不重构,只能离职了,无法面对兄弟再这样干活,改不完的问题单”。最后,主管真的怕他负气离职,才被迫同意。

案例2:XX年XX产品重点工作“解耦、组件化”落地后,XX责任田Owner感慨说:现在业务模块似乎清楚些了,但怎么还是不好增加新需求、修改问题单还是容易引发问题、模块之间问题定界也不容易呢?形式为模块,神则没有做到抽象、内聚、封装、重用,当然出不了效果。但是,没有人天天琢磨这些。

3)    匹配工程师文化和创新文化,谷歌任职/绩效评定导向是80%的时间满足组织安排的工作交付,考评优秀来源于20%时间的“技术影响力”带来周边同行评价。这条规则使技术这个过程结果成为了评价的重要依据,从利益上牵引大家通过“高新技术”提供“更好质量”、“更高效率”的开发成果。无论管理者,还是技术人员都遵守这个原则。

而我们在安排工作时,就基本确定了对应的绩效,例如:

案例1:XX工程师被任命为Committer,Committer的职责刚开始落实,主管跑来说,有个XX紧急需求,你来搞。临危受命,一番努力,半年后成功交付,绩效为A。他感慨:“Committer工作繁琐、还要不断影响他人,最后不好体现结果,还是紧急交付来得直接、简单。”

案例2:XX员工感慨说:“每年,团队中各成员相互从工作分工安排,就能看出哪些人是打A潜质。要做技术黑马很难。

4)    谷歌的激励除了奖金和工资外,大家更看重能帮忙自己进一步提升技术能力、扩大影响力的非物质激励,比如:和技术大牛一起“喝咖啡”等。这样做是因为他们想通过这个活动向大牛推荐自己、向大牛学习技术经验、向大牛了解未来更多的技术发展方向等等,总之就是为了自己的技术影响力、职业发展。

而我们,某产品线过去2年选拔出来的89名“软件高手”、“软件精英”的代码提交情况,发现其中有17人在最近一年之内没有代码提交的记录。而在有代码提交记录的72人中,又有25人在最近一年之内的代码提交次数少于5次。也就是说,在成为代码高手两年后,仅有52.8%的软件高手们还活跃在编码的第一线。由此可见,大家不愿意或者没有办法,沿着编程技术走下去。与是否用金钱或者荣誉来激励没有关系,关键是如何构建一个技术成长氛围。

总之,我们更多看软件工程师交付的代码是否满足功能需求,以及上网后是否会有问题,没有关注代码的实现过程和实现技术是否最优,传递的是“业务交付导向”,没有真正营造出“技术成长氛围的工程师文化”。从而促使,软件工程师追求的是代码实现的基本/一元质量要求,没有针对技术的“魅力质量”诉求。

ü 导向交付:搞好代码没有本质利益关系,个人没有意愿写好代码,加上编程实践缺失或者执行不到位,更写不出好代码。

ü 导向不出问题:导致遇到问题时,大家不是聚焦技术,最先最优解决问题,而是相互推诿,定责等。

所以,我们需要重塑软件工程师文化,用“技术影响力”激活组织和个人的活力,营造“聚焦人性(弱化管理指标牵引,强化技术工程方法的约束),以技术为核心的软件工程师文化、创新文化”。

2         架构:以架构为核心,以文件夹为载体,实现业务、架构、技术、组织统一&扁平

谷歌在“模块级代码重用”的导向下,20亿行代码共仓,所有近200款产品共享重用。它是以文件夹为载体,实现了模块边界可视化、具体化,借此抽象出一系列的规则和技术要求:设计规则、依赖规则、Build规则、测试规则、开源/第三方规则、编程规则等等,再对应上组织进行看护,模块和组织绑定。实现了业务、架构、技术、组织统一,保证了产品实现要求和技术得到长期的积累、演进,功能模块不断稳定、重用,提升产品在开发方面的竞争力——质量高、效率高、成本低。

组织角色的分解:将“代码质量”看护职责,拆分成四种角色看护:文件夹Owner、编程语言专家、Reviewer(LGTM)、SWE,每种角色都要编码交付需求,靠技术影响力晋升。为什么要拆分成这么多角色、搞这么复杂?一是术业有专功,擅长一个方向,可以通过他带动其他人;二是大家很忙,一个人搞这么多职责短期内无法快速积累能力,无法满足组织的需要,但组织能力成熟度高后,这些角色会归一到一个人。当前,谷歌1个人拥有2~3个证书的人很多。

1)    每个文件夹根据需要设置文件夹Owner,对文件夹完整配置负责、交付质量负责,将一个产品的质量、效率、成本诉求,通过“契约开发”方法拆解到一个个文件夹,实现矛盾的分而治之,使问题解决更加简单、直接,并形成文件规范,是责任田内软件工程师的工作的基础要求。

a)    通常一个项目有1个主Owner(对特性很了解的文件夹Owner),他会负责项目的规划;还有1~N个相关的文件夹Owner支撑。

b)    文件夹Owner离开时,指定下面有经验、有能力的人继承,保证经验、能力得到传承。

 

2)    在技术方面,设立编程语言专家角色,它由专职或者兼职的软件工程师担当,对编程实践给组织带来的代码的质量、开发效率、开发成本负责,对责任田氛围内的软件工程师提出编程规范、编程实践要求,并对上库代码遵从度负责。在这三个方面取得成果的工程师,靠技术影响力获得该证书。没有人数限制,鼓励人人成为专家,同时营造了人人写好代码的氛围。

3)    文件夹Owner和编程语言专家的要求如何得到有效执行,光靠他们远远不够,因为他们也要交付需求,还要研究技术,无法保证下面那么多兄弟会落实这些要求,因此,设立了Reviewer角色,它对文件夹Owner和编程语言专家设定的规范要求落地执行负责。他们是遵从这些规范要求最好的人中,由文件夹Owner和编程语言专家授证书。没有人数限制,鼓励人人成为Reviewer,也营造了人人写好代码的氛围。

以“技术影响力”为牵引核心,建立统一升级、任职通道,使各角色统一到围绕“代码”的成长路径,都是懂代码、会编码、善编码的人,就是管理者也是由此路径成长出来的。大家对于什么是代码质量、开发效率都有统一的理解。因此,谷歌的各角色(管理、开发类)对于代码质量的看护很容易达成一致意见,并且各角色绩效和任职也由技术贡献统一:

a)         SWE、Committer绩效评价:来源于项目管理者的交付评价 + 技术组织的工程师互评(相关的高级别工程师的看法):                                                  A = 80%的正常的需求交付结果(最高B+)+ 20%技术影响力结果(责任田在技术上的变化、培养的下面的人等)

b)         SWE、Committer任职:项目管理者推荐(业务交付好)  +  技术组织的评审结果(技术牛)

c)         LM、PM等主管绩效和任职:每年要有技术影响力的工作呈现

回看自己:

设计:一个模块为什么这么设计,往往没有人完全清楚,需要增加需求或者修改问题,都采用保守疗法----散弹式、倾入式等,这样会进一步破坏封装,导致代码复杂难懂。因为我们没有机制保证这些设计要求的传承。例如:XX产品针对XX模块开展LLT时,同一个桩需要在代码中23处地方出现。这个代码情况,就是典型的缺少代码封装、重用。

开发:我们代码只要功能没有问题马上可以上库,谁会关以代码可读性等方面的好坏。

例如:非常多的人,包括我们不少编程骨干都觉得下面代码结构很好(我们说的是结构问题,如果有其他问题先放放):

http://image.huawei.com/tiny-lts/v1/images/9779625f24d426122a7c_614x137.png@900-0-90-f.png

根据代码同一水平线原则;业务逻辑与算法分离原则,代码应该写成下面这个样子。从这个例子,就可以看到我们有多少人在研究“好代码”的写法。

测试:历史上先后推广过UT、LLT、DT等开发者测试方法,但这些方法,没有一个真正在基层团队开展好,普遍反馈是成本高、发现不了问题、方法没有用。但基层团队有几个人花时间,认真研究过这些方法,都是应付管理指标。因为大部分人的落实这些方法的时间点是在代码调试甚至集成、系统测试结束后,迭代交付/TR4/TR5过点前,目标是满足流程过点要求。而谷歌的LLT理念是:LLT是设计的一个要求。可见他们是从设计环节就在开展LLT活动。谷歌之所能这样,就是他们的编程语言专家不断在研究编程实践,并不断扩散这些能力和要求。以我们现在的编程能力,“编程语言专家”角色设立和牵引,时不我待。

分工:我们不断的围绕代码质量看护职责设置角色,但对代码质量真正承重的就PL一个,并且这些角色中起来越多的不直接参与代码的产出。我和很多PL聊过,他们每天都有很多拉通、交付、人员管理等工作,在代码上投入的时间非常有限,根本无保证代码质量。我们的代码生产环境就像一个繁忙的路口,没有交警、红绿灯、摄像头等执法,完全靠司机自律自觉。结果是我们老外在自己国家开车老实的人,在这也是各种穿插,违反规则;而路口,经常堵车、事故频发。

角色分布:相比谷歌就两类角色,我们协作复杂、高手都不为代码质量真正承重。

从团队时间投入:我们项目管理、需求分析、拉通协调时间占用非常多,反而模块设计等活动没有或者很少开展,所以我们的代码抽象、内聚、封装、重用程度比较低、代码复杂度高,从而导致我们“项目管理、需求分析、拉通协调”等事,非常难办、耗时。并进入了恶性循环。比如:XX产品一个网上问题拉着骨干10+人在会议室里封闭半个月,每天搞到凌晨;某产品LM和PL团队过点问题单保DI值,每天审视问题单关闭情况到凌晨;XXX开发人员接到一个需求,为了搞清楚需求,经过逐层咨询MDE→DE→SE→MKT,都讲不清楚细节,最后MKT的人说:你是业务的实现Owner,你应该最理解用户想要什么,你自己好好想想……结果开发人员都没有想明白需求,就开始编码了,质量可想而知。而谷歌,他们是Committer三种角色充分承重,E2E主导和牵引软件工程师开展工作,把问题预防、发现、解决在前端,进入了良性循环。下面是活动时间分布对比,华为和谷歌的编码时间都不长,但一个是有质量保证的编码,一个没有质量保证的编码:

[J(1] 

建议:

1)         以技术为中心,将文件夹(架构)、开发组织对应,设立文件夹Owner持续看护业务要求、编程语言专家看护最优实现、Reviewer深耕组织,保证质量要求落地。逐步把过去多个角色的职责归一,角色承重落实到位,减少开发协作环节、减少知识传递的损耗,提升开发效率和质量。随着能力积累,将出现一个真正的扁平化组织:

2)         建立Committer生态,持续度量每个软件工程师的每一次代码提交情况,用技术数据可视化每一个工程师的技术实战能力,使“技术影响力”成为软件工程师生存、发展的基石,持续将组织演进成技术型组织。

3         代码开发过程:Committer落实神三能力,E2E承重,确保最终代码质量

看下图:

ü  谷歌的每一行代码都要经历这个过程,才能被产品应用,没有捷径。

ü  整个开发过程中,从动作节点看,没有“新”的、“特殊”的动作。

这些动作在我们的流程中也都有定义,但我们更关注交付结果(基本/一元质量),忽视了这些动作是否执行或者执行是否到位。比如:模块设计动作,大量的项目组都没有开展,因此在没有设计代码方案的情况下,不断实现一个又一个需求。前人没有定下设计原则和设计方案,后人不明白前人为什么这么做,随心叠加代码,导致代码耦合、臃肿,质量不断恶化。

谷歌的Committer三种角色以“专业能力”看护整个代码生产过程,就像道路中的红绿灯、摄像头、交管部门,让道路有序、高效通行,及时识别并通过技术手段解决问题。

再看谷歌的一个质量观点:质量更像是一种预防行为,而不是检测;质量是开发过程的问题,不是测试问题。可见谷歌对于过程的重视。

而我们现在主要是依赖主管发起的KPI晾晒、赛马 、QA的监查等管理手段来驱使开发人员达成目标,但换来的是指标变化,代码质量和代码的生存环境没有变化甚至变得更坏。比如:我新眼看到一个员工为了达成函数超长的控制指标,他硬是将一个方法拆成了三个,结果是更让人无法理解了;双如:XX产品需要减少代码规模,控制新需求交付的代码量。XX员工在此导向下,将三句话(三个变量定义),变成一行,这种方法,让规模达标。直接影响了代码可读性和可维护性。

这样的例子很多,大家在紧张的工作中觉得只要达到这个甚至超过这些目标就很好了,没有人关心技术细节,但最终无论是管理者还是开发人员都会面对一个现实,这个目标除了带来开发成本的上升、质量下降外,其他什么价值也没有(无法发现问题、无法提升开发效率……)。但组织依然需要我们持续改进这些问题怎么办?就不断的换Owner、换工具、换工程方法名字……总之,各种管理手段层出不穷、老酒换新瓶,就是很少有人真正研究这些技术和工程方法,浅尝则止。

 

下面,我们聚焦代码质量保证动作,再看谷歌的具体玩法:

1) 需求&设计:80%的工作是基本要求,20%来营造自身技术影响力,是升级、加薪的来源,把组织和个人的长期和短期的管道平衡出来,不是一味交付

a)         保证高质量的开发结果,开发团队和需求提出者共同决策、制定计划,需求提出者对交付的业务结果负责,开发团队对交付质量负责:谷歌的需求也很多,人也很忙,他们通过impact driven和data driven,决定一个项目/功能是否需要,来控制开发管道的量。任何一个人提出并想落地一个需求,他必须用数据来说明这个需求的价值,并对需求上线后的效果负责。开发团队会评估他的数据价值的真实性,达成一致,即可开发。除了谷歌,在国内,我们和互联网公司(今日头条、腾讯等)交流过这个话题,他们也是这样做的,按他们话说,就是老板提需求也要经历这个过程。如果开发团队按要求交付了需求,如果没有达到预期的业务效果,影响是这个需求提升者的影响力,而不是一味追究开发团队。后续开发团队接他需求的可能性会降低。这样每个需求提出者对自己的“观点”会很谨慎,需求量得到有效控制。

b)         PL/EL会定期将相关模块重构工作落入需求交付计划:在谷歌管理者(PL/EL)也都是技术人员成长出来的,会定期主动审视相关特性的开发情况,如果发现有质量、效率下降的隐患,并且相关团队长时间处于强交付状态,他们就会主动规划代码重构计划,并落入交付计划中,牵引团队完成重构。

c)         需求的分析设计由相关的最强的文件夹Owner牵头负责:需求相关的最强的文件夹Owner直接、平等的与项目交付者共同制定交付计划(权衡进度、质量、成本效果),并主导需求的分析和设计,相关的文件夹Owner支撑,最后各文件夹Owner按契约化编程完成模块交付

d)         LLT是设计的一个要求:在模块设计时就会考虑代码的可测试性、测试方法、测试策略等,并且文件夹Owner、编程语言专家会重点评审。

而我们:

a)         需求规划时,“编程技术”、“人员成熟度”等因素考虑偏少,一切都是倒排。例如:XXPL说:“主管要他带个项目,就给了2个工作1年的,其他都是刚来的。计划和老员工的时间点一样,规模也不小。”

b)         做需求分析和设计的人,不做开发,不承重。通过串讲、反串讲,向开发人员传递相关方案,效果无法超越721模型的定义:交流最多20%的效果,只有实战才能到达70%。例如:开发人员和设计人员潜在矛盾由来已久,我经常听到下面说法:

设计人员说:“开发人员能力不够,不能很好理解和实现设计方案。”

开发人员说:“设计方案不接地气,不考虑实现业务情况,无法实现”

c)         模块设计会做的团队不多,做得深入的团队更少,白盒测试设计就更不用说了。例如:我们一个特性涉及的模块依赖关系能可视化呈现的团队又有几个?

2) Code:开发与测试同时进行,严格自律编程,绝不重复造轮子

a)         先输出测试用例和测试驱动,用于自己开发后的测试和提供给需要的人支撑其独立测试

b)         谷歌鼓励重用,几乎所有的项目维护在一个统一的代码库中,方便搜索代码和API的迁移、重用、重构,避免重复造轮子。

c)         严格自律的编程文化,清除编程规范问题、尽力将代码写到最优,每个工程师都很理解Committer提出的各种要求,他们会主动在提交前达到这个要求,并且为了构建自己的技术影响力会主动创造性的达成这些要求

而我们:

a)         XX员工说:“代码都写不完,哪有时间搞测试(LLT)”。

b)         代码重用,是最初级的复制粘贴,工程和组织环境等不利于高级别的代码重用。例如:XX牛人说:“模块重用基本不可能,没有人为了你抽象、没有人为了维护,因为对他没有好处。而我为了重用他的模块,到处协调、推动。时间很紧,最后只能把代码复制过来,改改就用。实在不行,自己仿制一个,都比推动快。

c)         开发人员对于前人为什么这么写代码,都搞不清楚,为了进度只能为所欲为。例如:XX Committer说:“每天有几十次新员工问他,相关代码为什么这么写,可惜他也不全懂。”

d)         代码检视更多关注的是业务设计是否有缺失,因为担心交付后业务功能会有问题,但对于编程规范和编程实践等代码质量无暇考虑、或者考虑很少。例如:XX产品一个switch有300+ case,是历年来不断叠加的结果。目前维护的人员说:“每次修改都如履薄冰,稍有不慎就会引入大量问题,能不动原来的,就不动了”。回看谷歌,任何人可以修改任何一行代码,不用担心引入问题。

3) Build 源代码级交付,产品依据模块依赖关系自动化编译构建,高效、同源可追溯、二进制统一

谷歌的20亿行代码共仓,几乎所有产品共享重用这些代码。每个产品都是通过持续集成系统,按模块依赖关系,用源码自动构建。该系统起源于开发人员20%创新活动,是技术影响力牵引的产物。

为什么开发人员会做这个?因为Committer对于每一个文件夹(模块)都有详细设计原则、规范要求等。可以基于此抽象建模,从而构建一个统一的持续集成环境。

谷歌构建的效果:

a)         全公司基于主干(Main Trunk)的开发-- 2 Billion LOC、1Billion File,支撑几万工程师和40 Offices

b)         Hermetic  从Source (at head)  分布式构建

c)         普遍使用Feature Flags

d)         工程师40k commit/day  机器系统24k/commit/day

e)         Bazel 构建800k/day ,2PB of Output

而我们:哪个产品的头文件/依赖关系,谁能讲明白,构建效率如何能从代码本质量上提升?比如:XX部门XX年重点工作:编译构建效率达成1个1,但有一个关键头文件,依赖关系非常复杂(30多个依赖,而且嵌套),没有人清楚为什么这样,当时试着改了一点,结果是版本几天没有编译出来。后来时间太紧,只能依靠增强编译环境,并且减少了编译规模,才达成了目标。

4) Test:测试比开发更重要,测试驱动开发

a)         谷歌开发自测的目的:除了保证功能正确性,防止别人改错自己的代码外,作为API使用的样例,利于模块级的代码重用。

b)         UT为主、IT、ST为辅,大量的时间(甚至比编码活动时间长)用于测试,测试代码比产品代码多,测试集成到CI

c)         每个文件夹和项目有自己的测试 (包括Canary Test)和Owner,长期积累模块的测试能力

d)         大规模测试自动化反馈,大部分服务于个人:代码库非常庞大且不停变化,如果测试失败,系统会提供哪次变更导致失败,同时也保证秒级~分钟级的测试反馈。依靠文件夹依赖关系,依赖风险自动识别,自动启动上游风险用例,不破坏其他人已经合入的代码。

e)         工具会详细分析测试设计和结果,会建议增加、修改测试用例,保证测试用例健康:可信测试99%+

而我们:用开发人员的一句话来说,编码都没有时间,哪有时间做测试。白盒测试活动很多情况,是为了过点在补充用例、获取覆盖率,数据好,没有效果。更没有测试能力的长期积累。因此,大家无法感受到测试带来的收益。

 

5) 代码质量分析  用于软件工程师自我持续改进,展现自己的技术影响力

谷歌有一个代码质量分析平台,会自动针对每一个change list开展代码质量分析。不同的项目、不同文件夹在公司统一的规则下,都有差异化的代码分析配置,主要项目如下:

a)         集成、系统、DFX测试分析

b)         代码静态检查

c)         测试覆盖率、用例分析

d)         Changlist、设计文档等有效性分析

e)         代码缺陷自动分类与跟踪,缺陷修复推荐

每一个软件工程师在提交代码前会详细审视分析结果,只有完全符合规范要求了,才会发起向主线的代码提交。

而我们:

a)         门禁主要是做静态检查,没有针对代码质量的全面分析。例如:XX产品有对LM及以上组织进行软件能力和质量的打分排名,但员工需要关注自己的情况时,确没有相关数据。

b)         现有的代码质量度量平台,更多支撑管理者审视、晾晒、排名,无法支撑软件工程师日常代码质量改进。例如:XX产品的静态检查问题每个版本都要整改一次,为什么同样问题要反复解决,不能转化为软件工程师的编码能力,一次把代码写对。

 

6) Review:强制人工Review,满足业务、编程要求,才能上库

作为代码上库的最一个审核环节,是对文件夹各种要求的集中审视,而不仅仅是审核代码。

a)         所有上库(机器和人写的)的代码都需要通过人工code review,且必须同时三个Committer角色签名,否则无法上库

b)         三个Committer角色,就是前面“组织架构”一节中提到的三个角色:

                                       i.      文件夹或项目的owner:看护业务要求,主要是业务设计要求、测试要求等一切保证业务正确、高质量实现的要求

                                     ii.      编程语言专家:看护编程规范和编程实践,主要是代码格式、测试方法等一切保证代码质量、开发效率的方法和能力

                                    iii.      LGTM:专家助手,保证文件夹或项目的owner和编程语言专家提出的要求无偏差落地

c)         时间来源:项目计划中考虑人员能力风险,提前规划了检视时间;可以是20%创新时间来开展此活动

d)         通常各Committer角色 24小时内完成检视任务(特殊情况可以3~6天的检视时间),以收到检视通知为开始

e)         检视规模:提倡small code change。75%的change在99行以下。small (<99 lines), medium (<250), large (<999 lines),extra large highly discouraged。

而我们:

a)         紧急情况,主管一句话”先入库,后面再改“,代码就上库了。然后,就没有然后了。

b)         检视也只是检视代码,没有代码设计、测试效果等综合检视。例如:XX产品有些团队实现LLT后,说LLT没有效果。我去检视他们LLT用例时发现,大量用量没有断言。可见,他们没有人关注用例的实现质量。

c)         Committer数量少,而员工的提交数量非常大,且自己还有大量需求要交付,没有时间和精力投入。例如:XX产品Committer的交付量是普通员工的2~3倍。

d)         检视规模,多年来是各产品线一直在优化控制,但目前为止很多产品的平均提交规模依然以K为单位(甚至平均2~3k及以上,谷歌认为大于1K的代码,是无法检视的),这样的规模基本使代码检视活动无法有效开展。

建议:

a)         大力优化Committer机制,把当前Committer只是一个代码审核者,扩展到整个开发过程中承重,从提出设计开发要求到测试效果保证,最后审核上库,一条龙做好看护,E2E的保证代码质量。否则,问题集中到上库前,再牛的Committer也搞不定。

b)         设置Committer三个角色,充分人员专长,快速提升组织能力、卷积更多人为代码质量负责。

c)         针对当前代码情况,设立代码重构机制,持续牵引重构优化,使Committer组织与代码架构的优化共同成长,重构一个看护一个,稳定一个。

4         工程工具:研发工具起源于,软件工程师20%的技术创新活动

工具开发者,就是工具的使用诉求者,保证工具的有效性:谷歌最牛的人开发工具,大量的研发工具起源于,软件工程师20%的技术创新活动。受认可后,部分复杂的工具会由该软件工程师带领专门的工具团队演进、维护。这样的工具自然能够真正解决软件工程师日常工作中的问题。例如:编译构建工具、各种专项静态检查工具等等。

而我们:

a)    工具的开发团队对业务团队的真实使用情况不了解,开发的工具不实用、不好用。因为他们自己也不用。

b)    工具的长期演进和维护不能连续,避免不了试点-推广-全面应用的三阶段命运

c)     工具开发团队在业务线没有技术影响力,无法统一大家思想,最终在众多想法和诉求下,曲折演进

建议:

a)    营造技术影响力氛围,牵引软件工程师创造性解决问题,主动开发工具,公司对于有通用性的工具做战略投资,认可技术贡献和绩效。

b)    公司只定义通用规范要求,各产品各文件夹各项目定义自己的要求集和要求

5         解决方案 没有捷径、银弹,主要是回归人性、聚焦技术扎实落地

回顾上面谷歌的实践和自己情况,可以看到谷歌Committer角色职责,也分布在我们多个角色职责中、谷歌的代码开发流程也和我们一样、相关技术方法我们也一直地落地等等,为什么效果没有谷歌那么好?我觉得可能是我们建设能力时,太过于理想化,忽视“人”、“技术”的本质及它些技术存在和发展需要的生态环境,而是妄图通过大棒(简单的管理手段KPI驱使)+胡萝卜(树标杆、发奖等)政策获得额外的技术收益。

所以建议:代码质量提升没有捷径、银弹,主要是回归人性、聚焦技术扎实落地。

方案角度1:从技术牵引来看

1)         设立1个长期技术目标:模块级的代码重用

2)         从代码架构、代码质量、导向氛围,识别支撑长期目标达成的3方面关键抓手:

a)         代码架构:将模块这个技术名词定义成边界明确的“文件夹”,并形成技术要求和规范,实现设计方案与代码实现的统一,使代码架构明确化、可视化

b)         代码质量:确定公司统一的编程规范(保证代码风格一致性,便于代码可读,提升可维护性、可重用性)+业务团队自己确定的编程实现(实现代码的艺术,保证最优方式实现,提升代码的魅力质量);公司建立统一的代码质量度量分析平台,能够帮忙每一位软件工程师评估每一次代码提交的质量、识别编码能力短板,帮助员工持续提升交付质量和自身能力

c)         导向氛围:以Committer为核心,建立代码重用和被重用的高绩效氛围,驱动软件工程师不断的创造性解决代码质量问题、提升自身编码能力、加强相互协作,提升开发效率和质量。

3)         从架构技术、流程组织、工具、氛围四个方面,落地4个关键措施:

a)         基于文件夹的自动化测试:实现架构设计与代码实现的统一,使代码实现做到抽象、内聚、封装,成为好代码;建立白盒测试防护网,快速反馈测试结果,保证修改不引入问题;测试用例和测试驱动是模块级代码重用的样例,保证代码的可重用性。

b)         Committer机制优化:传承设计原则、编程原则等要求,使技术经验不断层、不缺失;以Committer为技术核心,带动整个基层组织的软件能力提升;物以类聚,人以群分,依赖Committer组织形成技术凝聚力,营造技术氛围。

c)         代码质量分析平台:打造一个开发人员自己的质量评估与分析工具,使其成长开发人员工作、技术成长的好帮手。

d)         营造工程师氛围:建立技术影响力的软件工程师生态环境,让大家靠技术获得尊重、成长和荣誉;形成技术创造氛围,摆脱简单的重复劳动,用创造性方法、有追求的解决问题。

 

方案角度2:从交付件结果看

聚焦好代码的产出,从需要满足哪些要求、需要落实哪些工程实践、软件工程师需要具备哪些思想意识三个方面落实:

1)         满足哪些要求:业务实现要求(达到业务功能交付要求,属于底线要求)、编程规范(像一个人写的代码,便于理解、共享、重用,属于一般要求)、编程实践(代码的艺术,代码实现的最优解,属于有追求)、代码度量分析(重构、技术提升每天都在进行,属于有追求)

2)         需要落实哪些工程实践:

a)         代码重用:通过设计原则使代码抽象、内聚、封装、去重,使代码处于可重用状态;通过模块级代码重用,使每次新增加或者修改代码的影响范围变得更小,质量保证活动更聚焦变更的代码质量。

b)         基于文件夹的自动化测试:保证防护的模块,质量上可以重用;从对应的驱动和用例上,可以成为重用的样例,使大家更容易重用

c)         代码质量分析:评估模块质量、代码提交质量,促使软件工程师不断输出“更好”代码

d)         Committer机制:将代码重用、基于文件夹的自动化测试、代码质量分析的工程能力落实,持续提升组织软件能力

3)         软件工程师需要具备哪些思想意识:

a)         技术影响力文化,形成靠技术,闯事业,以技术影响力为荣的工作环境,鞭策大家不断提升技术能力、构建技术影响力。

b)         建立软件工程师的技术创造文化,牵引软件工程用创造性的方法和技术解决问题。

c)         建立专家磨练新人的成长文化,实现编程思想、编程实践、编程经验的最大程度的传承和发扬

 

 

综述,代码质量问题是一个复杂的问题,它就像文章一样,除了要遵守语法,还融入了人类思维的语义;代码质量提升是一个系统工程,它需要运用各种流程、工具、方法、组织、人才、氛围、技术等,使系统整体与局部之间的关系协调和相互配合,实现总体的最优运行。

所以,代码质量提升的推手-----没有捷径、银弹,主要是回归人性、聚焦技术扎实落地。

 

 

如果你总是重复过去做过的事情,结果也会一成不变。

                                                                     ----阿尔伯特 爱因斯坦

posted @ 2022-06-28 17:45  易先讯  阅读(435)  评论(0编辑  收藏  举报