太长不读:在很长一段时间我并不知道怎么去平衡速率和质量之间的关系,我虽然看过不少书和文章告诉我只有保证质量才能保证速率,但我还没有见过反例,我没办法很好地说服别人,我只能看着他们义无反顾的冲向进度,然后抱怨时间不够。我想用我经历和见证的不同项目、不同情况来和你聊聊为什么质量等于速率。
“ 我们这个迭代的卡完成不了了,你们先不要管重构测试之类的东西了,先把功能写完。 我们上个迭代的速率还不错,不过这个迭代的压力也很大,我们还是加加速吧,code review先放放。 ”
这可能是在很多进度紧张的 fix bid 项目能听到的一些话。质量和进度的取舍好像是软件工程一直都绕不开的话题。对于这两个维度我们到底应该怎么做呢,放弃质量追求进度的做法可不可行呢?或者我们能不能都要?
在很长一段时间内我并没有答案,毕竟没有过经历就没有发言权,不过如今在我经历了不同项目、不同情况后好像找到了一些答案。
不过在开始前我想先简单聊聊质量和速率这俩东西。
产品质量
what-is-quality
人们最普遍的认知是,质量越好的产品成本越高,价格也越高。当然我们需要控制变量,不考虑品牌溢价等因素。
举个例子,假如一个产品用两个月就坏了,为了让它用的更久,我们可能需要改进工艺,可能需要使用更好的材料制作。这些改进会让制造商付出更多的成本,为了赚回这些成本,这个产品就会卖更高的价格。
软件质量
但是软件质量和一般的产品质量又有些许不同。它主要分为两个方面 -- 外部质量和内部质量。
外部质量
外部质量简单来说是用来描述软件用户能够触碰到的部分的质量。比如是不是有 bug 会导致软件崩溃;软件的可用性/易用性如何;用户是不是能用软件快速解决自己的问题等。
内部质量
内部质量则对用户不可见,用户也不关心软件的内部质量。它主要涉及的是代码的可维护性。比如代码架构是不是合理;是不是可扩展的;模块间的依赖是否杂乱无章;是不是有无法控制的技术债;是不是有自动化测试保证代码的正确性等。
质量与速率
如果把这两部分拆开来看,用户压根不关心内部质量,因为用户只管你的软件好不好用,至于代码好不好维护这件事他们根本就不在意。但是用户在意的是他提出的意见和建议开发者能不能快速反馈,他想要的新功能你能不能赶紧给安排上。
而这个时候就体现出了内部质量的重要性。我们每个人都知道的是,在一份内部质量优秀,技术债不多的代码库中增加新功能,要比在一个内部质量相对差一些的代码库中增加新功能容易得多。
cruft-impact
当然你也有可能会想,我们为了使代码保持高质量,去解决技术债、重构、组织代码结构的时候是实实在在花了时间的,我把这些时间用来增加新的功能速率就是会更快。
在保持其他条件不变的情况下,我们无法否认这样做是会加快你的速率,但这其实是一个短期收益和长期收益的取舍,而这个短期可能比你想象中要短的多。
velocity
从这个图里可以看到,如果代码一直保持高质量,开发速率将在几周后超过内部质量不太好的代码,这个结果来自于 Martin Fowler 和他认为资深同事的经历的总结。
和大多数人一样,我在最开始对这份数据持怀疑态度,直到我如今我参与或间接见证了几个有意思的项目。
六边形战士
perfect-team
这是一个从起项目就聚集了一众大佬的明星团队,哪怕在客户的各种胡搅蛮缠下大家对于工程实践和软件质量的要求都很高。我所学习到的停留在书本上的敏捷实践在这个组都能看到。我在这里体验了极限编程、clean code,大家会抽时间 pair,实践 TDD,用重构来使代码符合 simple design,有自发的不定期的分享,以定期 tech huddle 的形式来管理和讨论技术债......
我是在项目更替许久已经进入某个平稳期的时候加入项目的,这时候项目闲的发慌,大家都在想各种办法提升自己的能力,我在这里切身体会到了 P2 文化。
事情的转折来自于项目 N 期准备上线前的几个迭代,客户给我们玩了个大的,我们自己也没兜住需求,项目进入了赶进度阶段。
但是在这种紧张的情况下我们并没有丢弃各种实践,大部分实践依旧是我们的底线,不过我们也确实停止了 pair,暂停了 tech huddle。TDD、重构、simple design,各种工程实践已经成了这个项目的 baseline。
这些后来被称为“负担”的东西不仅没有拖慢我们的脚步,反而帮我们兜住了客户反复横跳的需求,大大减少了 debug 和破坏已有功能的可能性。
停不下来的雪崩
avalanche
这是一个已知的短期项目,我并没有亲身经历。项目组制定的策略是,放弃一些实践,短时间内快速堆出客户想要的东西完成 MVP。
在开始的一段时间项目按照预期稳步进行,但随着时间的推移和来自于交付的压力,团队放弃的实践越来越多。从开始的放弃 pair、放弃 TDD,到后来的放弃写测试、放弃 code review。再到后来为了达到预期的 velocity 开始加人。
但是情况已经如同雪崩一般无法阻止。从外部观察和私下与项目中 core team 的小伙伴 retro 来看,项目刚开始确实要轻松一些,但是随着大家不管不顾项目质量,一味追求进度,项目开始向雪崩的方向发展。
项目逐渐陷入泥潭的时间接近两个迭代,也就是四周接近一个月,与 Martin Fowler 的结论非常接近。在这个 retro 中我开始意识到老马好像没有骗人,他那个图没有瞎画。
当然我们并不能将这样的情况简单归咎于项目质量,但这的确是一个不可忽略的关键因素。
高达的内部协调
gundam
这是我经历过人数最多的项目,超过 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 大叔在《敏捷整洁之道》一书中对敏捷勇气价值观的解释来结束这篇文章。
“ 勇气 —— 换句话说, 就是 在合理范围内敢于冒险。敏捷团队的成员并不太关注公司政治意义上的“安全”,那会导致牺牲质量和机会。他们意识到,长期来看,管理软件项目的最佳方法是具备一定程度的侵略性。 勇气和鲁葬是有区别的。部署最小的功能集需要勇气。维护高质量的代码和高质量的纪律需要勇气。但是,部署你自己都没有信心的代码,或者设计不具可持续性的代码,这就是鲁莽。通过牺牲质量来遵守时间表就是鲁莽。 质量和纪律会提高速度,这是一种信念,强势但幼稚的人们在面对时间压力时会不断挑战这种信念,因此坚持正确的信念需要勇气。 ”