本文主人公从事软件开发近 20 年,既在大公司待过,也在小公司待过。在他的职业生涯中,发现了很多跟技术选型相关的各种问题:应该选新潮的技术还是无聊的技术?应该搭建极客范的技术栈还是使用基础的工具?应该用开发者喜欢的还是维护起来省事的?他对此作了深入的剖析,希望能帮到你。(多图流量预警!)
我叫 Dan McKinley,坑里的那个人就是我。
我现在在一家叫作 Mailchimp 的公司上班。更早之前是在 Etsy,因为在 Etsy 待的时间比较长,所以后面会更多地提到我在这家公司的经历。其实在离开 Etsy 之后,我也在其他几家公司干过。
我既在大公司待过,也在小公司待过,还创办过自己的公司。在经历了这些公司之后,我注意到了一些现象。
大公司有自己的做事方式,他们提供了“沙盒”一样的环境,在这样的环境里,会有人满足你的需求,帮你答疑解惑,让你感觉受到了“百般宠爱”。
但我也经历过几个过渡时期,在这些过渡时期,需要自己解决一些棘手的问题。
首先,如何选择合适的技术?
另一个我比较关心的问题是:如何让开发人员开心地使用这些技术?因为我自己也是开发者,所以这一点对于我来说比较重要。如果有可能,我会尽量让自己过得开心些。
如果你问开发人员什么东西会让他们开心,他们通常会说:“如果可以使用 Clojure 作为开发语言,我就会很开心”。我不否认,当他们说这些话的时候,他们脑子里浮现的应该是曾经最让他们感到兴奋的经历。
但我相信他们所描述的这种状态是他们所能达到的最高的精神境界。
我以前也喜欢这样。
例如,Etsy 的早期应用程序是用 PHP 开发的,而开发这些应用程序的人当时刚好在学习 PHP。
但我却花了好几年时间尽量不去碰触这些 PHP 代码,我甚至尝试使用 Scala 和 MongoDB 来重新开发这些服务,因为我认为它们才是更好的技术栈,可以解决所有的开发效率问题。但事实上,没有任何迹象表明我的做法是对的。
现在在网上还能找到我在这段时期所做的一些尴尬的事情,你可以把它们搜出来,然后用它们来取笑我。现在的 Etsy 员工还在拿这些东西来调侃我。
后来我创办了自己的公司,用上了 Clojure。虽然,这家公司现在已经不在了。但请不要多想,公司倒闭并不是因为使用了 Clojure。
不过我还是很乐意分享这段经历,毕竟我也是个体验过函数式编程乐趣的人。
我并不是一个容易沉迷于开发技术的工程师。我的其他演讲很少是关于工程技术的。
我还没老到或者脾气暴躁到成为那样的人。但通过总结马斯洛需求金字塔理论,我也有了自己的看法。
简单地说,马斯洛需求金字塔就是指在满足更高层次的需求之前,需要先满足较低层次的需求。如果你连肚子都填不饱,哪里还有心情吟诗作对?
虽然这个比喻不一定非常贴切,但在软件开发领域,这是事实。如果你还在忙于讨论要使用哪个数据库,怎么可能有时间去担心整个产品的蓝图?
幸运的是,在我经历的一些场景中,基本需求都得到了满足,所以我希望也能让其他项目进入这样的状态。
要达到这样的状态,首先要集中注意力。人类专注细节的能力是有限的。
我的朋友 Andrew 总是穿同一牌子的黑 T 恤。他认为,如果把花在挑选衣服上的精力囤起来,就可以把它们花在其他更有意义的事情上。
我不知道这样做算不算缺乏品味,但我觉得是有意义的。
接下来我要谈谈我的想法。假设我们手上有一些代币,但数量有限。
这些代币代表了我们的创新能力或解决困难挑战的能力。在一家公司的早期,我们可能有三枚这样的代币。
那么你的公司会怎么做?Etsy,也就是我之前工作过的公司,宣称要重塑整个世界的经济。
我不知道开发者该如何看待一家科技公司所宣扬的使命,我是觉得压根就不应该把它当回事。
不过,现在姑且让我们保留一点点“天真”,看看如果这家公司真的要改变世界,结果会怎样。
重塑整个世界经济不是一件小事,可能需要花掉一个代币。
离开 Etsy 之后,我加入了另一家公司,这家公司想要“增加互联网的 GDP”。
这个看起来也是件大事,所以可能也需要花掉一个或两个或所有的代币。
即使你认为创新是一种稀缺资源,但对于已经很成熟的数据库或编程范式来说,创新并没有太大意义。
问题不在于说它们的创新不可行,实际上,它们是可行的。
但在软件开发领域,越是晚出现的东西越是需要更多的关注。
为了找出其中的原因,我想从哲学方面入手。对于一项技术,我知道多少东西?
我不喜欢 Donald Rumsfeld(美国前国防部长),我希望他滚蛋。但他与我们要讨论的问题有关,所以我不得不忍受他恶魔般的存在。
在面对未知事物时,通常有两种情况。
一种是“已知的未知”(known unknown),也就是那些我们知道自己不了解的东西。还有一种是“未知的未知”(unknown unknown),也就是那些我们不知道自己不知道的东西。
这是一个“已知未知”的例子。当数据库发生故障时,我们可能不知道具体原因是什么,但我们知道网络分区有可能是造成故障的原因之一。因为我们知道有这个可能性,所以可以进行针对性的测试。或者我们也可以双手合十,希望这个跟网络分区没有关系。不管怎样,我们都知道有这个可能性存在。
还有一些是“未知的未知”。这是我最近看到的一个例子:一个人花了四个月时间试图弄清楚为什么会出现 GC 停顿,结果发现是因为他往文件中写入了统计信息。显然,他事先并不知道会发生这样的事情。
软件中的很多 bug 都是这样的。我们并不知道系统里存在这些 bug,它们都是“未知的未知”。
现在,我们知道这两种“未知”在软件系统中都存在。一个 10 年的老软件系统通常在 JIRA 里会有很多相应的 ticket,没有人会去修复这些问题。除了这些,系统里还会有很多未知的 bug。
但并非所有技术都是一样的,新技术更有可能出现这两种情况。
我选择了“无聊的技术”作为标题,并为此后悔了好多天。因为有人说:“无聊的就是糟糕的,你为什么还说它是好东西呢?”
但我所说的“无聊”与 CSPAN(美国总统电视系列)的无聊是不一样的,我所说的“无聊”是指有些东西我们已经很了解了。如果你认为一项技术不够好,那是因为你已经知道其中的原因了。
你可以只使用 Postgres,这样就万事大吉了?不,你所选择的技术组合也有问题。
假设你已经使用了这个技术栈:Python、Memcached、MySQL 和 Apache。
然后你有一个新的问题需要解决。那么,你认为在技术栈里加入 Ruby 会有助于你解决问题吗?
几乎所有人都会说:“天哪,这怎么可能!”
我们知道,加入 Ruby 给我们带来的边际效益并不会多过它给我们带来的复杂性,因为 Python 和 Ruby 基本上就是类似的东西。
好吧,那么加入 Redis 呢?我们已经有 MySQL 和 Memcached 了,还需要加入 Redis 吗?
从我的经验来看,这就是问题的关键所在。出了问题就打着多语言编程的旗号,就像带着一班人攻击巴士底狱一样,然后说:“你不能阻止我们使用最好的工具来完成这项工作。”
当人们开始屈服于这种本能,就会说服自己:这是在给开发者更多的自由。的确,这是一种自由,但其实是一种狭义的自由。
那么,这背后究竟是怎么回事?让我们来探究一下。
当我们要往项目里添加一些半冗余的技术时,通常是这想的:“这项新技术在短期内会让开发变得更容易,它给我们带来的好处超过了采用新技术所耗费的成本”。
我们可以换一种结构化的思维来考虑这个问题。
假设你的工作就像我的朋友 Coda 所说的那样:通过技术来解决业务问题。
因为我们所在的领域和计算机科学有一定关系,所以不妨先假设自己是计算机科学家,并用二分图来描述我们的问题。
我们需要把左图所有的点与右图的点连接起来,这样才算把问题解决了。连接一条边表示做出了一项技术决策。
每一个技术决策都有一定的维护成本,但同时也享受着新技术带来的好处。
也就是说,每一项技术决策既会给我们带来好处,也需要我们承担一定的成本。
我们可以使用之前已经选好的技术。
也可以选择其他技术。选择新技术需要承担相应的成本,但如果采用新技术会加快开发速度,那么就是值得的。
我们试图最小化这个成本函数。总成本等于总维护成本减去开发速度的提升和从这些技术决策中得到的好处。
我们的行为实际上取决于方程中的哪一项在现实当中占主导地位。
如果技术维护成本很高,那么成本就占主导地位。如果技术极大地改变了开发方式,那么技术所带来的好处就占主导地位。
所以,你可能会像图中所示的那样,使用不同的技术把所有问题都解决了。
如果说每增加一种技术的成本很低,那么这样做就没什么问题。
这是另外一种策略。我们只选择了一部分技术来解决所有问题。
当新技术会给我们带来很大负担时才会这么做。
在现实中,新的技术决策通常伴随着很大的负担。
长期维护一项技术的成本通常会超过它能够给我们带来的便利性。
所以,通常我们会选择可以解决所有问题的最小技术集合。
要把技术维护做到很专业其实是很难的。在刚开始时可能很容易,但越到后面就越难。
为什么会这样?添加新技术很容易,但要维护好并不容易。
比如,我现在就可以用 brew 安装一个数据库,然后开始写代码。但要在生产环境中运行整个系统又是另一码事了。
所以,技术多元化并不是我们要追求的自由。
如果你将局部决策权赋予个别团队,会对全局造成伤害。
从某个角度来看,这确实是一种自由。你把一团链条和挂锁交给了开发者,然后告诉他们可以自由地做出技术决策。这些技术决策会一直伴随着它们,直到死神来临……
更糟糕的是,技术多元化不仅会给我们带来重复劳动和维护开销,还消除了使用共享平台给我们带来的积极正面的好处。
以 Etsy 网站的活动页为例。
Twitter 和 Facebook 都有类似的功能。Etsy 在经历了多年的风投之后,也想要一个跟他们类似的功能。
所以,在 2000 年,我开发了这个小功能。
要开发一个活动流功能,可以像下面这样:从外部获取事件,把事件写入数据库。然后另一个进程(比如机器学习)对这些事件进行聚合,把结果写到 Redis 里,最后前端流量就可以访问 Redis 里的数据了。
在我们刚开始开发这个功能时还没有 Redis,所以我们选择了 Memcached。它们的功能很类似:存储二进制对象,然后通过相似的 API 获取对象。但它们在某些方面很不一样,对于我们来说,最大的不同是 Redis 支持持久化,而 Memcached 不支持。
也就是说,在使用 Memcached 时,我们需要做一些额外的工作。因为可能当你需要某些数据时,Memcached 刚好给你弄丢了。
为此,我们需要多做很多额外的开发工作。但我们将这些额外工作与新加入一个数据库可能带来的运营成本相比,还是决定硬着头皮继续使用 Memcached。
就这样过了几年,我们也很少想起这件事。
有一天,我不禁想起了这个功能。然后很吃惊地发现这个功能的使用流量比最开始时增加了 20 倍。
这个功能的用户增加了 2000%,而我竟然毫不知情。当然,这无疑是我技术职业生涯的一个巨大成就。
之所以会出现这种情况,是因为我们使用了共享基础设施。共享基础设施会有专人照看,他们会在必要的时候增加更多的 MySQL 实例或者其他资源,还有一些人负责把新机器运行数据中心,把它们加到基础设施里。这是一种横向伸缩。但是,这些人都不知道我们开发的这个应用程序。
如果我们当初使用的是 Redis,那么可以肯定的是,在用户流量增加 20 倍之后,一定会在某个时刻出现瓶颈。如果是这样的话,我们就不得不自己去倒腾 Redis 的扩容问题。
或者更有可能是让其他人来做这件事情,因为我们的团队在一年后解散了。
但我发现,人们其实并不喜欢给别人擦屁股,所以这件事对于任何人来说都是很件苦差事。
我想借这个例子告诉大家,请尽量选择成熟的技术,而且尽量不要使用过多的技术。
但这也只是个建议,请不要把它当成是一个准则,因为有时候往技术栈里添加新技术也是有必要的。
问题在于如何选择新技术。
简单地说就是:你要和大家一起讨论。
因为在公司层面采用新技术涉及到整个公司的利益,并不是工程师个人的事情。
在讨论过程中,你可以这么问:“如果不使用新技术,那该怎么解决眼前的这个问题?”
这个问题其实就是要采用新技术的一个征兆。“可能是因为我们没有使用 Cassandra,要不我们试试看”。只要走多这一步,后面就可以停止讨论了。
假设你在现实当中遇到了这样的问题。比如,你在生产环境中部署了一个服务,而你认为如果不在使用新技术就无法完成新的功能,那可能是因为你想得不够多。
你可能需要使用一些特别的方法,但不管怎样,你完全可以基于简单的技术栈获得更好的效果。
你可以把需要做的事情列出来,你会发现情况并没有想象的那么糟糕。即使它们看起来很糟糕,但肯定不会比维护新技术更糟糕。
当然,也会出现另一种情况:当你把所有东西都列出来后,你会发现采用新技术可能更值得。
如果你决定采用新技术,那么就要想办法把风险降到最低。不要试图一次性重写整个应用程序,而是要循序渐进,每次修改一点点,逐步建立信心。
如果你往技术栈里添加了与之前类似的技术,那么就要想办法把旧技术替换掉,而不是同时维护两种技术。这是一个长期的计划。如果发现新技术不合适,还要有回退的能力。
总 结
选择人们所熟知的技术。
所选择的技术应该能够让你把注意力放在值得的东西上。
从整体考虑问题,你所选择的技术应该能够覆盖到整个问题领域,并解决所有问题。
掌握你所选择的技术,这一点很关键。
我发现几乎所有的软件系统都遵循这个定律。在刚开始时你会觉得它们很烂,因为你会碰到很多问题。
如果你是一个新手,可以试着在实践中验证这条定律。你可以试着把新开发的功能部署到生产环境,然后你就会想:下一个新功能我想尝试新的技术。
但使用新技术并不一定会更好,因为你根本不知道它们还会出什么问题。
可能是因为你跳过了曲线的”Mastery“部分。尽管在这个阶段仍然存在问题,但你会觉得这些问题是可控的。
这个定律有一个可怕的悖论:或许你要使用你最讨厌的技术,因为你越是讨厌它,说明你对它越了解。
在使用新技术时,你需要遵循一定的流程,比如先和其他人一起讨论。
你应该试着顺着马洛斯需求金字塔网上爬,从全局看问题,而不是每天想着是使用这个数据库还是那个数据库。如果你每天的工作就是干这个,那一定是哪里出了问题。
做有意义的工作会让你幸福感满满。
好了,现在可以把我从坑里挖出来了吗?
评论 8 条评论