什么是质量内建

随着时间的推移,我们项目的开发效率会逐渐降低,直到几年之后整个项目可能就无法维护,只能推倒重来。具体的表现首先就是随着时间推移,我们会发现整个需求列表里面能做的需求越来越少,因为每当我们增加一个新特性,需要改动的代码就非常多,所以最后每提出一个新的需求,团队评估出来的改动成本都非常高,导致最后难以增加新的特性。

第二个表现就是缺陷难以修复。我们做出来的系统只要有人用就会有反馈一些线上的故障,一开始代码很简单的时候修复起来是很快的,但是随着代码越来越复杂、代码行数越来越多,我们会发现定位问题太难了。尤其是现在我们的项目采用的是非常复杂的架构,所以当用户线上报错的时候,我们很难去定位到是哪里出了问题。但其实只要定位到了问题,修复起来是很快的。

第三个表现我们称之为“打地鼠现象”,简单来说就是当你“按”下一个缺陷的时候,又会蹦出来几个新的缺陷。这样会导致大家在工作的过程中压力非常大、心情也会比较沉重。

所以对于这些挑战,我们也有想办法去解决,CI、CD 以及 DevOps 的出现都让我们看到了很好的方向。但是我看到很多团队其实只是靠 DevOps 解决了一些基本的问题,并没有解决核心的问题。这是为什么呢?因为核心问题主要是靠开发人员的能力提升来解决的,但由于改变一个人是很难的,所以企业往往会绕开这些问题。所以我今天分享的内容主要会涉及到开发人员如何去写代码等一些实践。

我们在刚开始启动一个项目的时候,我们会制定一些代码规范,所以代码相对来说是比较清晰的。但是随着需求的演变,在实现这些需求的时候,每个人都会选择最低成本、最保险的方式。这就会导致没有人敢去大幅度地改动代码,只会在里面追加一些代码,造成了代码里面有大量的重复、过长的方法。同时开始的时候设计的架构也是非常清晰的,但是如果后续没有很好的落地、监控、自动化地发现问题,架构就会在这过程中腐化,变得一团乱。

Deming 先生曾提出“问题发现得越早,修复的成本越低”,这句话也是我们去降低软件开发成本、更高效地保证质量的重要原则。所以我们采用质量内建的方式,可以把整个软件质量的保障内嵌到开发的过程中去,而不是留到后面再去检测,因为越往后修复的成本越高。

85% 的缺陷都是在代码编码阶段引入的,然而大部分的缺陷并不是在编码的时候发现的,而是在后面的单元测试、功能测试、集成测试发现的,越往后发现的缺陷越多。按照刚刚那个原则,假如在编码阶段发现的缺陷只需要 1 分钟就能解决,那么单元测试阶段需要 4 分钟,功能测试阶段需要 10 分钟,而到了上线之后再发现可能就需要 640 分钟,这个成本是非常高的。

那么质量内建的方式是怎么样的呢?首先我们通过自动化测试、重构、简单设计等手段,可以使在编码阶段引入的缺陷变少,因为我们代码写清楚了,bug 就藏不住了。同时当我们做到自动化测试等工作时,在编码阶段发现的缺陷也变多了。那么通过质量内建,我们在编码阶段就把大部分的问题都捕获到,同时引入的缺陷也更少,它就降低了软件的开发成本。

大家可能会有一个疑惑,就开始开发人员原本只用写功能就行了,现在却还要写测试代码,而且测试代码的比例和实践代码的比例不一定,这样会不会增加成本。这里想跟大家说一下,很多人会把我们编写代码的时间当成整个软件开发的时间,其实不是。在编写玩代码之后,还得开发自测,然后还要去联调,之后还要进行内部测试、线上故障修复等,所以整个软件开发有这么多的过程,而我们现在解决问题的办法是在第一阶段投入更多的时间,做更多的测试、更多的代码优化等,从而减少后面所要花费的时间。根据刚刚说的修复时间越晚,成本越高的原则,我们这样做是划算的。

技术债与质量门禁

技术债是什么呢?债是一种比喻,与我们金融方面所说的债务意思相同,那么在我们技术范畴里面也有这样的债务问题。在我们编写代码的过程中留下了一些重复的代码,或者没有起好名字、没有给出注释,类似这样的问题就是我们欠下的技术债。

对比金融里的债务,技术债也有相应的特性。首先这个债我们必须得还,否则到了后面越欠越多可能会把整个团队压垮了,导致大家没有动力去开发新的功能。同时技术债是有利息的,假如最开始写代码的人留下了问题没有去解决,那么下一个接手这个代码的人可能就没法理解这个代码,就不敢大胆地去改代码,越晚就越不敢。

既然技术债存在这么多问题,大家为什么还要去欠债呢?因为技术债也有好处,有的时候我们要做的产品并不是一个非常靠谱的产品,我们就会追求更快,用一个比较粗糙的手段做完交给客户去进行测试。得到反馈之后,靠谱的话我们就会用心去优化、迭代;不靠谱的话我们就会放弃这个项目,这是它的成本也很低。所以由于互联网行业的这种快节奏,人们会倾向于欠下很多的技术债务,从而快速试错。当我得到反馈,确认用户的痛点之后再来进行代码的优化。当然我今天更想讲的我们为什么会欠下债务,其实还是是因为态度以及习惯问题。如果我们能改掉我们的坏习惯,我们就会少欠下一些技术债。

当我们搭建好一个项目的基础框架,写了一些示例的代码,后面就会上很多的人来做一些新需求。这个时候就会出现“失控”,我们会发现一开始的代码非常整洁,但是人一多之后就会形成“破窗效应”——简单来说就是一旦一扇窗户上出现了一个破洞,那么很快上面的破洞就会越来越多。代码库也是如此,当一个人没有按照规范写代码,同时没有人制止,那么很快其他人也会纷纷开始这样做,很快代码就会变得乱七八糟。

那么应对这种“破窗效应”的方法就是“童子军军规”,就是不管原来的质量怎么样,我们也得保证我们接手处理完之后,代码的质量要比原先好上一点、干净一点,哪怕是改一个变量命名也好,改一个格式也好,人人都这样做的话我们的代码库就会越来越干净、质量越来越高。这种方式就是我们所谓的“质量门禁”。

接下来讲一下偿还技术债,首先第一点是并非所有技术债都应偿还,或者说技术债的偿还应该有一个优先级,我们更应该关注的是那些频繁地需要变更的代码。第二点是应用童子军规则,也就是有债就还,不要拖欠太久,保证每次提交代码的时候比接手时要干净一点。第三点是先偿还高息技术债,就是看哪些问题不处理的话带来的后果会更严重,我们就优先处理这些问题。接着是分期偿还技术债,将我们的技术债管理起来,每次迭代的时候就一边做有客户价值的工作,一边偿还技术债。这里很关键的就是不能依赖开发人员的自觉性,而是在迭代的时候就要明确那哪一块要优化、要重构,分到个人的头上,同时后面要进行评测、验收,经过这样一个流程正式地去对待技术债。

自动化

自动化是一个实践,我们经常会听到像自动化发布、自动化打包、自动化构建、自动化测试等,尤其是自动化测试是一个反复被强调的一个实践。我们的流水线其实整个都是自动化的,构建是自动化的,检查是自动化的,包括后面的测试和部署也都是自动化的。

有一个原则叫自动化一切,就是“一切能被自动化的都应该被自动化”。除了常见的编译、检查、测试和部署,服务器的配置也可以进行自动化,甚至业务上的一些部署,比如一些迁移之类的,能自动化的我们都把它自动化。我们作为开发人员,最擅长的其实就是写代码,很多人会觉得自己的工作没什么挑战,这是因为你天天都在手工地做一些重复的事情,当然没有挑战了。这时候你可以尝试去自动化一些事情,你会发现很好玩,也能学到新的东西,个人能力能得到成长,同时做的事情也有价值。

我体会到自动化的几个好处,跟大家分享一下。第一个是沉淀知识,就是把知识沉淀到了自动化的脚本里面,而不是存在于某个人的脑子里。而对于掌握知识的这个人来说,他也减少了被打断的可能。第二点就是自动化能够提高效率,解放生产力,这一点其实是一个很明显的好处,原来手工要花五个小时的事情,自动化可能几分钟就跑完了。最后一点就是固化流程,降低出错率。也就是将我们的这个流程固化下来了,原本一件事情今天是 A 做,明天是 B 做,他们在做的时候可能就基于自己的理解来做,中间就会引入一些错误。而自动化就可以规避这种问题。

其他有效实践

①结对编程:结对编程是我非常推崇的实践,很多人认为结对编程就是一个人写一个人看,这样就浪费了一个人,其实不是的。其实结对编程有点像汽车拉力赛,领航员会看地图然后告诉 driver 前方的路线,例如前面的弯道该怎么走,所以他的视野会更加宏观,看得更远,也有助于对我们的 driver 做一个思维上的引导。写代码的时候也是这样的,操作键盘的人在考虑代码该怎么敲,而另一个人则是在引领思路,所以他俩是在互相配合的。

②代码评审:代码评审就是大家坐在一起,分享代码的收获、踩过的坑以及解决问题的方法、技巧。这是一个开发人员的交流活动,而不是一个类似于质量门禁的东西,这是有温度的一场交流、分享,传播有价值的东西。

③暴徒式编程:暴徒式编程是结对编程的一种方式,由一个人操作键盘,同时设置定时,每隔一段时间换人。其他人就负责盯着大屏幕告诉他该怎么操作,这个也是一个很好的学习手段。

小波老师将在完整视频中继续为大家带来

更多精彩分享以及重构的在线演示

点击观看完整视频

CODING DevOps 系列第四课:DevOps 中的质量内建实践的更多相关文章

  1. Vue基础系列(四)——Vue中的指令(上)

    写在前面的话: 文章是个人学习过程中的总结,为方便以后回头在学习. 文章中会参考官方文档和其他的一些文章,示例均为亲自编写和实践,若有写的不对的地方欢迎大家和我一起交流. VUE基础系列目录 < ...

  2. Python中3种内建数据结构:列表、元组和字典

    Python中3种内建数据结构:列表.元组和字典 Python中有3种内建的数据结构:列表.元组和字典.参考简明Python教程 1. 列表 list是处理一组有序项目的数据结构,即你可以在一个列表中 ...

  3. CODING DevOps 系列第三课:云计算、云原生模式下 DevOps 的建设

    本文首先会和大家分享当前整个应用生命周期的演变历程,然后讲解云计算模式下 DevOps 建设包含的过程.流程规范和标准,最后讲解云原生时代到来会带来哪些改变,以及标准化的建设会有哪些改变和突破. 应用 ...

  4. 初识google多语言通信框架gRPC系列(四)C++中使用gRPC

    我的这几篇文章都是使用gRPC的example,不是直接编译example,而是新建一个项目,从添加依赖,编译example代码,执行example.这样做可以为我们创建自己的项目提供借鉴.如果对gR ...

  5. CODING DevOps 系列第五课:微服务测试——微服务下展开体系化的微服务测试

    微服务测试的痛点与挑战 这张图可以形象地展示单体服务和微服务的对比,单体应用就像左边巨大的集装箱,软件模块和应用都包括其中:而微服务就像是由一个小集装箱组成,微小的服务组成一个庞大.完整的系统.单体服 ...

  6. ASP.NET 5系列教程 (四):向视图中添加服务和发布应用到公有云

    向视图中添加服务 现在,ASP.NET MVC 6 支持注入类到视图中,和VC类不同的是,对类是公开的.非嵌套或非抽象并没有限制.在这个例子中,我们创建了一个简单的类,用于统计代办事件.已完成事件和平 ...

  7. Knockoutjs官网翻译系列(四) computed中依赖追踪是如何工作的

    初学者无需了解这些 ,但是很多高级程序员想知道我们为什么可以保持跟踪这些依赖以及可以正确的更新到UI中.它其实很简单.跟踪算法是这样的: 无论何时你定义了一个computed observable,K ...

  8. javascript面向对象系列第四篇——OOP中的常见概念

    前面的话 面向对象描述了一种代码的组织结构形式——一种在软件中对真实世界中问题领域的建模方法.本文将从理论层面,介绍javascript面向对象程序程序(OOP)中一些常见的概念 对象 所谓对象,本质 ...

  9. python第二十四课——set中的函数

    集合中常用的一些函数: 1.add(obj):追加一个obj元素到集合中 pop():从集合中随机弹出一个元素 remove(obj):删除集合中和obj匹配的元素 clear():清空集合 s1={ ...

随机推荐

  1. arch 系列manjaro更新deepin-screenshot没有图标

    问题描述 deepin软件安装到其他分支后,这个问题出现,相信各位一点都不意外,原因不细说,简单的概括就是没有DDE的桌面环境!! 简单介绍 deepin-screen截图软件在使用的时候是深受国人的 ...

  2. Verilog代码和FPGA硬件的映射关系(五)

    既然我们可以指定寄存器放在IOB内,那我们同样也可以指定PLL的位置.首先要确保我们有多个PLL才行.如图1所示,我们所使用的EP4CE10F17C8芯片刚好有两个. 图 1 为了演示这个例子,我们使 ...

  3. Github自动打包并推送Nuget版本

    如何将自己的类库,自动打包并自动发布到Nuget? 1. 项目csproject属性修改 新建一个项目GitToNugetPackageTest 不用添加任何类,我们修改csproject属性. 替换 ...

  4. 【C++】VS Code配置

    0.前言 本文已配置C++环境为例,本文主要是面向刚开始接触VS Code的朋友,采用生成默认配置任务的方法,在编写本文过程中大量参考了官方文档,感兴趣的朋友可直接前往传送门. 环境: win10 + ...

  5. 居然还有人这样解说mybatis运行原理

    目录 Mybatis基本认识 动态代理 JDK实现 CGLIB动态代理 总结 反射 Configuration对象作用 映射器结构 sqlsession执行流程(源码跟踪) Executor Stat ...

  6. 【JVM】垃圾回收器总结(2)——七种垃圾回收器类型

    七种垃圾回收器类型 GC的约定参数 DefNew——Default New Generation Tenured——Serial Old ParNew——Parallel New Generation ...

  7. Java实现第八届蓝桥杯最大公共子串

    最大公共子串 最大公共子串长度问题就是: 求两个串的所有子串中能够匹配上的最大长度是多少. 比如:"abcdkkk" 和 "baabcdadabc", 可以找到 ...

  8. PAT 程序运行时间

    要获得一个 C 语言程序的运行时间,常用的方法是调用头文件 time.h,其中提供了 clock() 函数,可以捕捉从程序开始运行到 clock() 被调用时所耗费的时间.这个时间单位是 clock ...

  9. 数据的存储结构浅析LSM-Tree和B-tree

    目录 顺序存储与哈希索引 SSTable和LSM tree B-Tree 存储结构的比对 小结 本篇主要讨论的是不同存储结构(主要是LSM-tree和B-tree),它们应对的不同场景,所采用的底层存 ...

  10. Tomcat 配置必备的 10 个小技巧

    现在开发Java Web应用,建立和部署Web内容是一件很简单的工作.使用Jakarta Tomcat作为Servlet和JSP容器的人已经遍及全世界.Tomcat具有免费.跨平台等诸多特性,并且更新 ...