技术债:the good, the bad, and the tao

安姐最近一篇文章『关于技术债务』(请戳文末阅读原文访问)阐述了如何避免和处理技术债(tech debt),值得一读。也许是年关将至,出来混,欠的账都要还了,我正好也打算写写技术债,干脆趁热打铁,也来一篇。本文从另外一个视角看技术债。

首先,什么是技术债?通常意义上的技术债是指我们在开发产品或者功能过程中,快速(往往伴随着混乱和各种限制)地在时间和资源受限的情况下完成正常情况下无法完成的工作时引入的技术问题。这些问题可能会导致将来引入新的功能变得很困难,或者开发出来的功能很快到达容量上的瓶颈。这种由于时间和资源受限的情况下不得不牺牲质量引入的技术债是最主要的一种技术债。

第二种技术债是因为开发团队未能事先进行合理的设计,导致架构混乱或者过度设计,从而引发在未来时刻爆发的技术问题。

第三种技术债是随着时间的推移,一个原本设计良好的架构不断叠加第一种技术债,导致代码是 case by case 堆叠起来的。老的 case 不敢删除,新的 case 不断增长,使得代码膨胀到一个无法控制的规模。

还有一种技术债是因为开发团队的能力和水平有限,写出来的代码质量低下,从而引发在未来时刻爆发的技术问题。

大部分软件开发团队同时背负以上几种技术债。所有的技术债最终不得不被架构更加合理,接口更加清晰,效率更优,容量更大的方案取代,也就是所谓的还债。

注意技术债都是形容完成了工作但留下了遗憾或者隐患,完不成工作的那种叫技术败(tech failure),不在本文讨论当中。

The good

债务是人类经济活动中最伟大的发明之一,其重要性可以和纸币等量齐观。可以负责任地说,有经济的地方,就有债务。作为锄禾日当午的农民大军中的一员,我们码农最熟悉的债务就是房贷。房贷可以让我们在当下现金流短缺的情况下购买到我们将来才能负担得起的房子。如果没有房贷,我们是无法过上居者有其屋的生活的。

技术债既然被称之为一种债务,虽有债务的缺点,但也连带债务的好处。序员们并非活在乌托邦中,可以无所顾忌地追求极致的架构;我们生活在一个实际的,血淋淋的商业世界里。销售要某个产品和别人对标打单,市场要编制一个美丽的五彩缤纷的故事来应付发布,客户要求在限期之内完成某个他们自己也不知道什么时候才使用的功能(通常只是为了彰显甲方那种「我所说的,你都要照做」的气势),工程师就必须在限期之内完成。完不成,产品卖不出去,市场推广不开,客户和你一拍两散。还房贷?娃的奶粉钱?想都别想。所以这种时刻,引入技术债是不得已的聪明的做法。

这种做法在创业圈里有一个美丽冻人的名字:MVP(minimal viable product)。技术圈里已经快被人遗忘的,令人尊敬的老司机盖子同学就是个中好手,basic 解释器如此,DOS 系统也是如此。如果他老人家不付出技术债,恐怕还没拿到第一桶金就挂了,软件世界的版图也就可能没微软什么事了。

另一个经典的同时也令人扼腕的「举债经营」成功,不愿负债失败的例子是 mongodb / rethinkdb。做为一个 database,mongodb 在其前两个版本的打法是骇人听闻的:一个数据库竟然靠 mmap 来提高效率,通过 fsync 来保证持久化(如果你不打开 oplog 的话,就只能听天由命了),然后还好意思发布 1.0 一路到 2.x —— 有木有搞错!技术老司机们纷纷看不下去了,各种质疑雄文层出不穷,比如说经典的 call me maybe(请自行 google call me maybe mongodb)。可以看得出 mongodb 把技术债用到了极致,而且是用在了对于其他软件公司性命攸关的部分:数据库。而 rethinkdb 则走向了另一个极端,为了技术正确,一路闷头苦练内功。两个同样在2009年发布第一个版本的产品,2011年 mongodb 就已经有人在 production 里使用了,而直到 2015 年,rethinkdb 才遮遮掩掩地宣称它们 production ready。结果是 rethinkdb 错失了 NoSQL 的红利期,赚不到也筹不到足够的钱维持其运营,不得不解散团队。而 mongodb 在购买了 wiredtiger 引擎后,基本解决了最让人诟病的技术债,在软件服务的路上走得还算顺畅。

所以,不要怕引入技术债,相反,要敢于引入技术债,举债经营,尤其是新的产品和功能未能证实其价值的时候。通过引入技术债,我们以软件日后腾挪的空间换取了发展的时间。从这个意义上讲,MVP 不仅仅适用于创业公司,也适用于任何大小的公司。

The bad

债务的缺点自然是利息。房贷固然让你圆了一个安居乐业的梦,但经济不景气时可能导致的失业一下子会让你失去了偿债能力。银行会收了你的房子,残酷无情地敲碎你「人生蒸蒸日上」的黄粱美梦,让一切归零。所以有了房给贷这样的债务,一定要关注短期偿债能力,把流动比率或者速动比率控制在一个合理的范围内,同时要握有足够的自由现金流应对不时之需。

对于公司的运营来说,短期负债过多会影响正常运营甚至导致公司没有偿债能力从而不得不破产或者进行债务重组。对于软件开发而言,技术债所带来的利息是新功能更长的开发周期,或者老功能很快触碰天花板。这会影响公司接下来的发展。如果累积一段时间技术债的利息无法清偿,公司可能会技术破产,进而导致运营上的破产。twitter 在早期开发时,MVP 选用了 rails。快速的开发能力带来了快速的产品验证,然而 rails 的低效使得 twitter 很快在技术上触及了天花板(尽管 twitter 针对其做了无数优化):2007 - 2008 年,twitter 动不动就挂了,并且一度挂了三天( https://techcrunch.com/2008/04/22/twitter-may-not-have-to-care-about-uptime-any-longer/ )。后来在技术主管换血之后,它们痛定思痛,大刀阔斧做了债务重组,摒弃 rails,拥抱 java 生态圈,用 scala 重写很多核心服务,终于把服务稳定下来。

技术债一个很要命的问题是债务的叠加。生活中我们很少叠加债务,如果你有了房贷,你会减少各种开销,避免同时背上那些 APR 极高的信用卡债务。软件开发中我们却很容易叠加债务。这些叠加的技术债会使利息指数增长,从而反噬我们。

技术债还有一个很严重的问题是 backward compatibility。软件的有些接口设计上的缺陷被使用者当成了功能去使用,使用的范围之广以至于开发者在新版本中无法还债,只能被动地一路保有这样的债务。windows 系统在其演进过程中,有大量的缺陷和设计失误被开发者当成了功能,使得微软无法改变这些不合理的接口。这是历史的教训,我们需要小心这类技术债。一个很重要的原则是对外的接口(API,SDK等)一定要小心设计,如果要负债,让接口背后的实现去负债。

The tao

技术债并非洪水猛兽,我们要要合理控制,让其发挥债务的优势。处理之道:

1) 拥抱 MVP。先解决温饱问题,再考虑还债。

2) 把技术债外包出去,套句时髦的话,就是 tech debt as a service。infrastructure 交给 aws/azure,monitor 交给 datadog / newrelic,search 交给 elk / algolia,analytics 交给 mixpanel / 神策数据(好友的公司,免费硬广),支付交给 stripe / 微信 / 支付宝。。。将自己的非核心技术问题/难题转嫁给更合适的团队。不过这种债务除了需要支付月度的服务费作为利息外,还有另一种隐性的,代价不菲的利息:vendor lock-in。

3) 雇佣你所能获得的最优秀的人,给予她们你所能给予的,最能发挥她们能力的权限。由人引发的技术债是最让人痛心疾首无语凝噎的技术债。这条不解释。

4) 拥抱匡威定律。你的组织架构决定了你的代码结构。想要快速独立的功能交付能力,你要有包含所有角色,拥有直接决策权的端到端的功能团队,而不是开发,测试,运维等彼此独立,组织上汇报给不同 VP,优先级完全不在一个调子上,各自为战的团队;想要使用 micro service,组织结构上就要打造彼此平行的 service team。很多技术债的出现是由于技术方向和组织结构不协调导致的。

5) 在实现上可以多些负债,在接口上尽量减少负债。你有没有问过自己,除了从别人那里学习撰写代码的艺术和教育新人的目的外,为什么我们需要 code review?为什么我要关心别人的代码写成什么样子?我要关心 memcpy 的实现么?我要关心 twillio 究竟在代码级怎么把短信发给我的用户么?我不关心。我只关心 interface 和 SLA。而软件中重要的恰恰是 interface。为什么我们没有interface review?为什么如此重要的 interface review 要混在并不那么重要的 code review 中?code 可以被抛弃,被重写,但 interface 一旦确认,修改的代价会很大,尤其对外的 interface。

说句题外话。如果我们只做 interface review,不做 code review,有什么严重后果么? 貌似没有 —— 如果 code 写的不好,扔了重写就成。如果我们以这种思维打造软件,我们还需要整个开发团队使用一种或者少数集中语言么?我们还需要为每个工程师设置 peer backup 么?一旦一个工程师离职,他的工作在需要的时候能够被重写,是不是就足够了?对于这一点,不懂软件的 Bezos 有着远见卓识 —— 他在 02 年提出了一系列苛刻的关于 service instance 的构想,并强制研发团队照做。见我的文章:拥抱约束

6) 意识到软件是个有新陈代谢的有机体,并围绕这个思想来打造软件。代码是需要新陈代谢的,这意味着新的代码不断加入,旧的代码要不断删除。生物体里细胞坏了会重新造,组织死了会重新生成。生物体不会试图「修复」生老病死的细胞,只会将其杀死并代谢掉,同时重新生成功能一样的细胞。我们写代码时也要使每个部分保持独立(像一个个完整的细胞或者组织),留有日后自己或者别人将其完全删除重写的余地。这样,当我们主动或者被动引入技术债时,便心中不慌了 —— 因为我们知道,我们保留了删除和重写的权利。

为什么解决技术债时不是考虑重构,而是考虑重写?重构技术债太多的代码有重构的代价 —— 能重构代码,你得先把前任(或者几个月前的自己)的古怪逻辑搞清楚,还得小心不要让自己引入问题。很多时候,技术债累积过多的代码叠加了难以伸缩和扩充的问题,与其小心翼翼,瞻前顾后地重构不如重写来的干脆,也避免了半吊子工程。当然,如果能达到每个功能或者组建可以不费太大代价重写的理想,那么要选用以此为核心的架构,比如说 "micro service",并且制定相关的规则,比如说,一个 service 不超过一个程序员一周的工作量,以便于随时重写。

重写代码而非重构是足够疯狂的想法,但我们身边有个最好的例子:unix。有人反对 unix 是一系列 micro service 组成的系统么?估计没有。我们应该把我们打造的系统组织成围绕着一个核心的一个个 ls / grep / awk / vim...。vim 作为一个伟大的软件,其代码质量实在不敢让人恭维,所以有人干脆另起炉灶,照着 vim 的接口做了一套 neovim。

先写这么多。回头我就软件的新陈代谢,生老病死再写篇文章。

原文发布于微信公众号 - 程序人生(programmer_life)

原文发表时间:2016-11-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

可再生能源物联网:当物联网遇到能源

随着对全球变暖和化石燃料消耗问题的关注不断上升,世界正在转向替代能源以满足其需求。其中最具可持续性的选择是可再生能源。该领域遍及太阳能,风能,水力和地热等各种能...

57314
来自专栏养码场

限量领取|VIP前端视频,Web全栈开发工程师的必备武器

在过去的 Stack Overflow 开发者调查 中就显示,全栈开发工程师是最受欢迎的开发者职业。显然,IT工程师如果仅凭传统开发思维,无法突破固有知识体系,...

1422
来自专栏大数据文摘

【经济学人特别报告之广告业与技术】通过数据来了解你

2016
来自专栏罗超频道

盘点2012年中国互联网十大泛滥的东西

互联网加速了信息传播,社会化传播让传播速度指数级增长。互联网产品生产的短周期,使得好产品的设计、理念、模式和体验更容易传播到其他产品。不过,好的东西不一...

2928
来自专栏VRPinea

花式观景,虚拟喷气式飞行带你体验巴黎的美

2919
来自专栏程序员的知识天地

小程序未能成为APP们的救命稻草,未来更是让人担忧

从小程序开始公测第一天“所有的技术群都成了小程序群”,所有的开发者都成了小程序开发者,所有的公众号都成了小程序的义务宣传队,所有的App都像是头上长了删除号在瑟...

993
来自专栏罗超频道

实测第一弹!横亘在微信5.0面前的几座大山

虎嗅、雷锋2013年8月6日 8月5日,微信5.0千呼万唤始出来。 这个版本原定于今天下午15:00正式发布。不过由于没能控制住苹果App Store的审核节...

3446
来自专栏腾讯数据中心

“阴阳相济,生生不息”——数据中心经典医案赏析

所谓日久情深,作为数据中心经理,我们常常将数据中心当作自己的孩子,为之挡风避雨,为之担惊受怕。即使只出现风吹草动,我们亦是辗转反侧,夜不能寐。或是久病成良医,多...

2904
来自专栏机器人网

作不作? 科学家竟然教机器人拒绝人类命令

英媒称, 如果好莱坞给科学家上过什么课的话,那就是一旦机器开始反抗它们的创造者会出现什么情况。英国《每日邮报》网站报道称,尽管如此,专家却已开始教授机器人如何拒...

2866
来自专栏CDA数据分析师

数据分析之老顾客养成秘籍—让顾客回头的必杀技!

现在整个淘宝的市场形式日益严峻,新用户成本越来越高,转化越来越低,新品难以打开销路,新增商家日益增多,产品重复率大,竞争十分惨烈,且顾客及其容易流失。在这种情...

1849

扫码关注云+社区

领取腾讯云代金券