软件工程是个面包机
原文链接 https://drmingdrmer.github.io/tech/bla/2018/09/27/toaster.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
软件工程是个面包机
<!--excerpt-->
我们平时印象中的面包机是这个样子的:
烤面包机属于加热电器。其功能是在面包片附近生成足够的热量,以便对面包进行烘烤
面包机的原理非常的简单, 有个开关控制通电, 通过电热来加热, 这些基本的原理普通的中学生都知道, 也都能在wikipedia上找到. 这么简单的东西...
如果我们要亲手制作一个面包机, 需要花费多久呢?
话说, 英国有个叫Thomas Thwaites 的艺术家, 几年前, 花了9个月的时间做了一个面包机.
恩...没错, 9个月!
如果依赖现代社会的完善体系, 这件事情是很简单的,
之所以花费了这么久, 是因为他没有使用现成的零件, 一切都是从头开始制作的!
我们看看他到底怎么完成的:
<!--more-->
首先他买来一个面包机拆了, 看看都包括哪些零件, 估计一下工作量, 恩...
大概400种零件, 100种不同的材料:
看起来...工程会很庞大...但是聪明的 Thomas 经过一系列研究, 简化和删减, 把材料和零件大大缩减了, 看起来是一个可以完成的任务了:
- 铜: 用来做插头和电线.
- 钢: 用来做弹簧和烤架.
- 镍: 用来做加热系统.
- 云母和塑料: 用来做外壳.
这是简化后的材料列表:
有了计划和方案, Thomas开始了兴致勃勃的制作过程
首先 Thomas 托人从一家朋友的铁矿, 捎带了一箱铁矿石:
去博物馆找到了冶金学的教科书:
有了理论, 有了物料, 集中起来!
Thomas 用吹风机和垃圾桶搭建了一个炼铁炉:
然后 Thomas 用这个炼铁炉炼出了一堆...
炉渣...
Thomas 的炼铁计划失败了...
比较幸运的是, Thomas 找到了另外一个专利, 那就是...
用微波炉...加热并融化铁矿石!
(有点专利流氓的味道)
经过30分钟的微波轰炸, Thomas 终于融化了铁矿石...
Thomas 用融化的铁做出了面包机的框架.
接着, Thomas 从工厂搞来3大桶工业废水, 将其中溶解的铜离子置换出来, 变成铜:
做了3个插头:
最后要准备的东西, 是面包机外壳用的塑料. 然而... Thomas 打了30分钟电话, 也没能成功说服油井工人帮他提一壶石油回来.
因此...从石油DIY塑料的计划失败了...
但是锲而不舍的 Thomas 又找到了一个专利: 从土豆中提取塑料:
一种马铃薯淀粉基可降解塑料薄膜的制备方法
Abstract
本发明公开了一种马铃薯淀粉基可降解塑料薄膜的制备方法。 包括以下步骤:将60‑80份马铃薯淀粉、300‑400份去离子水混合成淀粉水溶液,在85‑90℃糊化;...
等过了几天, Thomas 再去看他放在室外风干的淀粉塑料时, 发现...
塑料...已经快被蜗牛吃光了!
绝望至极的 Thomas 只好再寻他法. 他去了1个废物回收站, 拉回一箱废料...经过
打碎...
融化...
铸模...
最后终于得到了一个面包机的外壳:
到此为止, 基本上凑齐了所有的材料和零件, Thomas 开始正式组装面包机!
又经过几天紧张的忙碌, 最后的成品面包机大概长成这个样子:
没有塑料外壳的时候, 面包机里面的结构大概是这个样子:
接下来就是令人兴奋的试用了!
虽然没有开关... 但能通电工作就可以了...
虽然面包机并不绝缘... 但只要注意安全就可以了...
就这样, 在第一次通电后...
大约过了5秒钟...
面包机就融化了...
是的...融化了...但是...Thomas觉得...这个项目...
非常成功!!!
并且为这个项目专门写了一本书讲述面包机的制作过程: <<The Toaster Project>>
hm...即使是面包机这么简单的小东西, 其制作过程也是十分不易的.
在这个过程中 Thomas 所有用到的东西罗列如下:
这里还不包括被蜗牛吃掉塑料的问题...
不包括使用了现代的锄头挖铁...
不包括借用了现代的微波炉炼铁...
我之所以讲这个故事...
是因为作为一个软件工程师, 我觉得, 它跟我们每天的工作太像了:
#### 面包机
定一个要实现的产品
#### 把面包机拆成400个零件
分析已有的产品, 做到详尽的细节/架构的了解
#### 裁剪功能到5种材料
制定自己的开发计划, 并按照发布要求删减目标功能
#### 垃圾桶炼钢炉
着手开发, 有时发现预先设计的方案无法实施
#### 油井不让拿走一桶石油
有时在开发过程中难免遇到无法逾越的困难, 临时切换方案
#### 用微波炉融化铁矿石
也有时在寻找解决方案的时候, 突然找到好用的工具
#### 没有开关
几经周折, 开发出一个1.0版本的面包机, 丑, 功能不完善
#### 不绝缘.
几乎无法在作为可靠的产品来用, 但总归是一个能工作的产品.
#### 5秒融化.
然后在第一次使用时就挂了
看起来这样的软件开发工作low到了极点是不是?
实际上, 大多数时候, 就是这样的:
- 90%的研发项目都在走这个流程.
- 90%的项目以失败告终.
- 90%的代码生存时间不超过3年.
然而, 问题并不在于执行这个工作的人.
从Thomas 的实施过程中容易看出他是个聪明的小伙子, 翻阅资料, 想各种办法. 但仍然耗费了长达9个月时间, 还是只作出了这么一个几乎是废品的产品.
为什么会这样? 为什么有时一个非常出色的工程师有时也无法做出好的产品?
这就是因为上面的故事里的一个苛刻的前提决定的:
从零开始
在<<The Toaster Project>>
这个项目中, 从零开始,
意味着Thomas无法借助整个人类社会构建的科技/工具体系来支持自己的工作,
所有的事情都必须由他自己完成.
(让我们暂时先忘了Thomas有点作弊的使用了微波炉炼铁这个事情).
如果抛开"从零开始"这个前提, 那么, 各种工具和组件立刻会改变Thomas的状况:
- 标准化的螺丝;
- 质量可靠的导线;
- 3D打印机或塑料模具;
- 钳子扳子...
这一系列的工具如果可以被Thomas使用, 让可靠的零件有了保证, 让零件之间的接口标准化对接有了保证, 那么制作出一个像样的面包机, 就不可能再是一件难事.
有了这样一个完善的工具体系, 即使没有太多制作面包机经验的人, 例如我, 也能做出一个漂亮的面包机(我发誓我之前没有做过面包机), 我可以从市场上买到高质量的螺丝, 高质量的绝缘塑料, 高质量的导线, 和标准化接口的开关.
只要我不犯太白痴的错误, 我相信, 组装一个不会融化的面包机, 以我现有的智商, 还是不成问题的, 我已经没有很多犯错的机会了.
因为我组装面包机的每个零件, 都是高质量高可靠的,
我的精力会非常集中, 集中在如何组装这些零件, 而不是去考虑每个零件是否正常.
回到我们日常的研发工作中,
聪明的工程师无法做出高质量的产品, 原因是, 他缺少一个可依赖的:
完善的工具体系
可能叫做工程体系更全面一点, 但我在这里不打算把话题扩展的太大, 今天只讨论其中最核心的工具.
软件工程就像人类社会一样, 绝大多数人的工作不是制作"面包机"这种最终产品的.
大部分人的工作是提供零件和支持:
- 一个螺丝的国际标准化的指定, 粗细, 硬度, 螺纹数量和间距等等;
- 或炼钢厂产出的优质的钢丝;
有这些可靠的支持, 一个产品的生产者才能做出优质的产品.
如果每个负责产品的人来亲自准备这些基础工具和材料, 恐怕无法避免的, 要去涉猎几十个或上百个自己完全不擅长的领域, 以有限的精力挑战数十个领域, 难免做出残次品零件. 零件的质量没法保证, 最终产品的质量也无从谈起.
而研发体系的建设, 也像面包机项目一样, 它的成功与否, 取决于是否能让一个工程师把精力集中在业务核心的思考上, 也就是说, 取决于是否在这个工程师背后有一个支撑他的完善工具体系.
现在我们就需要这样一个工具体系.
要想在竞争中胜出, 就需要站在最高巨人的肩膀上, 再上一步.
社区中已有的开源软件, 和市场上可购买的商用软件, 都是很好的借力点, 这是第一级台阶. 它们可以帮我们快速达到一个高度.
但它们也都是落后的, 因为软件行业发展的太快了, 每个人都在改进已有的东西以超越对手.
- 那些可以拿来就用的工具, 给产品的平庸下了定义.
- 那些在已有的工具(开源或商用软件)上做的更多的事情, 给产品的优秀下了定义.
在市场竞争中, 我们不能等别人做好工具给我们用(所有人都能获取的东西, 只是让大家一起提升平庸的水平线而已). 我们必须自己来构建超前现存软件的工具体系, 这是第二阶台阶. 依靠自己和同伴, 构建我们自己的工具体系, 它会是很多个完善的细节积累的成果: 让它足够稳定和足够先进, 那它就是那个更高的肩膀. 我们就可以在这个肩膀上伸手去触摸更高的位置.
我们为自己搭梯子. 然后踩着自己搭起来的梯子一步一步登高. 就像制作面包机一样, 再聪明的人, 也无法再没有支撑系统的情况下一下子制造出漂亮的面包机. 在这里, 工具体系显得比个人的智慧更加重要. (个人认为是90%以上靠体系,10%靠个人)
公司是什么
### 如果从外部来看, 公司是这样一个地方: 它把一群人聚集起来, 为了一个目标而努力.
### 如果从内部来看, 以上这种描述还不足以描述公司的真正作用和意义:
除了共同的目标, 公司更多的应该是一个平台, 让这个平台上的每个人, 都可以借力到其他人的高度而登上更高的地方.
就像不同职能的部门的互相配合达成一个目标, 是显而易见的: 通常来说, 不同职能之间的互相支持非常简单直接, 因为能力是互补的, 没有太多选择.
而同职能之间的互相支持, 显得困难一些, 同职能之间的隔绝非常难以发现, 职能外的人是肯定发现不了的(原因在于专业度不够无法深入, 也在于没有足够的精力), 制度这种独立于所有职能以外的东西也帮不上忙.
因为同质化, 需要非常仔细深入的观察, 才能发现互相可以依赖的方面.
IT也不同于传统业: IT行业中, 信息的复制, 经验的复制, 都不需要额外的实体成本, 它的每个成果都是可以零(实物)成本传递给其他人的.
所以我们有什么理由不尽最大努力促成和发挥出这种优势呢?
好的公司应该是能为这样一群有着共同目标的人, 提供一个高温环境, 把每个人的智力融化成岩浆, 汇集到一起, 再凝固成更高更大的山峰.
巨人的肩膀就是我们自己的肩膀, 也只能是我们自己的肩膀.
让自己的肩膀越来越高, 越来越稳, 为其他人提供一个坚实高大的肩膀, 是每个人的责任.
让每个人可以看到并抓到同伴的肩膀, 登上下一个更高的位置, 是公司的责任.
在技术研发方面, 我们能为其他人做的, 就是把我们的经验和思路, 标准化成各种各样的工具, 零件, 构成一个整体, 就像Thomas 拿到的可靠零件一样, 让其他人在这个平台上跑的快跑的稳.
公司里工具体系最好的样子
简单说, 公司的自我发展应该是小金字塔到大金字塔的演变.
从最初的几个工具支持一个产品, 几个工具支持一个产品, 演变成所有的工具支持所有的产品:
如果像上图这样,从3个产品各自独立维护3个它们所需的工具(有重复). 到右边5个工具共同支持3个产品, 需要花费精力的从9个单位变成5个单位, 从某种意义上来说, 效率就从5个单位提升到9个单位.
质量
互相支持就是为其他人提供可以用的东西, 而且这个东西必须是可靠的.
就像一个精确符合标准的螺丝钉, 可以让再次使用它的人, 免除后顾之忧. 耦合紧密, 无需担心松动脱落.
这些"无需担心" 就是效率的大大提升! 因为使用它的人可以把自己的时间集中到该花时间的地方.
复用的前提就是质量, 高质量的复用是效率的提升, 低质量零件的复用是故障率的提升.
这也就是为什么质量是体系化研发的基础.
我在这里不直接讨论效率的问题, 因为质量和效率是因果关系, 有了前者后者是一个无需追求的直接结果.
第一优先就是保证质量, 否则就是在努力燃烧生命去生产垃圾. 浪费员工的生命, 浪费公司发展的良机.
一个不能保证质量的环境是在制造天使, 努力的天使们每天的做的事情就是往地球上添屎.
受早期互联网产品的影响(一类通常通过客服来弥补质量问题的产业), 质量在行业里不太受关注, 因为质量产生的影响需要几个月或1,2年才显现出来. 互联网应用中, 90%的代码生存时间不超过2年, 有些可能更短.
一般来说, 短期的质量下滑带来的问题都可以通过运营/客服来弥补.
长期来看, 短期收益越来越被喜欢, 因为疗效快. 长期收益被忽视. 慢慢的运营/客服比重越来越大, 直到增加100%运营/客服投入却只能换来10%收益增长的时候.
质量的测量
保证质量首先要了解质量.
质量是一个不太容易度量的东西, 因为包含的方面很多, 一个细节的疏漏往往就毁掉了一个产品的质量, 即使其他1000个细节做的都很好.
要想度量一个产品的质量就要去度量每个细节, 漏掉一个都不行. 例如:
如果我对一辆好的轿车的描述是: 起步快, 减震好, 外观华丽内置豪华, 还带遥控(batman的车); 听起来好像没有问题.
那么可以有一辆车: 起步快, 减震好, 外观华丽内置豪华, 还带遥控, 但没有座椅的车子.
这里例子也说明为什么KPI这类东西对提升产品质量的帮助不大.
早期产品质量不太重要, 因为只要有几个突出的亮点就够了. 但随着产品的成熟, 包括的环节越来越多越来越完整, 每个细节都变的非常重要, 最短板的位置决定这个产品的质量.
如果要发现软件质量的问题, 短期内没有非常好的方法, 但长期上也可以用比较简单的方法可以量化出来, 关注2个概念: 代码的 增长率 和 丢弃率 :
- 增长率是一段时间内代码行数增加的百分比, 一般是业务增长造成了代码量的增加.
- 丢弃率是一段时间内代码被删掉的百分比, 一般是修复问题而造成了问题代码的丢弃.
如果增长率比较高, 而丢弃率比较低, 那么这个产品的质量是比较好的, 因为没有太多的重复劳动, 大部分精力都花在了创造新的东西上面.
如果丢弃率比较高, 说明在研发过程中有大量的修改, 造成了重复的开发, 重复的开发代表着发现了问题而去修正的行为. 这时候就需要关注这些被修改的地方了, 需要深入研究下为什么最初引入了问题.
测量质量的例子: nginx
拿nginx举例, 它现在已经是非常稳定的一个软件, 从它的git历史分析, 13年内, 代码增长率: 201%, 丢弃率 48%:
- 13年前到5年前, 129%的增长, 39%的丢弃.
- 最近5年内, 代码增长率31%, 丢弃率 11%.
- 最近1年内, 代码增长4%, 代码丢弃了仅仅1%.
可以看出, 最初的一段时间是大规模的开发, 而也发现了不少问题(丢弃率39%), 最近一段时间, 有些小的修补, 产品非常稳定.
稳定可靠的标准
一个稳定发育的产品, 它应该符合这样的比例才算是稳定:
- 年丢弃率应该在 10% 以内.
- 年增长率应远高于丢弃率, 例如10倍左右.
短期来说, 如果增长率是丢弃率的10倍左右, 就非常健康了.
这里说的短期, 也不是指非常短, 至少要包括一个反馈周期: 也就是能检测出产品问题的一个时间周期, 可以是测试/review的周期, 也可以是POC试用/反馈/调试的周期.
这里的2个参数是检测方法, 不能作为KPI或其他考核标准. 否则就会完全变了味道.
我从不怀疑工程师反抗此类愚蠢KPI的能力.
改进质量
于是, 改进产品质量的方法也变得很直接:
Review下被删掉的代码都是什么, 为什么删掉, 当时的考虑哪里不全, 漏掉了什么导致要修改.
长期行动: 建立工具体系: 基础代码库
一般业务相关的代码, 会有非常剧烈的变化, 这部分代码在最上层, 对质量的影响不大, 而它依赖的下层代码, 对质量起了非常重要的作用.
拿我们自己的代码说事:
- 8年前的代码中, 有58% 已经被丢弃, 那个时候刚刚上线, 也是经历了一波快糙猛的迭代.
- 现在的情况会好很多, 去年到今年, 40%的增长, 只有9%的代码被丢弃,
- 7年前的一年里: 28%增加, 16% 丢弃.
- 8年前的一年里, 44%增加 13% 丢弃.
这里里面有很大一部分是是业务变化带来的修改. 而频繁修改对保证质量是有阻碍的, 于是后来基于产品对质量的依赖, 将一部分基础工具独立于业务项目之外, 有2个目的:
- 独立测试方便, 容易保证质量.
- 方便共享.
目前来说这2个代码包的一年内的变化:
- pykit: 5% 被丢弃 138% 增加; https://github.com/bsc-s2/pykit
- lua-acid: 7% 被丢弃 135% 增加; https://github.com/bsc-s2/lua-acid
这2个工具包, 目前在我司4个产品中被使用:
- 存储(S2),
- 冷数据(EC),
- 边缘计算(Edge),
- 配置中心(Config-Center).
已经做到了一个工具支持多个产品的复用模式, 而这个工具又是非常稳定的, 上层的业务开发就变得非常的高效.
当初做EC的时候, 能在40天里上线, 1部分来自于经验积累, 而更多的部分来自于上面这2个工具的积累:
- 其中的分布式redis集群,
- 分布式事务管理,
- 元数据中心
等几个模块, 每个模块大约只有3,4个人天的时间开销(包括逻辑实现,运维工具, 日志管理等等完整的产品), 就是因为工具的完善(如果没有工具的积累, 大概会像Thomas 那样忙9个月来).
日常行动
在日常提高产品质量的行为中, 我会按照以下顺序去做, 时间总是不够, 如果必须做选择, 从左边最重要的方面开始:
代码可读性 > review > 文档 > 测试
以上优先级是根据长期收益来评估的. 短期测试最有效, 长期来说, 可读性让一个工具的复用率更高, 而每次复用就是一次效率的提升.
可能会有人有异议, 例如对于测试的重要性排在最后一个. 有TDD, 也有反TDD, 不展开了. 个人理解上:
- 对测试的依赖是最容易生产没人敢动的代码的方式.
- 出色的可读性则是生产人人都能修改的代码的方式.
毕竟测试是用来检查低级(已知的)的错误的.
总结
巨人的肩膀就是我们自己的肩膀, 也只能是我们自己的肩膀.
让自己的肩膀越来越高, 越来越稳, 为其他人提供一个坚实高大的肩膀, 是每个人的责任.
让每个人可以看到并抓到同伴的肩膀, 登上下一个更高的位置, 是公司的责任.
最后, 让每个工程师, 能制造出一个漂亮的面包机.