of drdr.xp Blog drdrxp @weibo.com

软件工程是个面包机

我们平时印象中的面包机是这个样子的:

烤面包机属于加热电器。其功能是在面包片附近生成足够的热量,以便对面包进行烘烤

面包机的原理非常的简单, 有个开关控制通电, 通过电热来加热, 这些基本的原理普通的中学生都知道, 也都能在wikipedia上找到. 这么简单的东西…

如果我们要亲手制作一个面包机, 需要花费多久呢?

话说, 英国有个叫Thomas Thwaites 的艺术家, 几年前, 花了9个月的时间做了一个面包机.

恩…没错, 9个月!

如果依赖现代社会的完善体系, 这件事情是很简单的,

之所以花费了这么久, 是因为他没有使用现成的零件, 一切都是从头开始制作的!

我们看看他到底怎么完成的:

首先他买来一个面包机拆了, 看看都包括哪些零件, 估计一下工作量, 恩…

大概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, 不展开了. 个人理解上:

  • 对测试的依赖是最容易生产没人敢动的代码的方式.
  • 出色的可读性则是生产人人都能修改的代码的方式.

毕竟测试是用来检查低级(已知的)的错误的.

总结

巨人的肩膀就是我们自己的肩膀, 也只能是我们自己的肩膀.

让自己的肩膀越来越高, 越来越稳, 为其他人提供一个坚实高大的肩膀, 是每个人的责任.

让每个人可以看到并抓到同伴的肩膀, 登上下一个更高的位置, 是公司的责任.

最后, 让每个工程师, 能制造出一个漂亮的面包机.

Archive