我工作的公司正在逐渐失去“创业公司”的特质。对此我努力克制不让自己陷入怀旧当中。无论如何,这说明我们运转良好;最近几个月里我们获得了不少有分量的褒奖和重量级订单,说明我们干得不错。心怀感激的同时,变化不期而至。
随着过渡期的结束,我们需要在一些事情上有所升级。我们雇了一个 CTO 和一个企业架构师。诸如“遵从 PCI”、“负载测试”和“问题根源分析”之类的辞令成了办公室里的惯用语。我们开始更加小心翼翼地部署产品代码,我们变成熟了。
但很多事情并未变化——最重要的是,公司还是那个公司;在我加入时,我领会到在这里弥补技术债是高优先级的任务——目前也依然如此;对代码复查的抵触文化依然盛行;我们依然要身兼数职;我们在快速前进,所以需要轻装前行——保留那些实验证明还不错的实践方法,丢掉其它的——感谢前人的远见,留下的比丢掉的多;从我刚来到现在我们的开发流程就没变过,它运转良好。
尽管如此我还是要多说几句。我们不会因为“这玩意还能运转”就因循守旧,因为实际上如果有无限时间和资金以及几个踏实肯干的程序员,任何东西最终总能运转起来的。但这只是及格而已,对我来说更重要的是,其中的过程能让我这种 A 型(译注:争强好胜暴躁侵略性)+ 强迫症工程师人格获得满足感。我们不该浪费时间,我们的代码库不应随着时间推移变烂,我不会去添加那些我讨厌的功能,我们的工作流程能让我每天心情愉悦。如果你也是码农,那么一定知道这听上去有多美好。
而我们要做的,正是后者,这就是我们的秘诀。
一般人(包括程序员自己)都倾向于把程序员当作写代码的机器,头戴耳机不停敲出分支语句的聪明人。但这种简式化不足以发挥我们全部的潜能,否则的话,一个程序员的价值就完全取决于他 / 她的技术知识和打字速度了。一个程序员最好的状态应该像对付软件的瑞士军刀,富有创造力、洞察力、理性思维,在公司讨论会中能够充当重要角色。能够认识到这一点的公司会拥有更多快乐和高产的程序员,那些(不管你信不信)不是所有时间都盯着代码的程序员。DirectScale 正擅长于此。
广告时间已经足够长了:1 我们到底在干什么,2 我们的这套方法代表了通常意义上的最佳实践集,不仅对我们,也对任何软件开发团队同样有效。为什么我确信这一点?说来话长。我们来进入正题吧。
我们(从来)不做什么
寻常公司的常见“敏捷”工作流大概是这样的:
- 某些衣冠革履的家伙(高管)拍板,要上一个新功能。
- 他找来另外两个主管,轻描淡写地确定了这个功能是干啥的,长啥样。没人征求过程序员的意见(稍后详述),整个功能设计幼稚得无可救药。
- 他们整理好他们的讨论结果,发 email 给开发部门不写代码的老大。他们遗漏了一些重要细节。
- 不写代码的老大(项目经理?产品主管?敏捷教练?或随便什么新潮头衔)写下一堆业务需求,一部分来自 email,一部分来自他自己对公司愿景的理解,一部分是他的个人偏好。接下来的两天里,这些业务需求被神秘地转化成了用户故事(user stories)。
- 一个程序员分到了其中一个用户故事。极不走运的情况下,他要估算完成所需的时间:要用到高级微积分和特殊的咒语才能算得出。他马上开始思考如何编码,并抑制不住写下 55 行优雅的 C#原始代码。脑中的思绪如尼亚加拉瀑布般倾泻到键盘上。新功能就这样突然成真了。他提交了一个 pull request 并像法国大厨那样亲吻自己的手指。
- 开发组长复查了这个 pull request,毙掉了它并给出 17 条解释:代码性能很差、有个边界情况方法没有考虑到、整个类读起来像在读恐怖小说、并且完成的功能跟一开始的定义根本不沾边。
- 程序员和组长反复拉锯,重写了两遍代码,两天后他们都对修改后的结果满意了。
- 代码当天就被合并部署到产品中了。
- 几天后主管们看到一堆全新的版本更新说明,对这轮冲刺(sprint)中所有事情都很满意,但其中一位奇怪为什么他提的需求没实现。他开始重复第 2 步。
很显然这并不明智。这就像是翻版的传话游戏、看图说话或者20 猜,作为游戏玩玩还不错,拿来运作公司就太扯了。而实际上,硅谷精英们还在宣扬这种方式,敏捷教练还在鼓吹它,而年轻的团队还把它当作默认工作方式。
我们可以做得更好,现在从第一步开始重来。
把程序员拉进来参与其中
在DirectScale,我们不会雇佣一群想法哥,围坐在一起编故事让我们实现。我不想戳破这个现实,但很抱歉,实际上并没有“想法哥”这个职位。事实是人人都有想法。关于你的产品如何提高以及解决什么问题,人人都能贡献好点子。而公司可以选择鼓励或者打压这些想法。大部分公司选择了打压,即便是以不起眼的方式:把程序员和设计人员排除在产品特性讨论之外,让中间人来传递指示和需求,或者把程序员的异议当作“懒惰”或者“抱怨”。
更好的方式是确保开发流程中每个阶段各方的意见都能顺畅表达,每次会议、每个功能点,从始至终都应如此。与其等到下次冲刺才发现你的新特性在技术上不可行,为什么不现在就去发现它呢?当然这意味着你的程序员要离开键盘(停止编码)很长时间,但如果你征求并听取他们的意见,他们就会鼎力相助,为团队共享的愿景付出,对产品特性负责,从而更好更快地完成他们自己的工作。新功能规模越大,需要的支持越多,也就需要更多程序员加入讨论。
如我之前提到的,这样做同样可以防止你浪费好几天的时间构想出一个新功能,但程序员不能(或不应该)这么实现。大部分人都有过这种经历,即看上去简单明了的功能有时候会超乎想象地复杂,不可能做到或者根本不应该这么搞。没人愿意等到功能发布日期确定以后再意识到这件事。
我最近参加的一次会议是关于一个大功能集的 Design Studio ,这关乎到 DirectScale 未来几年内最大的一笔收入来源。这也是我们一整年内讨论过的最重要的纵向产品。猜猜谁参加了?
- 一个用户体验(UX)设计师
- 三四个程序员
- 一个美术设计师
- 我们的产品经理
上级并没有给出我们要构建的功能细节,也没有任何 C 什么 O 或者副总参加会议。就只有我们。为什么不呢?我们是有备而来的:我们的 UX 设计师和产品经理都具备行业经验,并同几位功能集的潜在用户聊过。领导层已经表达了他们对功能集的期望(并不涉及对创作过程的控制)。我们已经有过几次讨论,限定了要解决的问题范围。而且毕竟这些东西最终要由我们来实现。
两个半小时以后(比一般的会议要久,但很值得),我们画了一些草图,对这个问题的理解达成了初步的共识,每个人都很满意我们的意见会成为最终结果的一部分。这就是我们的产品从无到有的过程。
开发设计(与代码复查相反)
一旦我们的想法有了雏形,我们就准备好编码了。但我们仍没打开 IDE 呢。
瞧,软件构建是一门遗失的艺术。Steve McConnell,《代码大全》(Code Complete)的作者,激情洋溢地花了数章的篇幅来讨论从业务需求创立后到真正开始编码前的过程——据他描述,这个过程是对每个类、接口、交互和数据节点的详细设计,要由办公室中最优秀的架构师来完成。McConnell 的观点是,如果编码时只有用户故事(user story),就好比没有蓝图就去盖一座摩天大楼一样。
但不知何故,大部分公司都忘了该怎么设计。
问题出在我们的敏捷流程例子中的第 4 步和第 5 步。在用户故事定义以后,程序员开始敲键盘之前,还发生了什么别的事?大部分情况下,什么都没发生。
一个有经验的程序员也许有能力预见到简单任务所需的所有工作量,但更多时候则是智者千虑必有一失。我们都经历过那种“噢该死”的时刻,就是在我们写下最后一行代码时,突然意识到整个情况并不符合我们的逻辑。
或者我们会沮丧地发现团队里的新手写了一整个类去解决问题,而这些问题在别处代码里早就有标准的、可复用的解决方案了。在开始编码之前,花 5 分钟分享已有的知识就能防止之后 3 小时重复造轮子。
还有,当代码还没成型时,什么质量流程之类的都被抛诸脑后:代码规范先放一边、可复用性回头再说。随着时间流逝代码变得凌乱不堪,bug 随处可见。不出几年,你将不得不处理这些遗留代码(想想都头大)。当然在代码复查中特定的问题会浮出水面,但这不过是亡羊补牢,一周周的开发时间、一轮轮的问题修复周期、程序员的热情和投入,这些都被浪费了。
其实 Steve McConnell 早在 1993 年就给出了建议,只要参考一下,所有这些问题就都能避免(至少缓解)。这就是:提前规划。
在 DirectScale,我们把代码复查提前了。与其木已成舟再去复查,不如一开始就确保无误。我们提前规划。我们管这叫开发设计(Dev Design)。这很像协作代码复查,但是是在任何人写下第一行代码之前进行。流程是这样的:
- 创建一个用户故事。
- 把这个用户故事的工作量分给一个程序员。
- 程序员获悉需求后,浏览代码库看看有没有什么有用的或相关的代码,调研可能会用到的库或者技术,然后写出详细报告,分步描述他将如何编码完成用户故事。生成的文档(开发设计)包括所有最终落在代码中的变量名、缓存技术、类、接口以及交互方法。极端情况下,程序员可能还要加入几行伪代码。
- 程序员安排一个和团队内其他程序员的会议。他们在一起复查这个开发设计,作必要的修改,提出可替代的方法,分享见解,作出架构上的决策。
- 团队完成开发设计中的任务。如果有新问题需要另行处理,他们会立刻讨论并决定该如何处理。
- 大部分情况下,不再需要代码复查。只需合并新代码并提交 QA 测试。
(点击放大图像)
开发设计的一部分示例。完整文档有4 页,包含5 个任务。我们用了很多速记法,并且省略了一些工具和服务的背景知识,如果有人不太熟悉的话,我们会在开发设计复查会议上解释。
通过这种手段我们很大程度上避免了重复拉锯。在我以前的公司,有个团队领导极其吹毛求疵,在他手下我每个pull request 平均要重写5 遍代码。现在,这个平均数是0。提前规划确保我们在一开始就能采用正确的方法做事,不至于南辕北辙。
20% 的时间(不是 Google 的那种)
在 DirectScale 我们不允许堆积技术债。因为技术债拖的时间越长就变得越多(就像普通债务一样),当它超过临界值后,就不可避免地会破坏用户体验,让客户不胜其扰。更要命的是,它对程序员是一种摧残:在不得不处理那些受牵连的代码时,“这写的什么破玩意儿”效应会让码农身心俱疲。
然而解决技术债却是一项反直觉的活动,对新手来说无法理解。如果一个资深程序员花数周时间偿还技术债(比如:重构代码、拆分类、理顺部件间的关系、完善边界条件的处理),那他最后用什么来展示工作成果呢?其他程序员也许能领会重构后的变化,但任何有“经理”头衔职位的人都只能相信自己的理解:没有新东西可以秀给客户、没有修复紧急bug、甚至应用本身也没明显变快。如果产品负责人看到你在版本更新说明里写了“处理技术债”,那他怎么知道你不是一整个星期都在玩“弹纸足球”的游戏呢?没办法知道。
其实只要拿它与真正的“债务”类比,事情就好解释了:假设你有$15,000 的信用卡欠费。如果你用接下来的3 笔薪水偿还掉它,那你能秀出什么新东西呢?基本上也没有,没有新家具、没有精美的晚餐、你也没修理烤炉或者车子。但是你就是比之前的状况要好很多。而且你最好应该认识到这一点。
为了防止程序员和经理们被工期和产品特性绑架,为了能够产出健康的代码,我们强制推行一种严格的保底策略:至少20% 的时间要用来解决技术债。这意味着至少1/5 的用户故事是做这个的。我们每周抽一点时间做标准化、重构、负载测试、调研以及生成开发者资源。我们做的这些事客户永远不会了解,但他们一定会了解的是,我们的软件比他们用过的所有软件都更先进、更可靠、更讨人喜欢。他们对此深信不疑。
尽管处理技术债的工作并不总是令人兴奋(尤其是重构,对一个程序员来说这是最有挑战性、也是最乏味的事情之一),也并不能像功能驱动的任务那样有立竿见影的效果,但它是有回报的。用一两天的时间专注修整和改善代码的感觉就像从感冒中痊愈一样:世界从此变得更美好了。不同以往,你的代码更加合理,更容易维护,就像是教科书中的一样。随着以往数月的技术债修补完毕,程序员再面对精心维护的代码时就会轻松多了。
用户故事去专家化(despecialization)
创业公司“臭名昭著”的一点就是给员工冠以各种不同的头衔。有些公司的工作职位头衔都是事后安上去的:很多时候你会变成(像我经历过的那样)一个后端程序员、前端程序员、QA 工程师、技术文章写手、西班牙语翻译、可用性复查专员以及打印技术支持。有些人不太喜欢小公司的这一点,但我很喜欢。
我们的团队鼓励将去专家化一直进行到用户故事为止。有些公司管这叫“群集”。我们的方法是这样的:当程序员准备好开始另一项任务时,无论他是否为用户故事中其他任务工作过(且无论他是否知道这项任务该怎么做),在我们的优先级公告板上他都会分到最顶端的任务。我们的团队要为产品负责,而不是为编程语言负责——如果入职时你就会T-SQL、C#、.NET、JavaScript、Angular.JS 和SASS 的话这当然很好,如果你不会,那因为任务需要也得学会。这样使得我们更像是艺术家而不是流水线上的装配工。我们的目标是确保团队所有成员都对代码中的每一部分有足够了解,上手就能干。尤其是紧急情况下更应如此。
这样也能够确保我们的团队通过“公交测试”:如果哪个团队成员明天被公交车撞了,我们的业务是否能不受连累运转如常?还是需要参考职责范围,把如何维护留下的代码这一艰巨任务划拨给某人?在DirectScale,除了我身边很有天赋很能干的合作者外,公交测试尤其针对那些专注于我不太关注的项目的员工。尽管如此我还是觉得我们可以通过测试,就算我们最聪明最勤奋的员工不幸突然去世,我们的工作还是能照常运转。而且谁都不会太过怀念那个人(就是我,玩笑玩笑)。
午餐研讨会(Brown bags)和职业发展
DirectScale 提倡持之以恒地学习和改进的文化。这不仅仅是指我们有提供 PluralSight 的会员赞助和公司图书馆。我们认为最好的老师就在我们自己人中间,最好的课程就是人与人之间的交流和探讨。为了达到这样的效果,每隔一两个月我们就会有组内的经验交流活动,我们称之为午餐研讨会( Brown bag )。
这通常包括一个讲座,由公司内某项技术的大拿主讲。我们讨论过的话题覆盖面很广,包括 CSS、团队合作、睡眠习惯以及 SQL 调试等等。任何话题都可以拿来讨论。
像去专家化一样,这也能帮助我们通过公交测试。一个真正热衷于某件事的人,给他一小时非正式的讨论时间,回馈给你的信息量绝对能让你大吃一惊。
这同样有助于使程序员心情愉悦。因为程序员在学习和成长的过程中最快乐。我还记得我最快乐的工作日——每天沉浸在工作带来的快乐当中,到家都还是兴高采烈——就是我学习新框架或新理念,并开始付诸实践学以致用的日子。对我们程序员而言,学而时习之,不亦说乎。
为什么这么快乐?
我曾经花了很长时间分析是什么能让我们快乐地工作。尽管我已指出我们在工作方法和思想中的优势,试图以此来全面均衡地解释,我还是略微担忧有人读到此处而完全不解其意。在有些地方,有些公司——我有太多程序员朋友似乎都在类似的公司工作过——有一群中层管理者,他们的思维停留在了 1906 年,他们所能想到的就是“谁在乎你快不快乐”?对他们来说程序员就像奶牛一样:把他们关在牛棚里,像挤奶一样让他们产出尽可能多的代码,然后遗忘他们。如果有人退出,只要给出的薪水有足够吸引力,总会有其他人顶替他。
如果你不幸拥有了这种态度的中层,我恳请你开掉他们。
如今的说法是:快乐的程序员才有惊人之作。事实上这差不多是种瓜得瓜种豆得豆的事情——成功的企业不会去问“我们是否应该对程序员好一点?”而是“我们怎样才能让程序员更快乐?”
另一方面,不开心的程序员会短暂地写一段时间烂代码然后拍屁股走人。如果你不把程序员是否开心当作全公司范围内优先考虑的事情,最终你将不得不面对程序员流动造成的全公司范围的危机。
我当然知道,作为程序员,这么说符合我自身利益最大化。你也可以指责我是在耍大牌。也许你是对的,但即使我是大牌,我也是用十个手指头辛苦劳作,写出优秀软件的大牌。这年月对我这种大牌的需求爆棚,而其实我离我这个领域真正的佼佼者也还相去甚远。
简而言之,尝试像优化卷烟厂流水线那样来优化你的程序员,这样做得不到半点好处。你应该放手让他们按自己的方式行事,而最终你也会对结果满意。快乐的程序员写出优秀代码,而写出优秀代码的程序员是快乐的。
结论
我已经带你大致了解了在一家真正懂得软件开发的公司工作是什么样子。我希望你看到了全貌。我已经很久没写代码了,但总的来说,程序员的经历还是改变了我对这个行业的整体看法。
你可能注意到了,招聘程序员竞争激烈,需要技巧。我会定期收到招聘者的来信,几个月前有一位甚至说服我一起共进午餐。为了确保我们有共同的理念,我问了他这样几个问题:
- 在你们的设计和探索阶段,程序员多大程度上可以参与进来?
- 你们如何处理技术债?
- 在你们公司代码复查流程是怎样的?
- 你觉得在你们的代码库里“烂代码”有多少?
这令他有些尴尬。但(感谢)他最终还是对我开诚布公:在他们公司这些方面没得到足够重视。我谢过他的午餐并表示我不太想进一步沟通了。
听着,如果你还是觉得足球或者可口可乐可以让程序员快乐,我在这明确地告诉你这行不通。有这些福利固然很好,但能让程序员真正快乐的是每天完成挑战,构建他们熟悉的、巧妙的、实现他们引以为傲的功能的代码。
如果我们从你这里得不到这些,那你也就没什么可以给我们了。
关于作者
Isaac Lyman: 程序员、诗人、音乐家、丈夫及摩门教徒。他的主页: http://isaaclyman.com
查看英文原文: https://hackernoon.com/development-driven-development-75c01b2afca1
感谢冬雨对本文的审校
评论