时间都去哪了?

news2024/11/16 21:44:42

在很长一段时间我并不知道怎么去平衡速率和质量之间的关系,我虽然看过不少书和文章告诉我只有保证质量才能保证速率,但我还没有见过反例,我没办法很好地说服别人,我只能看着他们义无反顾的冲向进度,然后抱怨时间不够。我想用我经历和见证的不同项目、不同情况来和你聊聊为什么质量等于速率。

我们这个迭代的卡完成不了了,你们先不要管重构测试之类的东西了,先把功能写完。
我们上个迭代的速率还不错,不过这个迭代的压力也很大,我们还是加加速吧,code review先放放。

这可能是在很多进度紧张的 fix bid 项目能听到的一些话。质量和进度的取舍好像是软件工程一直都绕不开的话题。对于这两个维度我们到底应该怎么做呢,放弃质量追求进度的做法可不可行呢?或者我们能不能都要?

在很长一段时间内我并没有答案,毕竟没有过经历就没有发言权,不过如今在我经历了不同项目、不同情况后好像找到了一些答案。

不过在开始前我想先简单聊聊质量和速率这俩东西。

产品质量

**人们最普遍的认知是,质量越好的产品成本越高,价格也越高。**当然我们需要控制变量,不考虑品牌溢价等因素。

举个例子,假如一个产品用两个月就坏了,为了让它用的更久,我们可能需要改进工艺,可能需要使用更好的材料制作。这些改进会让制造商付出更多的成本,为了赚回这些成本,这个产品就会卖更高的价格。

软件质量

但是软件质量和一般的产品质量又有些许不同。它主要分为两个方面 – 外部质量和内部质量。

外部质量

外部质量简单来说是用来描述软件用户能够触碰到的部分的质量。比如是不是有 bug 会导致软件崩溃;软件的可用性/易用性如何;用户是不是能用软件快速解决自己的问题等。

内部质量

内部质量则对用户不可见,用户也不关心软件的内部质量。它主要涉及的是代码的可维护性。比如代码架构是不是合理;是不是可扩展的;模块间的依赖是否杂乱无章;是不是有无法控制的技术债;是不是有自动化测试保证代码的正确性等。

质量与速率

如果把这两部分拆开来看,用户压根不关心内部质量,因为用户只管你的软件好不好用,至于代码好不好维护这件事他们根本就不在意。但是用户在意的是他提出的意见和建议开发者能不能快速反馈,他想要的新功能你能不能赶紧给安排上。

而这个时候就体现出了内部质量的重要性。我们每个人都知道的是,在一份内部质量优秀,技术债不多的代码库中增加新功能,要比在一个内部质量相对差一些的代码库中增加新功能容易得多。

在一份内部质量优秀,技术债不多的代码库中增加新功能,要比在一个内部质量相对差一些的代码库中增加新功能容易得多。

此图来自:https://martinfowler.com/articles/is-quality-worth-cost.html

当然你也有可能会想,我们为了使代码保持高质量,去解决技术债、重构、组织代码结构的时候是实实在在花了时间的,我把这些时间用来增加新的功能速率就是会更快。

在保持其他条件不变的情况下,我们无法否认这样做是会加快你的速率,但这其实是一个短期收益和长期收益的取舍,而这个短期可能比你想象中要短的多。

如果代码一直保持高质量,开发速率将在几周后超过内部质量不太好的代码

此图来自:https://martinfowler.com/articles/is-quality-worth-cost.html

从这个图里可以看到,如果代码一直保持高质量,开发速率将在几周后超过内部质量不太好的代码,这个结果来自于 Martin Fowler 和他认为资深同事的经历的总结。

和大多数人一样,我在最开始对这份数据持怀疑态度,直到我如今我参与或间接见证了几个有意思的项目。

六边形战士

六边形战士

这是一个从起项目就聚集了一众大佬的明星团队,哪怕在项目压力大,需求复杂且不明确的情况下,大家对于工程实践和软件质量的要求都很高。我所学习到的停留在书本上的敏捷实践在这个组都能看到。我在这里体验了极限编程、clean code,大家会抽时间 pair,实践 TDD,用重构来使代码符合 simple design,有自发的不定期的分享,以定期 tech huddle 的形式来管理和讨论技术债…

我是在项目更替许久已经进入某个平稳期的时候加入项目的,这时候项目闲的发慌,大家都在想各种办法提升自己的能力,我在这里切身体会到了 P2 文化。

事情的转折来自于项目 N 期准备上线前的几个迭代,突然出现了一批之前没有分析出来的隐藏需求,但与此同时我们还需要做上线前的准备,项目进入了赶进度阶段。

但是在这种紧张的情况下我们并没有丢弃各种实践,大部分实践依旧是我们的底线,不过我们也确实停止了 pair,暂停了 tech huddle。TDD、重构、simple design,各种工程实践已经成了这个项目的 baseline。

这些后来被称为“负担”的东西不仅没有拖慢我们的脚步,反而作为保护网保护了我们在需求不明确的情况下反复修改的代码,大大减少了 debug 和破坏已有功能的可能性。

停不下来的雪崩

这是一个已知的短期项目,我并没有亲身经历。项目组制定的策略是,放弃一些实践,短时间内快速堆出客户想要的东西完成 MVP。

在开始的一段时间项目按照预期稳步进行,但随着时间的推移和来自于交付的压力,团队放弃的实践越来越多。从开始的放弃 pair、放弃 TDD,到后来的放弃写测试、放弃 code review。再到后来为了达到预期的 velocity 开始加人。

但是情况已经如同雪崩一般无法阻止。从外部观察和私下与项目中 core team 的小伙伴 retro 来看,项目刚开始确实要轻松一些,但是随着大家不管不顾项目质量,一味追求进度,项目开始向雪崩的方向发展。

项目逐渐陷入泥潭的时间接近两个迭代,也就是四周接近一个月,与 Martin Fowler 的结论非常接近。在这个 retro 中我开始意识到老马好像没有骗人,他那个图没有瞎画。

当然我们并不能将这样的情况简单归咎于项目质量,但这的确是一个不可忽略的关键因素。

高达的内部协调

这是我经历过人数最多的项目,超过 200 人被分成 7 个团队,大家分布在 3 个国家的 6 座城市。而这个分布式团队要通过各种合作造出一个高达。

我们接手的是一份质量说不上好的代码库。刚上手我的体验极差:

  • 项目代码结构混乱,很难找到自己想找的代码
  • 被称为内部渲染引擎的的一份框架代码逻辑混乱,几乎没有测试,但被大半个代码库依赖
  • 按照 README 甚至没办法启动本地环境
  • 为数不多的测试代码极其不稳定,但又找不到不稳定在哪,然后之前的开发团队用了各种骚操作尝试修复这种不稳定

刚开始的一个月我们举步维艰,甚至还没开始就已经陷入泥潭,更不必说接下来的速率目标,之后我们制定了一些每个人都应该遵守的原则:

  • 就算花费时间久,起项目的前几个迭代中国区三个团队的 web devs 要在一起 code review,这有助于我们在 code review 的过程中发现和解决大家共同的问题
  • clean code 是我们的底线,每个人都可以参与制定团队代码实践并严格遵守,任何人都能给其他人留符合实践规范的 comments,不改完不 merge
  • 管理团队技术债并定期解决
  • 重新规划整个代码结构
  • 可以不 TDD 但必须写测试,制定可行的测试策略

大家顶着被各种催进度的压力在做各种取舍,这中间花的时间是实实在在的,但改变也是实实在在的。

“我改了 xxx,yyy 的测试挂了,我得去看看。然后就能提 PR 了。”
“aaa 文件在 bbb 里面,bbb 里面是整个 flow。”
“这张卡我搞定了,现在在做重构,搞完后下张卡会很快。”
ccc 的功能 pattern 已经定成上次讨论的了,你后面的功能搞成一样,一看就懂了。

我们花在质量上的时间,都在未来的时间中赚回来了:由于定好了开发规范,我们在 code review 过程中不会再争论无意义的规范问题;大部分 bug 在出现的那一刻都能被我们的测试捕获到并快速修复;我们能快速找到我们想找的东西。

而那些我们还没有努力过的方向,依然让我们难受,比如前面提到的渲染引擎(苦笑

答案

看完三个故事,现在你应该能发现我们的时间都去哪了。

每个人都知道写垃圾代码可以让开发速度增快,那么我们停止花时间维护代码质量,把所有精力扑到完成功能追赶进度上来,如果可以的话,时间拉满,一个月工作 380 个小时。

静下心来好好想想朋友,”快而脏“是不存在的,垃圾代码只会让你把你所有时间用在 debug 里,垃圾代码只会拖累你。请记住,加快速度的唯一办法就是保证质量

换个角度想想,你加快速度节省的时间,又在 team 里面其他人身上花掉了,QA 会向你抱怨为什么之前好好的功能现在坏掉了,同事会来问你为什么他昨天的代码今天自测就不工作了,BA 会来告诉你今天 showcae 又失败了…

你觉得省下的时间,三五天后又回来找你了。

勇气

最后我想引用 Bob 大叔在《敏捷整洁之道》一书中对敏捷勇气价值观的解释来结束这篇文章。

勇气 —— 换句话说, 就是 在合理范围内敢于冒险。敏捷团队的成员并不太关注公司政治意义上的“安全”,那会导致牺牲质量和机会。他们意识到,长期来看,管理软件项目的最佳方法是具备一定程度的侵略性。
勇气和鲁葬是有区别的。部署最小的功能集需要勇气。维护高质量的代码和高质量的纪律需要勇气。但是,部署你自己都没有信心的代码,或者设计不具可持续性的代码,这就是鲁莽。通过牺牲质量来遵守时间表就是鲁莽。
质量和纪律会提高速度,这是一种信念,强势但幼稚的人们在面对时间压力时会不断挑战这种信念,因此坚持正确的信念需要勇气。


文/Thoughtworks 张起荣
原文链接:为什么软件质量等于开发速率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/70814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据hadoop_HDFS的shell操作(2)

文章目录1. 基本语法2. hadoop hdfs命令指南3. 常用命令操作指南3.1 准备工作3.2 上传3.3 下载3.4 基本操作1. 基本语法 hadoop fs 具体的命令 或者 hdfs dfs 具体的命令一般是使用hadoop fs 具体的命令,因为是简单好记,容易理解。 2. hadoop hdfs命令…

TypeScript 开发环境搭建

TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript代码,TypeScript可以在任何浏览器,任何计算机和任何操作系统上运行。 目录 依赖环境 搭建步骤 1.新建一个文件夹 2.初始化一个NPM项目 3.安装typescript 包 4.新建一个tscon…

SQLite 基本命令使用方式

本文介绍创建一个简单的数据库,并能够在需要的时间和地点快速使用它们。SQLite 在世界范围内的许多设备中使用。 什么是SQLite? SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的…

Spring Cloud Alibaba整合Sentinel进行服务熔断降级

一、下载Sentinel Dashboard控制台服务 Releases alibaba/Sentinel GitHub 一样的,根据自己的Spring Cloud Alibaba版本下载相应版本的Sentinel 启动服务,可以指定端口 java -Dserver.port8849 -Dcsp.sentinel.dashboard.serverlocalhost:8849 -Dp…

记录下QT读取串口数据时遇到的问题

一、如果使用QT读取串口数据 使用定时器定时发送信号,然后调用槽函数来读取串口数据,串口数据读取过程加锁。 timer1 startTimer(15); connect(this, SIGNAL(callCapData()), this, SLOT(CapData()));void ecgfrom::timerEvent(QTimerEvent *event) {…

Allegro如何批量把器件放在指定的格点上操作指导

Allegro如何批量把器件放在指定的格点上操作指导 Allegro支持批量把器件放在指定的格点上,具体操作如下 以下图为例,器件在小数点位以后的格点上,如果只是个别器件,只需要切换好格点,并且手动移动下就可以了,如果有大量的器件都是这样,这样会比较费时 选择File-change…

Java中注解的理解

一.什么是注解 1.Annotation是从JDK5开始引入的最新技术 2.Annotation的作用: 1)不是程序本身,可以对程序做出解释,(这一点和注释(comment)没什么区别)。 2)可以被其他程序(比如编译器)读取…

【论文随笔】Time-Incremental Learning from Data Using Temporal Logics

[1] E. Aasi, M. Cai, C. I. Vasile, and C. Belta, “Time-Incremental Learning from Data Using Temporal Logics.” arXiv, Dec. 28, 2021. doi: 10.48550/arXiv.2112.14300. 好久没看文献了,来更一篇 Outline time-variant weights of STL weights are learn…

【Windows基础】Windows用户和用户组的管理

一、用户账户 什么是用户账户? 不同的用户身份拥有不同的权限每个用户包含了一个名称和一个密码每一个用户登录系统后,拥有不同的操作权限。为不同的账户赋权限,也就是为不用账户的SID赋权限!每个用户都有自己的配置文件(家目录…

opcj1——mac下如何快速搭建Java开发环境

这是我们OPCJ的第一篇,搭建基础的开发环境。我们的服务会不断增加新组件,我们这里先介绍如何快速搭建开发环境。一般来说Java程序员的电脑上总是会有一些已经配置好的idea、git、maven或者其他的,如果有的话,调整一下直接用就行了…

欧科云链对话ChatGPT:Web3会颠覆互联网?

最近,要说什么最火? 身为“当红炸子鸡”的ChatGPT 说第二就没人敢说第一 ChatGPT,是OpenAI基于Ai技术而实现的一个辅助引擎,通过大量的资料学习,打造出一个类似搜索引擎一样可以回答问题的工具。 它的爆火,…

uniapp使用unipush推送及java后台推送代码(含本地打包apk使用unipush推送)

你懂的,又是项目用到了,作为程序猿义无反顾需要定时 “进化” ,硬头皮去写,虽然曾经作为android开发者写了很多的推送,但是uniapp的推送也是有所差异的,记录一下,以后留用。 首先uniapp的推送u…

深度学习-Tensorboard可视化面板

文章目录简介安装SummaryWriter新建添加数字运行添加图片添加直方图实战前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 简介 Tensorboard是Tensorflow官方提供的实用可视化工具&#xf…

[附源码]JAVA毕业设计宿舍管理系统(系统+LW)

[附源码]JAVA毕业设计宿舍管理系统(系统LW) 项目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术&…

「微服务系列」微服务框架的介绍

为什么要学习微服务框架知识? 从求知的角度、企业的角度,微服务都是必知必会的! 需要学习哪些微服务知识? 传统单点架构,无法承载高并发场景;微服务领域按功能模块,将应用拆分成多个服务。大型…

【JavaScript高级】07-ES5、ES6中实现继承,原型及原型链

ES5、ES6实现继承,原型及原型链理解ES5实现继承对象和函数的原型对象的原型函数的原型new、constructor函数原型上的属性优化通过构造函数创建对象原型链原型链实现的继承借用构造函数继承寄生组合实现继承ES5实现继承 对象和函数的原型 对象的原型 JavaScript当…

C#实现发送钉钉工作通知消息

一、实现效果 实现在钉钉的工作中心里面发送消息(比如发送【文本消息】、【markdown消息】等不同类型的内容),实现效果如下: 二、实现思路 2.1、了解钉钉学习路径图 钉钉开放文档 (dingtalk.com)https://open.dingtalk.com/document/org-roadmap 2.2、学习了解钉钉的接入…

cubeIDE开发, stm32的RS485/232串口通信开发要点

一、stm32串口通信 stm32串口通信一般是指通过UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器传输数据,UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输,其在…

数据库01_内存分页管理_分段管理_设备管理_IO处理_索引文件结构_文件目录_位示图---软考高级系统架构师008

可以看到数据库方面的考点.在架构里面考4,5分左右 这里只说比较重要的标红的考点. 然后我们来看ER图: 1.首先看一下实体的概念:实体是指的客观存在并相互区别的事物,可以举一个例子,比如一家超市,那么超市经理,员工,部门经理,业务员等等,这都是名词,都是属于实体. 2.然后…

知识图谱-KGE-语义匹配-双线性模型(打分函数用到了双线性函数)-2013:NTN(Neural Tensor Network)

【paper】 Reasoning With Neural Tensor Networks for Knowledge Base Completion 【简介】 本文是斯坦福大学陈丹琦所在团队 2013 年的工作,好像是发表在一个期刊上的。文章提出了用于知识库补全的神经网络框架 NTN(Neural Tensor Network&#xff09…