第 5 章 领导大型项目
是什么造就了一位出色的项目负责人?很少是因为天才,而是因为毅力、勇气,以及愿意和别人交谈。当然,有时你可能需要想出一个绝妙而富有灵感的解决方案。但通常,一个项目之所以困难,并不是因为你在挑战技术的边界,而是因为你要应对模糊性:方向不明确;人是混乱而复杂的;遗留系统的行为难以预测。当项目牵涉很多团队、途中要做出重大而有风险的决策,或者干脆就是混乱又令人困惑时,它需要一位能坚持到底、相信问题终能解决、并且能驾驭复杂性的技术负责人。这个人往往就是 Staff 工程师。
项目的一生
本章我们将看看一个大型、困难项目的一生。正如我们在第 4 章看到的,项目有各种各样的形态,但我会聚焦于那种至少持续几个月、需要多个团队投入工作的项目。在本章中,我假设你是项目的指定技术负责人,也许会把一些较小的部分委派给几位子负责人。我假设参与项目的人都不向你汇报,但大家仍然期望你拿出成果。可能还有其他领导者参与:你可能有对应的项目经理或产品经理,还可能有几位工程经理,各自带着一个团队负责自己的领域。1 但作为负责人,你要对结果负责。这意味着你要思考整个问题,包括那些落在团队之间缝隙里的部分,以及那些其实不属于任何人职责的部分。2
我们会从项目正式开始之前讲起,那时你面对的是一大堆庞杂、未经梳理、很可能让人不知所措的事情。我们会介绍一些摸清情况、理清头绪的技巧。我们还会谈谈如何建立这样一种关系:大家共享信息、互相帮助,而不是彼此竞争。
然后,我们会像项目经理那样为这件事的成功做好准备:想清楚交付物和里程碑,设定预期(包括你自己的预期),定义目标,加入问责机制和结构,明确角色分工,以及——成功的头号工具——把事情写下来。
项目搭建好、运转顺畅之后,我们会来看看如何推动它。你有一个目的地,为了到达那里,你需要转几个弯,做一些航向修正。我会谈谈如何探索解决方案空间,包括为工作确立框架、拆解问题、围绕它建立心智模型。当一个项目大到任何一个人都无法跟踪所有细节时,叙事就至关重要。我们会看看在设计、编码和做重大决策时可能遇到的一些常见陷阱。本章最后会讲如何发现你路上的障碍——冲突、不一致或目的地的变化——以及在绕开它们时如何清晰地沟通。
项目的结尾要到第 6 章才会讲,现在,让我们从最开始说起。
项目的开始
项目的开端可能一片混乱,大家四处打转,试图弄清楚各自要做什么、谁说了算。恭喜你:作为技术负责人,你说了算。差不多吧。
项目里的其他人并不是你的直接下属,他们仍然听从各自经理的安排。你有……也许有?……一项把某件事做成的授权。但可能还不是每个人都认同这项授权到底是什么,或者认同他们应该帮你完成它。如果涉及好几位不同的经理或总监,你负责什么、他们期望自己掌管什么,可能并不清楚。项目里可能还有其他高级工程师,有些人也许比你更资深。他们应该听你的吗?你必须采纳他们的建议吗?
如果你感到不知所措……
也许你是中途加入一个已有的项目,它有自己全部的历史、决策、人际关系动态和文档。也许项目是新的,但已经有了详细的需求、项目规格说明、里程碑,以及一份记录在案、热情满满的利益相关者名单。又或者,只有白板上的几笔潦草涂画,或者——常常令人沮丧的是——一堆长长的邮件讨论串(其中有些你还没被抄送),最终的结果是某位总监决定出资启动一个项目,去解决一个表述不清、没有界定范围的问题。几乎可以肯定,还有其他人想就这个问题给你提意见,而且可能还有一些迫在眉睫的截止日期,你想抢在前头。所有这些都会在你真正搞清楚项目是为了什么、你的角色是什么、大家对目标是否达成一致之前就冒出来。要考虑的东西太多了。
你该怎么办?你究竟该*从哪里开始?*从这种不知所措开始。
开始一个项目时感到不知所措是正常的。建立能让你驾驭这一切的心智地图需要时间和精力,在项目开始时,这可能会让你感觉超出了自己的承受能力。但用我朋友 Polina Giralt 的话说:“那种不适感就叫作学习。”管理这种不适是一项可以习得的技能。
你甚至可能会觉得自己是被错误地放到了这个位置上,或者这个项目对你来说太难了,你担心会让别人失望或者当众失败:这是一种常见的现象,叫作冒名顶替综合征。情绪上的不堪重负会妨碍你吸收知识,甚至影响你的表现,让冒名顶替综合征几乎成了自我实现的预言。
这些感受可能是一个信号,说明你在第 4 章介绍的某种资源上已经不足了。如果你耗尽了所有精力、时间不够,或者觉得自己不具备完成任务所需的技能,这些都可能表现为压力和焦虑。审视一下自己,问问是否有哪项资源已经处于令人担忧的水平。3 有没有什么办法能让你获得更多时间或精力,或者培养更多技能?有没有人可以帮忙?
你还可以想想,如果是别人在做这份工作,会是什么感觉。findhelp.org 的工程总监 George Mauer 告诉我,他过去也有冒名顶替综合征,直到他意识到“99% 的人并不比我更清楚该怎么做”。也许你是边做边摸索,但嘿,其他人也一样!是只有我这么觉得,还是这真的很让人安心?不管是谁来做这个项目,他们也都会觉得困难。
困难恰恰是关键所在。我发现,当我真正认识到这就是这份工作的本质时,我就能应对模糊性了。如果它不混乱、不困难,他们就不需要你了。所以,是的,你在做一件困难的事,你可能会犯错,但总得有人去做。这份工作就是要做那个足够勇敢、敢于犯错——并为错误负责——的人。如果没有信誉和社会资本,你不可能在职业生涯中走到这一步。一个错误不会毁了你。十个错误也不会毁了你。事实上,我们正是通过犯错来学习的。一切都会好起来的。
下面五件事可以让新项目没那么令人不知所措:
为自己建立一个锚点
无论项目大小,我都是这样开始的:我会创建一份只给自己看的文档,在项目期间充当我大脑的外置部分。里面会塞满不确定的信息和传闻、待追踪的线索、提醒、要点、待办事项和各种清单。当我不确定下一步做什么时,我会回到这份文档,看看过去的我认为什么是重要的。把所有东西都放在一个地方,至少能解决“我把那个记在哪儿了?”的问题。
和你的项目发起人谈谈
弄清楚谁是这个项目的发起人,以及他们希望你为他们做什么。然后约他们聊一聊。去之前做好准备,带上一份清晰的(最好是书面的)描述,说明你认为他们希望从项目中得到什么,以及成功是什么样子。问问他们是否同意。如果不同意,或者存在任何模糊之处,把他们告诉你的内容写下来,并再次确认你理解得没错。误解任务出奇地容易,尤其是在项目开始时,而与项目发起人的一次谈话可以确认你走在正确的道路上(这总是让人安心)。这也是一个好时机,可以理清关于你的角色、以及你应该向谁汇报项目进展的任何困惑。
视项目发起人的情况而定,你可能可以经常接触到他们,也可能只有一次谈话机会,之后几个月都见不到人(这是一种很糟糕的工作方式,但确实会发生)。你和他们交谈的机会越少,就越要在一开始把所有信息都拿到手。
决定向谁倾诉你的不确定
想一想,当项目遇到困难、你感到力不从心时,你要找谁倾诉。你手下的初级工程师可不是合适的人选!虽然你可以也应该对他们坦诚前方的一些困难,但他们指望你带来安全感和稳定感。是的,你应该让经验较少的同事看到,资深的人也在学习,但不要让你的恐惧蔓延到他们身上。你的一部分工作就是为他们消除压力,让这个项目为他们带来生活质量、技能、精力、信誉和社会资本。
这并不意味着你应该独自承担忧虑。试着找到至少一个可以让你坦诚表达、不必假装确定的人。这个人可以是你的经理、导师或同级:我在第 2 章讨论过的 Staff 工程师同级在这里就再合适不过了。选择一个会倾听、会认可你、会说“是啊,这些事对我来说也很难”的倾诉对象,而不是一个从不肯承认弱点、或者只想替你解决问题的人。当然,你也要成为他们或其他人的这样一个倾诉对象。
给自己一场胜利
如果问题仍然太大,那就争取迈出一步,任何一步都行,只要能帮助你对它施加一些控制。找人聊聊。画张图。写份文档。向别人描述这个问题。从某些方面来说,项目开始时是最容易“不知道”的时候。你可以在说任何话之前加一句“我刚接触这个,如果我理解错了请告诉我,但我认为我们要做的是这样的”,并从中学到很多东西。到后来,不知道某些事情的认知成本会高一些,甚至可能会让你觉得有点尴尬。(但其实并不尴尬!学习是件好事!)不要浪费这段可以坦然“不知道”的短暂时光。
发挥你的优势
还记得我们在第 3 章讨论战略时,我说过你应该围绕自己的优势来制定战略吗?这一点在这里同样适用。你需要尽可能高效地把大量信息装进大脑,所以要动用你最核心的能力。如果你最擅长代码,那就直接钻进去。如果你习惯先从人际关系入手,那就去找人聊。如果你喜欢阅读,那就去找文档。
你最喜欢的切入点可能无法提供你需要的全部信息,但它是一个很好的起点,能让你的大脑相信这只是又一个项目而已。说真的,你能行的。
建立上下文
项目开始时会充满模糊性。你可以通过像第 2 章那样做一次绘制地图的练习,为自己和他人建立视角。这意味着要构建你的定位地图:把工作放到更大的背景中看待;理解项目的目标、约束和历史;并清楚它如何与业务目标联系起来。这意味着要完善你的地形图:识别你要穿越的地形和那里的本地政治,了解项目中的人喜欢怎样工作,以及决策将如何做出。当然,你还需要一张藏宝图,标明你们要去往何处,以及沿途要停靠哪些里程碑。
以下是一些你需要为自己和其他所有人理清的上下文要点:
目标
你们为什么要做这个项目?在所有可能的业务目标、技术投资和待办任务中,为什么偏偏是这一个正在进行?“为什么”将成为贯穿整个项目的动力和指引。如果你着手做一件事却不知道为什么,那么你很可能会做错事。你可能完成了工作,却没有解决你本应解决的真正问题。我会在第 6 章更多地讨论这种现象。
理解“为什么”甚至可能让你否定项目的前提:如果你被要求领导的项目实际上无法实现目标,那么完成它只会浪费所有人的时间。最好尽早发现这一点。
客户需求
我经常讲一个故事,是关于我加入一个新的基础设施团队的第一周。团队的一位成员介绍了他们正在做的一个项目:升级某个系统,以便提供一项新功能。他说,另一个团队需要这个功能。“他们为什么需要它?”我问,很高兴有机会摸摸情况。“也许他们并不需要,”他说,“我们认为他们需要,但我们没办法知道。”这两个团队坐在同一栋楼里,在同一层。
即使是最内部的项目,你也有“客户”:总会有人使用你创造的东西。有时你自己就是客户。但大多数时候会是其他人。如果你不了解客户需要什么,你就造不出正确的东西。而如果你没有产品经理,那么搞清楚这些需求的责任很可能就落在你身上。这意味着要和客户交谈,并倾听他们的回应。
产品管理是一门庞大而困难的学科,要理解用户真正想要什么——而不是他们告诉你的——并不容易。4 这需要时间,所以要为此预留时间。请用户允许你在旁观察他们使用你要替换的软件。请内部用户描述他们希望拥有的 API,或者给他们看一份你认为他们想要的界面草图,看看他们如何与之交互。不要在脑子里替他们补上你希望他们说的话;要倾听他们实际的回应。尽量不要使用行话,因为人们可能会被吓到,不愿告诉你他们没听懂。如果你足够幸运,团队里有研究客户体验的 UX 研究员,一定要读他们的成果、和他们交流,并尽量旁听一些用户访谈。
即使你确实有产品经理,也不意味着你可以忽视客户!我很喜欢 Gergely Orosz(新闻通讯 The Pragmatic Engineer 的作者)在他的文章《Working with Product Managers: Advice from PMs》中汇集的与产品经理的对话,尤其是 Ebi Atawodi 的那句评论:“你也是‘产品’。”Atawodi 指出,工程团队应该和产品团队一样以客户为中心,关心业务背景、关键指标和客户体验。
成功指标
描述你将如何衡量成功。如果你在开发一个新功能,也许已经有一种拟定的衡量成功的方式,比如一份产品需求文档(PRD)。如果没有,你可能需要提出自己的指标。无论哪种情况,你都需要确保你的发起人和项目的其他负责人都认同这些指标。
成功指标并不总是显而易见的。软件项目有时会隐含地以写了多少代码来衡量进展,但代码的存在并不能告诉你是否真的解决了任何问题。在某些情况下,真正的成功恰恰来自删除代码。想一想对你的项目来说,成功究竟是什么样子。是用户带来更多收入、更少的故障,还是某个流程耗时更短?有没有一个客观指标,你现在就可以建立起来,以便比较前后的差别?微服务专家 Sarah Wells 在她的 Kubecon 主题演讲《The Challenges of Migrating 150+ Microservices to Kubernetes》中谈到,她用两种可衡量的方式来判断迁移是否成功:维持集群健康所花的时间,以及团队成员在 Slack 上抱怨功能不符合预期的冷嘲热讽消息的数量。
如果项目是你发起的,那么在定义成功指标时要更加严格。如果你的信誉和社会资本很雄厚,有时你可以凭借别人对你的信任,或者凭借一份有说服力的文档、一场鼓舞人心的演讲,说服其他人支持一个项目。但你无法确定自己是对的!对你自己的想法要抱持最大的怀疑,并尽快设立真实、可衡量的目标,这样你就能看到项目的走势。正如第 4 章所说,你的信誉和社会资本有升也有降。不要把它们当作推动项目继续下去的唯一动力。
发起人、利益相关者和客户
谁想要这个项目,谁在为它买单?项目的主要客户是谁?他们是内部的还是外部的?他们想要什么?在你和最初的项目发起人之间,有没有中间人?如果有产品需求文档,这些可能都已经写明了,但你可能需要自己弄清楚你的第一个客户或主要利益相关者是谁,他们希望从你这里看到什么,以及什么时候看到。如果这项工作的推动力来自你自己,那么你可能需要不断地为项目的合理性辩护,并确保它持续获得资金。如果你能找到其他同样需要它的人,推销这项工作的价值就会容易一些。
固定约束
也许有一些高级技术岗位,你一走进去就可以开始解决大问题,不受预算、时间、难相处的人或者现实中其他恼人因素的约束。不过我从没见过这样的岗位。通常你会在某些方面受到约束:要弄清楚这些约束是什么。有没有绝对不能推迟的截止日期?你有预算吗?有没有你依赖的团队可能忙得帮不上你,或者有你无法使用的系统组件?你是否不得不和难相处的人共事?
了解你的约束会设定你自己的预期,也会设定其他人的预期。“发布一个功能”和“在工程师不够、两位利益相关者对方向意见不一的情况下发布一个功能”之间有天壤之别。同样,为热切想要参与 beta 测试的团队打造一个内部平台,和试图说服一百名工程师迁移到一个他们讨厌的新系统,是完全不同的两个项目。描述你所处情况的现实,这样你就不会把所有时间都花在对现实不如你所愿而生气上。5
风险
这是一个“登月”项目还是一个“登顶”项目?它是感觉宏大而充满抱负,还是朝着正确方向迈出的相当直接的一步?在理想世界里,项目中的每个人都会完美同步地交付自己的部分,时间和精力的可用性都是可预测的(最好还能顺带提升信誉、技能、生活质量和社会资本!)。现实是,有些事情一定会出错,而且项目越雄心勃勃,风险就越大。试着预测一些风险。有什么事情可能会发生,阻止你按期达成目标?有没有未知因素、依赖关系,或者一旦离职就会让项目注定失败的关键人物?你可以通过明确自己的不确定领域来降低风险。你不知道什么?可以采取哪些方法让这些事情不那么模糊?这是你可以做原型的事情吗?以前有人做过这类项目吗?
最常见的风险之一是担心白费力气,担心做出来的东西最终根本没人用。如果你频繁地进行迭代式的变更,相比于在最后只有一次孤注一掷的发布,你会有更好的机会获得用户反馈并修正方向(甚至及早取消项目;我们会在第 6 章更多地讨论这一点)。
历史
即使这是一个全新的项目,也会有一些你需要了解的历史背景。这个项目的想法从何而来?它是否已经在全员大会或邮件中宣布过,从而设定了某些预期?如果项目不是全新的,它的历史可能模糊不清、充满纠葛。当一些团队已经尝试过解决某个问题却失败了,可能会留下一些期望你去使用或在其基础上构建的遗留组件,或者有些用法古怪的现有用户希望你继续支持他们。你还可能面对那些尝试过却失败了的人的怨气和恼火,如果你想重新点燃他们的热情,就需要非常谨慎地行事。
如果你是新加入一个已有项目,不要直接一头扎进去。多进行一些交谈。弄清楚在开始打造新方案之前,有哪些半成品系统是你必须使用、绕开或清理的。理解人们的感受和预期,并从他们的经验中学习。记住我在第 2 章提到的 Amazon 首席工程师社区的那条信条:“尊重前人的工作。”
团队
视项目规模而定,你可能只需要认识几个关键人物,也可能要面对一大群团队成员、负责人、利益相关者、客户和相关岗位上的人,其中有些人会影响你的方向,有些人会做出你必须应对的决策,还有一些人你永远不会直接交谈。还会有其他担任领导角色的人。
如果你领导的项目只涉及一个团队,你可能会定期和团队里的每个人交流。在一个涉及很多团队的更大的项目中,你需要在每个团队里有一个联系人。对于规模更大的项目,你可能在每个领域都有一位子负责人(见图 5-1)。又或者,你的项目只是一个更大项目的一部分,而你是一位子负责人。
图 5-1. 作为项目负责人(这棵树的根节点),你可能在其他几个团队中各有联系人或子负责人。其中一些子负责人可能又在指导他们自己的子负责人的工作。
与其他所有领导者建立良好的工作关系、互相帮助,这一点至关重要。不要把时间浪费在权力斗争上。如果你们合作良好,就更有可能实现共同的目标;当然,当你和身边的人相处融洽时,工作也会愉快得多(生活质量更高,精力更充沛!)。遗憾的是,有多位领导者往往意味着谁做什么的预期不明确——这是冲突的常见来源。要弄清楚领导者都有谁、他们如何参与项目,以及他们期望扮演什么角色。
为项目搭建结构
把这些上下文都记在心里之后,你就可以开始建立那些有助于运作项目的正式结构了。设定预期和结构、遵循计划可能很耗时,但它们确实能提高你迫不及待想要开始的这件事真正成功的可能性。参与的人越多,你就越要确保大家的预期是一致的。这些结构也会成为帮助你掌控局面的工具——所以如果你仍然有点不知所措,别担心,这会让事情变得容易些。
下面是搭建一个项目时你要做的一些事情。
定义角色
我提到过有多位领导者时存在冲突的风险,那就从这里开始吧。在资深级别上,工程角色之间的界限开始变得模糊:比如说,一位非常资深的工程师、一位工程经理和一位技术项目集经理之间的区别,可能并不是一眼就能看清的。从基本面上看,他们都有一定的责任去当“屋里的那个成年人”,识别风险、扫除障碍、解决问题。按照定义,经理有直接下属,但工程师也可能有。项目集经理会发现缺口、沟通项目状态、扫除障碍,但如果这些事情没人做,其他所有人都应该站出来去做。我们或许会说工程师应该具备更深的技术能力,但有些项目集经理担任的是技术性很强的角色,而且很多人拥有丰富的软件工程经验。如果一位经理或 TPM 出身工程背景并且仍然参与重大技术决策,或者有不止一位 Staff 工程师,情况就更复杂了。谁做什么?
项目开始时是划定每位领导者职责的最佳时机。与其等到两个人发现自己在做同一份工作,或者工作因为没人认为是自己的而从缝隙中漏掉,不如提前描述需要做哪些事情、由谁来做。最简单的方法是创建一张领导职责表,列出每项职责应该由谁承担。表 5-1 给出了一个示例。
表 5-1. 领导职责表示例
| 产品经理 | Olayemi |
|---|---|
| 技术负责人 | Jaya |
| 工程经理 | Kai |
| 技术项目集经理 | Nana |
| 工程团队 | Adel、Sam、Kravann |
| 理解客户需求并提供 初始需求 | 产品经理 |
| 提供衡量产品成功的 KPI | 产品经理 |
| 制定时间表 | 技术项目集经理 |
| 设定范围和里程碑 | 产品经理、工程 经理 |
| 招募新团队成员 | 工程经理 |
| 监控并保障团队健康 | 工程经理 |
| 管理团队成员的绩效和 成长 | 工程经理 |
| 技术方面的指导和辅导 | 技术负责人 |
| 设计高层架构 | 技术负责人(工程团队 提供支持) |
| 设计各个组件 | 技术负责人、工程团队 |
| 编码 | 工程团队(技术负责人 提供支持) |
| 测试 | 工程团队(技术负责人 提供支持) |
| 运维、部署和监控系统 | 工程团队、技术负责人 |
| 向利益相关者通报状态 | 技术项目集经理 |
| 设计 A/B 实验 | 产品经理 |
| 对技术方案做最终决定 | 技术负责人 |
| 对用户可见的行为做最终决定 | 产品经理 |
我真的想强调,这只是一个示例!有些项目的领导者会比这多得多,有些则更少。面向内部的项目,比如基础设施团队的项目,通常不会有产品经理。6 如果你会在这些任务上填上不同的名字,那也没关系。如果你想把这一切做得更精细,一个流行的工具是 RACI,也叫责任分配矩阵。它的名字来自最常用的四种关键职责:
负责(Responsible)
实际做这项工作的人。
问责(Accountable)
最终交付这项工作并负责签字确认其完成的人。每项任务应该只有一个问责人,而且通常与“负责”的人是同一个人。
咨询(Consulted)
会被征求意见的人。
知会(Informed)
会被持续告知进展的人。
如果你真的很热衷于项目管理,你可能会喜欢阅读 RACI 的许许多多变体,但我在这里就不展开了。我只想指出,RACI 把前面的列表变成了一个矩阵,这样你就能更清晰地设定每个人的预期。在某些情况下它可能有些小题大做,但当你需要它时,你是真的需要它。我在 Google 的一位 Staff 工程师朋友跟我讲过,他们在一个混乱的项目中使用 RACI 的经历:
我们需要某种正式的框架来明确规定由谁做决策。这帮助我们摆脱了两种糟糕的模式:一种是因为不知道谁是决策者,所以永远做不出决定,只能无休止地讨论;另一种是因为没有做决策的流程,所以每个决定都被一遍又一遍地重新争论。RACI 并没有彻底解决这两个问题,但它至少为大家提供了一些(相当没有争议的)结构。
这种没有争议的结构才是真正的超能力。它让你可以提起这个话题,而不会显得奇怪。
Lara Hogan 为产品工程项目提供了另一种工具:团队领导者维恩图。图中有几个相互重叠的圆,分别代表“做什么”“怎么做”和“为什么”这几个方面的故事,然后分别分配给工程经理、工程负责人和产品经理。我还听到有人建议,如果能把第四个圆——“什么时候”的故事——塞进这张图里,它可以分配给项目经理或项目集经理。
无论你采用哪种方法,都要努力让每位领导者对各自的角色、谁做什么达成一致。如果你不确定是否每个人都知道你是负责人(甚至不确定你自己是不是),新项目带来的压力会变得更大。十多年前,我在一个项目中担任子负责人,每周和总负责人开会时,我都有点紧张,不知道自己是否完成了对我的期望。如果我直接就此谈一谈,本可以省去一大堆焦虑:“我认为我负责的是这些。你同意吗?我承担的主人翁责任合适吗?”
关于角色的最后一点想法:如果你是项目负责人,你最终要对项目负责。这意味着你会隐性地填补任何还没有人担任的角色,或者至少确保这些工作有人完成。如果你的队友没有经理,你就要帮助他们成长。如果没有人跟踪用户需求,那就是你的事。如果没有人做项目管理,那也是你的事。加起来可能会非常多。在本节余下的部分,我会谈谈在这些角色中可能会分配给你的一些任务。
招募人员
如果有一些空缺的角色你不想做或者没时间做,你可能需要找别人来做。这可能意味着从内部或外部招募人员,或者挑选子负责人来负责项目的某些部分。有时,这意味着你要寻找团队里不够多的特定技术技能,或者你自己没有的经验。7 也要寻找那些能与你的技能互补、填补你技能空白的人。如果你是关注全局的人,就去找一个热衷于钻研细节的人,反之亦然。如果想锦上添花,看看能不能找到那些特别喜欢做你讨厌做的那类工作的人。那是最好的合作关系!
2020 年 10 月,我有机会为 LeadDev 主持了一场主题为“Sustaining and Growing Motivation Across Projects”的小组讨论。在讨论中,Google 首席工程师 Mohit Cheppudira 谈到了他在招募人员时看重什么:
当你负责一个非常大的项目时,你在某种程度上是在构建一个组织,并且在引领一个组织。把组织的需求弄对很重要。我花了很多时间,努力确保在那个具体项目涉及的所有不同领域里都有最好的负责人。而当你寻找负责人时,你要找的不仅是技术判断力好的人,还要有合适的心态:他们乐观、善于解决冲突、善于沟通。你需要的是那些你可以依靠、能够真正推动项目向前的人。
招募决策是你要做的最重要的决策之一。你带进项目的人,对于你能否赶上截止日期、完成可见的任务、实现目标,会产生巨大的影响。他们的成功就是你的成功,他们的失败在很大程度上也是你的失败。招募那些能够协作、能够克服摩擦、能把事情做成的人——那些你可以依靠的人。
就范围达成一致
项目经理有时会使用一种叫作项目管理三角的模型,它在项目的时间、预算和范围之间进行权衡。你有时也会听到另一种说法:“快、便宜、好:三者只能选其二。”这话说出来显得理所当然,但不知为何很容易被遗忘:如果人手更少,你能做的事情就更少。就你们要尝试做什么达成一致。
你可能不会一次性交付整个项目。如果你有多个用例或功能,你会希望在过程中逐步交付价值。所以,先决定要做什么,设定一个里程碑,并在旁边写上日期。描述这个里程碑是什么样子:包括哪些功能?用户能做什么?
Jackie Benowitz 是一位领导过好几个大型跨组织项目的工程经理,她告诉我,她把里程碑看作 beta 测试:每个里程碑都以某种方式可用或可演示,并给用户或利益相关者多一次提供反馈的机会。这意味着你必须做好准备:每次增量变更都可能改变下一次变更的用户需求,因为改变用户能做的事,会帮助他们意识到自己还想做什么。他们也可能告诉你,你走错了方向,这让你有机会及早调整方向。
为了保持这种灵活性,有些项目不会规划到下一个里程碑之后太远的地方,而是把每个里程碑本身都视为一个目的地。另一些项目则会粗略地规划出整个项目,在需要改变方向时再更新这张地图。无论你偏好哪种方式,都要让每个增量足够小,让人总能看到下一个里程碑:一个感觉触手可及的目标是很有激励作用的。我也一次又一次地发现,人们只有在面对一个无法不去想的截止日期时,才会带着紧迫感行动。定期的交付物会让人们不再把所有事情都拖到最后。对什么时候应该发生什么,要设定清晰的预期。
如果项目足够大,你可以把工作拆分成若干工作流(workstream),即可以并行开发的功能块(也许由不同的子团队负责),每个工作流都有自己的一组里程碑。它们可能在某些关键节点上相互依赖,有些工作流可能要等其他工作流完全结束才能开始,但通常你可以独立地讨论其中任何一个。你也可以划分不同的阶段:完成一大块工作,重新调整方向,然后启动项目的下一阶段。像这样拆分工作,会让思考起来更容易把控一些。它让你增加了一层抽象,可以从更高的高度思考。比如,如果你能说某个工作流进展顺利,你就不需要深入该工作流中每项任务的细枝末节。
如果你的公司在使用产品管理或路线图软件,它很可能有把项目组织成阶段、工作流或里程碑的功能。如果所有需要参与的人都在同一个物理地点,你可以用白板上的便利贴做同样的事。重要的是,对于你们决定做什么、什么时候做,大家都能得到同样清晰的图景。
估算时间
我几乎没见过谁擅长估算时间。这也许是软件工程的本质:每个项目都不一样,而我们能判断一个项目要花多长时间的唯一方法,就是之前做过一模一样的事情。我读到过的最常见的建议是,把工作拆分成尽可能小的任务,因为小任务最容易估算。第二常见的建议是假设自己估错了,把所有数字都乘以三。这两种方法都不太令人满意!
我更喜欢 Andy Hunt 和 Dave Thomas 在 The Pragmatic Programmer(O’Reilly)中给出的建议:“我们发现,确定项目时间表的唯一方法,往往是在同一个项目上积累经验。”他们解释说,当你交付一小片一小片的功能时,你就会积累关于团队做某件事需要多长时间的经验——所以你每次都要更新进度表。他们还建议你练习估算,并记录估算的情况。和其他所有技能一样,你做得越多就会越擅长,所以即使在估算无关紧要的时候也要练习,看看随着时间推移,你估对的次数是否越来越多。
估算时间时需要考虑你所依赖的团队。其中有些团队可能全身心投入这个项目,也许把它视为本季度或本年度的头等大事。另一些团队可能只把它看作众多争夺他们注意力的请求之一。尽早与你需要的团队交谈,了解他们的可用时间。
尤其是平台团队的工程师,他们跟我说过那种沮丧:在最后一刻收到请求,要求添加上线马上就要用的功能,而如果他们在几个月前就知道,本来可以轻松提供这个功能,把它纳入本季度的规划中。你越晚告诉其他团队你需要他们提供什么,就越不可能得到你需要的东西。如果他们真的同意手忙脚乱地满足你,要记住你打断了他们之前的工作:你打乱了他们其他项目的时间估算。
就后勤安排达成一致
有很多小决定可以帮助项目顺利运转,你可能需要以团队的形式讨论它们。下面是一些例子:
何时、何地以及如何开会
你们多久开一次会?如果你们是一个团队,要每天开站会吗?如果你们跨多个团队工作,负责人们多久碰一次头?你们会定期做演示、举行敏捷仪式、开回顾会,或者用其他方式进行反思吗?
如何鼓励非正式沟通
会议是一种相当正式的信息交流方式,而且可能并不是每天都开。在会议之外,怎样才能让大家方便地互相聊天、提问?如果你们都坐在一起,这会很容易,但随着远程办公越来越普遍,坐在一起正在变得不寻常。8 在一个大家彼此还不认识的新项目里,人们可能不太愿意发私信,所以你可以通过一个社交频道、非正式的破冰会议,或者(如果大家的生活条件允许)把所有人聚到同一个地方待几天来鼓励交流。即使是表情包讨论串这样的傻事,也能在人们之间建立联系,让他们更快地互相提问、提供帮助。
如何共享状态
你的发起人希望通过什么方式了解项目的进展?公司的其他人呢:你希望他们去哪里了解更多信息?如果你计划定期发送进展更新邮件,由谁来发,多久发一次?
文档的大本营设在哪里
项目在公司的 wiki 或文档平台上有官方的大本营吗?如果没有,就建一个,并通过一个好记的 URL 或醒目的链接让人容易找到。这个文档空间将是项目宇宙的中心,应该链接到其他所有内容。当你要找会议纪要、下一个里程碑的描述或者相关 OKR 的措辞时,它让你有一个统一的起点。你希望每个人看到的都是同样的最新信息。它是混乱宇宙中的唯一不动点!
你们的开发实践是什么
你们要用哪些语言工作?你们要如何部署所创建的东西?代码评审的标准是什么?所有东西需要测试到什么程度?你们是否用功能开关(feature flag)来发布?如果你是在一家已经存在了一段时间的公司里新增项目,也许所有这些问题都有标准答案。其他问题可能取决于你在推进项目过程中要做的技术决策。不过,还是要开启这些讨论,让每个人达成一致。
召开启动会
作为搭建项目的一部分,你可能要做的最后一件事是召开启动会。如果所有重要信息都已经写下来了,这也许显得没有必要,但彼此见面这件事本身就有某种力量,能让项目带着势头启动。它让每个人都有机会同步信息,感觉自己是团队的一员。
下面是启动会上可以讨论的一些话题:
-
每个人都是谁
-
项目的目标是什么
-
到目前为止发生了什么
-
你们已经建立了哪些结构
-
接下来会发生什么
-
你希望大家做什么9
-
大家可以如何提问、了解更多信息
推动项目
我最喜欢的关于项目管理的演讲是 Google Cloud Platform 的 VP Kripa Krishnan 的“Avoid the Lake!”。我以前经常听到“推动项目”(driving a project,字面意思是“驾驶项目”)这个说法,却从没真正想过它意味着什么,但 Krishnan 把这个类比讲得很清楚,她说:“驾驶并不意味着你一脚踩下油门,然后一路直行。”换句话说,驾驶不能是被动的:它是一个主动的、深思熟虑的、专注的角色。它意味着选择路线、做出决策,并对前方道路上的危险做出反应。如果你是项目负责人,你就坐在驾驶座上。你要负责把每个人安全地送到目的地。
在第 3 章,我们看了项目负责人的一项职责:确保决策得以做出。现在,我们来看看在你驾驶项目驶向目的地的路上,可能会遇到的其他一些挑战。
探索
当一个全新的项目已经有了设计文档或计划时,我总是心存怀疑;如果其中还包括实现细节,比如“用 Node.js 构建一个 GraphQL 服务器来……”之类的,我就更怀疑了。除非问题真的非常直接(如果是这样,你确定它需要一位 Staff 工程师吗?),否则在第一天你不会掌握足够的信息来做出这类细粒度的决策。你需要做一些研究和探索,才能理解项目的真正需求,并评估可以采取哪些方法来满足这些需求。如果你在做一个设计时很难清楚地阐述目标(或者目标只是对你的实现的描述!),那就说明你在这个探索阶段花的时间还不够。
项目的重要方面是什么?
你们要着手实现的是什么?项目越大,不同团队对你们要实现什么、实现之后会有什么不同、大家采取什么方法,就越可能持有不同的心智模型。有些团队可能有你不知道的约束,或者对项目方向有未明说的假设:他们之所以同意帮你,可能只是因为他们认为你的项目也能实现他们关心的另一个目标——而他们可能想错了!团队成员可能会执着于项目中较小、不太重要的方面或小众用例,或者期望的范围与你不同。他们可能用不同的词汇描述同一件事,或者用相同的词却指不同的东西。要做到能够简明地解释项目中不同团队想要什么,而且他们也认同你的解释是准确的。
对齐问题、为问题确立框架,可能需要花费时间和精力。这需要和你的用户及利益相关者交谈——并且真正倾听他们说了什么、用了哪些确切的词。这可能还需要研究其他团队的工作,看看他们是否在做和你一样的事,只是描述方式不同。如果你带着成型的心智模型进入这个项目,要把这些先入之见放到一边、探索其他人如何看待这项工作,可能会很难。但如果每个人都在用不同的词、奔向不同的目的地,要推动这样一个项目会更加痛苦。
随着你不断探索、发掘各方的预期,你会开始为你们要做的事情建立一个清晰的定义。探索帮助你形成关于项目的电梯演讲,一种对项目的概括,把它浓缩成最重要的几个方面。你也会开始清楚地描述你们不做什么。对于与你的项目相关的其他项目,你会开始说明一个项目如何是另一个的子集,或者它们如何重叠。描述那些看起来相似、实际上并不相关的工作,或者那些看起来毫不相干、却有着意想不到的联系的工作,会让事情变得清晰。本章稍后我会谈谈如何建立心智模型,帮助你和其他人用同样的方式思考问题。你对问题理解得越透彻,就越容易为其他人确立问题的框架。
你可以采取哪些方法?
只有当你对你们要做什么有了一个清晰的说法之后,才去弄清楚怎么做。如果你带着某种架构或解决方案进入项目,那么当你意识到它可能实际上无法解决你在探索中界定的真正问题时,会感到很不适。这种思维上的调整非常困难,以至于我见过一些项目负责人死死抓住他们最初关于要解决什么问题的想法,抗拒所有与这种世界观相矛盾的信息。这样是得不到好的解决方案的。所以,在你们就需要解决什么问题达成一致之前,真的要努力对如何解决问题保持开放的心态。
也要对现有的解决方案持开放态度,即使它们不如从头打造新东西那么有趣或方便。在第 2 章,我谈到了在动手创造新东西之前,通过研究和学习其他团队(公司内外的都有)来建立视角。现有的工作可能并不完全是你设想的形状,但要乐于接受这样一种想法:它可能是一种更好的形状,或者至少是可行的。从历史中学习:了解类似的项目是成功还是失败了,以及它们在哪里遇到了困难。记住,写代码只是软件工程的阶段之一:运行中的代码需要维护、运维、部署、监控,有朝一日还要删除。如果有一种解决方案意味着在你的项目之后,组织需要维护的东西更少,那么在选择方法时要把这一点考虑进去。
澄清
启动项目的很大一部分工作,是为每个人提供关于你们在做什么的心智模型。当有很多变动的部分、各种意见和半相关的项目时,要把它们全都记住是很吃力的。作为项目负责人,如果花时间理解那些棘手的概念有助于你完成项目,你就有动力去这么做。但你请求帮助的那些人有不同的关注点,可能不会那么努力。除非你花时间为他们降低复杂性,否则他们最终可能会以某种方式思考项目,导致他们为错误的结果做优化,或者搅浑你试图向组织讲述的清晰故事。
在 The Art of Travel(Vintage)中,Alain de Botton 谈到了学习那些与你已知的任何东西都联系不上的新信息时的挫败感——比如你在异国参观一座历史建筑时可能会获得的那类事实。他写到参观马德里的圣弗朗西斯科大教堂(Iglesia de San Francisco el Grande)时得知,“圣器室和教士会堂里那些十六世纪的座席来自 Cartuja de El Paular,即塞哥维亚附近的那座加尔都西会修道院”。由于和他已经熟悉的东西没有任何联系,这段描述无法激起他的兴奋或好奇。他写道,这些新的事实“就像没有串链的项链珠子一样,毫无用处、转瞬即逝”。
我很喜欢这句话,在努力帮助别人理解某件事时,我经常想起它。我怎样才能把这个概念挂到他们已有的知识上?我怎样才能让它与他们相关,激发他们对它的好奇心?也许我可以通过一些相互关联的概念搭起一条项链的串链,或者用一个类比给他们一个足够接近、足以派上用场的概念,即使它并不完全准确。
我们来看几种通过建立共同理解,来降低大型混乱项目复杂性的方法。
心智模型
当你开始学习 Kubernetes 时,你会被大量新术语淹没:Pod、Service、Namespace、Deployment、Kubelet、ReplicaSet、Controller、Job 等等。大多数文档都是通过这些概念与其他新术语的关系来解释它们,或者用抽象的方式来描述它们——如果你已经理解了整个领域,这些描述就完全说得通。如果你是零基础进来的,可能会感到不知所措——直到某个朋友把它和你熟悉的东西联系起来。他们可能会用一个类比,让你通过想象某个已知事物的行为来理解:“把这部分想象成一个 UNIX 进程。”他们也可能举个例子,让你对所描述概念的形状有个大致的印象:“这很可能是一个 Docker 容器。”这些模型都不完美,但它们也不必完美:它们只要足够接近,能搭起一条通往你已经理解的其他事物的链,让你有个挂载知识的地方就行了。
我在本书中通篇都用了这类修辞手法,用电子游戏的类比和地理方面的隐喻来描述概念。把一个抽象的想法与我非常熟悉的事物联系起来,可以降低记住和描述这个想法的一部分认知成本。这就像把这个想法放进一个命名良好的函数里,以后我可以再次调用它,而不必去想它的内部实现。(看,我刚才又这么做了。)
正如我们构建 API 和接口,让我们无需处理组件的繁杂细节就能使用它们一样,我们也可以构建抽象,让我们能够处理各种想法。“领导者选举”比“分布式共识算法”更容易理解和解释。当你描述想要完成的项目时,你可能会有一堆抽象概念,如果对你所在的领域没有大量了解,这些概念并不容易理解。为概念提供一个方便、好记的名字,使用类比,或者把它和人们已经理解的东西联系起来,让大家有个好的开端。这样他们就能很快地为你所讲的内容建立起自己的心智模型。
命名
两个人可能用相同的词,却指的是完全不同的东西。我开玩笑说,我和一位最喜欢的同事的谈话总是会演变成争论词语的含义。但一旦我们理解了彼此,我们就能以一种非常细致入微、高带宽的方式交谈,在我们实际上在哪些地方意见一致、哪些地方意见不同的问题上,进行更有力的对话。
2003 年,Eric Evans 写了 Domain-Driven Design(Addison Wesley),提出了有意识地构建他所谓的“通用语言”(ubiquitous language)这一概念:这是一种由系统开发者和作为其利益相关者的现实领域专家共享的语言。在一家公司内部,即使是用户、客户和账户这样非常常见的词,也可能有特定的含义,而且这些含义甚至会因为你是在和财务、市场还是工程部门的人交谈而有所不同。花时间去理解哪些词对你要沟通的对象有意义,并尽可能使用他们的词。如果你要同时和多个群体沟通,就提供一份术语表,或者至少要有意识地说明你使用的术语指的是什么。
图片和图表
如果你真的想降低复杂性,就用图片。没有比这更容易帮助人们把你所讲的内容视觉化的方法了。如果某样东西在变化,一组“之前”和“之后”的图片可能比一整篇文章还清楚。如果一个想法包含在另一个想法之中,你可以把它们画成嵌套的;如果它们是并列的概念,就可以画成并列的形状。如果存在层级关系,你可以把它画成梯子、树或金字塔。如果你要表示一个人,用火柴人或笑脸表情比画一个方框更清楚。
要注意已有的联想:除非你不介意很多读者把它当成数据存储,否则不要在图上画圆柱体。如果你使用颜色,有些读者会试图解读颜色的含义,比如假设绿色的组件是要鼓励的,红色的则应该停止。
图片也可以是图表的形式。如果你能展示一个目标,以及一条朝着这个目标发展的趋势线(如图 5-2),那么成功是什么样子就一目了然了。同样,如果你的趋势线正朝着你所强调的某个灾难点发展,项目的必要性就会变得非常直观。
图 5-2. 图表可以展示朝目标推进的进展。
设计
完成探索、澄清了工作之后,你可能会对接下来要做的事情有很多想法:你要构建或改变什么,你要采取什么方法。不要假设和你共事的每个人都理解或认同这些想法。即使你在谈论这些想法时没有听到反对意见,你的同事们可能也还没有真正消化这个计划,他们的默许可能并不说明什么。你需要努力确保每个人都达成一致。最有效的方法就是把事情写下来。
为什么要分享设计?
在第 2 章,我讨论了口头文化和书面文化的公司。公司越大,其文化就越可能转向后者,大家也会期望你撰写和评审设计文档。这是因为,如果没有共同的理解,让很多人一起完成某件事是非常困难的;而如果没有书面的计划,你很难确定大家已经有了共同的理解。无论你是在创建功能、产品计划、API、架构、流程、配置,还是其他任何需要多人达成相同理解的东西,在你把它写下来之前,你都无法真正知道大家是否理解和认同。
写下来并不意味着每个微小的改动都需要一份 20 页的技术深度剖析。一份简短、明快、易读的文档,可能就足以让一群人(字面意义上)看到同一页。但你至少应该包含计划的重要方面,并让其他人在看到你的路上有危险时可以联系你。请别人评审设计,不仅仅是询问某个架构或一系列步骤是否可行;还包括就你们是否在解决正确的问题、你对其他团队和现有系统的假设是否正确达成一致。一个对某个团队来说显而易见的方案,可能会给另一个组织带来工作量,或者破坏他们的工作流程。正如我的朋友 Cian Synnott 所说,书面设计是一种非常廉价的迭代。
RFC 模板
以这种方式分享信息的一种常见做法是设计文档,通常称为征求意见文档(request for comment,RFC)。虽然你会发现很多公司都在使用 RFC,但对于 RFC 应该是什么样子、如何使用,并没有真正一致的标准。不同公司对细节和正式程度的要求不同,分享的范围有大有小,评论可能受鼓励也可能不受鼓励,还可能有一个正式的审批环节或讨论设计的会议。
我不打算评判哪种流程最好——这真的取决于你们的文化——但我非常推崇为这类文档准备模板。10 无论我们作为架构师有多出色,在设计一个复杂的系统、流程或变更时,都有很多东西要记住。而人类并不擅长关注所有的事情。正如 The Checklist Manifesto: How to Get Things Right(Picador)的作者 Atul Gawande 所说:
我们天生就不是为纪律而生的。我们天生追求新奇和刺激,而不是仔细关注细节。纪律是我们必须努力才能做到的事情。使用清单不知怎么的让人觉得有失身份,是一种难堪。它与我们内心深处的信念背道而驰:我们相信,我们之中真正伟大的人——那些
我们渴望成为的人——在应对高风险和高复杂度的局面时,是这样的:真正伟大的人敢于冒险。他们即兴发挥。他们没有规程和清单。也许我们对英雄主义的看法需要更新了。11
Gawande 认为,使用清单可以帮助我们彼此沟通、避免常见错误,并有意识地而不是隐性地做出正确的决定。一个好的 RFC 模板能帮助你把各项决策想清楚,并提醒你那些否则可能会忘记的话题。经历一番撰写这类文档、回答一些关于你的计划的(也许令人不太舒服的)问题的过程,有助于确保你没有遗漏某个关键类别的问题。
RFC 里应该写什么?
你的公司可能已经有自己的 RFC 模板,如果有的话,你应该遵循它。不过,下面是我在每份 RFC 中都会放的标题,我认为这些是最起码应该包含的。
上下文:我希望文档能锚定在特定的时间和空间里。当两年后有人偶然发现这份文档时,文档开头应该给他们足够的上下文,让他们能判断它是否与自己要找的东西相关。它应该有标题、作者姓名,以及至少一个日期;我喜欢“创建于”和“最后更新于”,但两者有其一也比都没有强。写明文档的状态:是早期的想法、开放详细评审、已被另一份文档取代、正在实施、已完成,还是暂缓。我喜欢给文档开头使用标准格式,这样快速浏览一份 RFC 会非常容易,但信息是否齐全比格式是否标准更重要。
目标:目标部分应该解释你们究竟为什么要做这件事:它应该说明你们要解决什么问题,或者要抓住什么机会。如果有产品简报或产品需求文档,这一部分可以是它的摘要,并附上链接。如果你写的目标只会引出这样的问题——“好吧,可你们为什么要做那个?”——那你就应该更进一步,把这个问题也回答了。提供足够的信息,让读者能判断他们是否认为你在解决正确的问题。如果他们不同意,那太好了——你是现在就发现了,而不是在造出错误的东西之后才发现。
目标中不应该包含实现细节。如果你给我发来一份 RFC,目标是“创建一个无服务器 API 来翻译鸡的叫声”,我完全相信这就是你们要做的事,我也可以评审这份 RFC,试着评估你的设计。但如果不知道你们要为谁解决什么实际问题,我就无法评估这是否真的是正确的方法。你在目标中规定了要做成无服务器的,所以你已经在没有给出理由的情况下做出了一个重大的设计决策。具体的实现应该服务于目标,而不应该成为目标。把设计决策留给设计部分。
设计:设计部分阐述你打算如何实现目标。确保你包含了足够的信息,让读者能评估你的解决方案是否可行。给读者提供他们需要知道的东西。如果你是写给潜在用户或产品经理看的,要确保把你打算提供给他们的功能和接口讲清楚。如果你要依赖某些系统或组件,要写明你打算如何使用它们,这样读者就能指出你对它们能力的误解。
你的设计部分可以是几段话,也可以是 10 页密密麻麻的内容。它可以是叙述性的文字、一组要点、一堆带标题的小节,或者任何能清楚传达信息的其他格式。
视你要做的事情而定,设计部分可以包括:
-
API
-
伪代码或代码片段
-
架构图
-
数据模型
-
线框图或截图
-
流程中的各个步骤
-
关于组件如何组合在一起的心智模型
-
组织结构图
-
供应商成本
-
对其他系统的依赖
重要的是,读完之后,读者应该理解你打算做什么,并且能够告诉你他们是否认为这行得通。
错了也比含糊好
我经常看到有人在写设计部分时有点含糊其辞——或者干脆避免对任何计划做出承诺——因为他们不想让别人和他们争论细节。但相比含糊其辞,犯错或者引起争议才是对时间更好的利用。如果你错了,别人会告诉你,你会学到东西,如果需要的话还可以改变方向。如果你在尝试一个有争议的想法,你可以及早发现同事们是否会反对你的方法。对你的设计有分歧并不意味着你需要改变方向,但它会给你提供一些否则你得不到的信息。下面是让你的设计更精确的两个技巧:
-
对于每一个动词,都要清楚是谁或什么在执行这个动作。如果你发现自己在用被动语态写作,比如“数据将在传输中被加密”或“JSON 载荷将被解包”,那你就是在隐藏信息、让读者去猜。相反,要用主动动词,并带上执行动作的主语:“客户端将在传输数据之前对其加密”或“Parse 组件将解包 JSON 载荷”。11
-
下面这个技巧对我来说改变了游戏规则:如果能避免歧义,多用几个词甚至重复自己的话都没关系。正如软件工程师兼作家 Eva Parish 在她的文章“What I Think About When I Edit”中建议的:
不要只说“这”或“那”,而应该加上一个名词,准确地说明你指的是什么,即使你刚刚才提到过它。
示例:我们只剩两个箱子了。为了解决这个,我们应该再订一些。
修改后:我们只剩两个箱子了。为了解决这个短缺问题,我们应该再订一些。
读了 Eva 的文章之后,我注意到设计文档中有太多例子,因为一个光秃秃的“这”或“那”而隐藏了信息。例如:“有一个提案,要用 NewSolution 取代为提供 OriginalFunctionality 而构建的 OldSolution。TeamB 需要这个,所以我们应该讨论一下需求。”TeamB 需要的是什么:是这个提案、原有的功能,还是新的解决方案?
如果你觉得写作很吃力,请记住这是一项可以学会的技能。你们公司能使用的任何学习平台上,可能都提供技术写作课程。或者也可以考虑 Google 的课程 Technical Writing One 和 Technical Writing Two。Write the Docs 网站也提供了大量关于如何写好文档的资源。
安全/隐私/合规:你有什么值得保护的东西,你要防范的是谁?你的计划是否以任何方式涉及或收集用户数据?它是否对外部世界开放了新的访问入口?你要如何存储将要用到的密钥或密码?你防范的是内部威胁、外部威胁,还是两者都有?即使你认为不存在安全问题、这一部分无关紧要,也要写下你为什么这样认为。
备选方案/已有方案:如果你能用电子表格解决同样的问题,你还会想这么做吗?12“备选方案”部分是你向自己(和其他人!)证明,你是来解决问题的,而不只是对解决方案本身感到兴奋。如果你发现自己因为没有考虑过任何备选方案而略去这一部分,那就说明你可能还没有把问题想透。为什么更简单的方案或现成的产品不行?公司里有没有其他人尝试过类似的事情,为什么他们的方案不合适?我有一条原则:如果公司内部已经存在一个看起来可行的选项,而我们不打算用它,那么 RFC 的作者必须把新设计发给那个系统的负责人,给他们一个回应的机会。
以上是我认为绝对必须包含的标题,即使是很小的 RFC 也一样。它们是“让你保持诚实”的部分!但如果你想让所写的文档发挥最大价值,还有其他一些部分通常也很有帮助。
背景:这里是什么情况?读者需要哪些信息来评估这个设计?如果你使用了评审者可能不知道的内部项目名称、缩写或小众技术术语,可以附上一份术语表。
权衡:你的设计有哪些缺点?你有意做出了哪些权衡,因为你认为收益值得付出这些代价?
风险:可能会出什么问题?最坏的情况是什么?如果你对系统复杂性、增加的延迟,或者团队对某项技术缺乏经验有些担心,不要隐瞒:提醒评审者,并给他们足够的信息,让他们自己得出结论。
依赖:你对其他团队有什么期望?如果你需要另一个团队来配置基础设施或编写代码,或者需要安全、法务或公关部门批准你的项目,你需要给他们留出多少时间?他们知道你要来找他们吗?
运维:如果你在编写一个新系统,谁来运行它?你要如何监控它?如果它需要备份或灾难恢复测试,谁来负责?
技术陷阱
虽然本书并不打算成为一本技术或架构方面的书,但我确实想指出一些我在设计文档中经常看到的陷阱。自己先把它们找出来,别让其他人替你找。
这是一个全新的问题(但其实不是):偶尔会有例外,但你的问题几乎肯定不是全新的。我已经谈过要寻找之前做过的和相关的项目,但这一点足够重要,值得在这里再提一次。不要错过向别人学习的机会,并考虑复用现有的解决方案。
这看起来很简单!:有些项目比看上去要难得多,具有很强的迷惑性,你可能要等到深陷实现的细节之中才会意识到这一点。软件工程师并不总是真正认识到,其他领域和他们自己的领域一样丰富、微妙和复杂。比如,他们可能看到一个会计系统,就以为自己能造出一个更好、更干净、更简单的。之前的团队怎么会在这种东西上投入数千个工程师工时!?但构建一个会计系统(或者薪资系统、招聘系统,甚至是一个能正确共享值班表的东西)实际上是一个难题。如果它看起来微不足道,那是因为你还不理解它。
只为当下构建:如果你是按照世界当前的状态来构建,你的解决方案三年后还能用吗?即使你按照当前用户数和请求量的五倍来设计,也还有其他维度需要考虑。如果系统需要了解公司的所有团队或所有产品,那么当公司发展壮大、进行收购或者被收购时,会发生什么?如果产品数量变成现在的五倍,这个组件会不会成为瓶颈,所有人都在等一个团队为他们添加定制逻辑?如果你的团队规模翻倍,团队成员还能在这个代码库里工作吗?
为遥远又遥远的未来构建:如果你在按比当前用量高几个数量级的规模做设计,你有真正的理由需要那么大的规模吗?如果支持更多用户轻而易举,那很好,去做吧;但要警惕过度设计的方案,它们比实际需要的复杂得多。如果你要添加定制的负载均衡、额外的缓存或自动区域故障切换,要解释为什么值得付出额外的时间和精力。“我们以后可能会需要”并不是一个足够好的理由。13
每个用户只需要……:如果你有五个用户,你大概可以一个个地教会他们系统中所有晦涩的规则。如果你有几百个甚至更多用户,他们就会用错;如果你没有为此做好规划,你的设计就行不通。解决方案中任何涉及人们改变工作流程或行为的部分都会很困难,需要作为设计的一部分来考虑。
困难的部分我们以后再想办法:这种情况在迁移中很常见:你花一个季度构建和部署系统,打磨它,让它在几个简单的用例上表现完美——然后你才不得不去想如何让它适用于更困难的场景。如果结果发现这根本做不到,怎么办?忽视项目中困难的部分,也可能意味着把复杂性推给别人,比如要求 API 的每个现有调用方都修改代码而不是保持向后兼容,或者迫使你的客户自己编写逻辑去解读那些晦涩而零散的信息。
为解决小问题而让大问题更难解决:如果你有很多小项目,人手勉强够用,你就会看到人们用取巧的方式绕开困难的问题,而不是直面它们。这些拼凑上去的方案往往隐含地依赖于现有系统的行为,这意味着以后要实施更全面的解决方案会更加困难。如果你的组织拒绝投入资源去解决根本问题,你可能别无选择,但至少要在设计中把它指出来。想一想如何在解决小问题的同时,不让大问题变得更加棘手。
这其实不算重写(但其实就是!):如果你看着一个庞大的软件系统,设想把它变成另一种形态,那么要对自己和他人诚实地说明这需要多少工作量。比如,你可能会设想“只是”把业务逻辑拿出来重构一下,或者为了上云而重新设计架构。但除非你的代码已经非常模块化、组织良好(如果是这样,你确定需要重新设计架构吗?),否则你很可能最终要重写的东西比你预想的多得多。如果你的项目是一次变相的“从头重写”,那就对自己诚实,承认这一点。
但它可运维吗?:如果你在下午 3 点都很难记得某样东西是怎么工作的,那么凌晨 3 点你更不可能理解它。而在你离开之后加入团队的人会觉得更难。确保你做出来的东西是别人能够理解和推理的。力求让系统可观测、可调试。让你的流程尽可能乏味、尽可能自我说明。
说到可运维性,如果它要在生产环境中运行,就要决定由谁来值班,并把这一点写进 RFC。如果是你们自己的团队,要确保团队里不止三个人(最好至少六个人),否则你们就是在为倦怠和漏接告警埋下伏笔。
在最小的决定上讨论得最多:谁不喜欢一场热烈的自行车棚讨论呢!“自行车棚效应”(bikeshedding)这个说法来自 C. Northcote Parkinson 1957 年提出的“琐碎定律”,该定律认为,由于讨论一个琐碎的问题比讨论一个困难的问题容易得多,团队往往会把时间花在琐碎问题上。14 Parkinson 举的例子是一个虚构的委员会,他们在评估一座核电站的方案,却把大部分时间花在最容易理解的话题上,比如员工自行车棚该用什么材料。15 技术人员通常都知道自行车棚效应这个概念,但即使是资深的人也会不知不觉地就最琐碎、最可逆的决定写出长篇大论,而对那些更难理解、更难达成共识的决定却完全不参与讨论。
这些只是一些常见的陷阱。你可能也注意到了其他陷阱。把它们加到你的清单上,并切实确保你自己没有落入这些陷阱。
编码
大多数软件项目都会涉及编写大量新代码或修改现有代码。在本节中,我会谈谈项目负责人可以如何参与这类亲自动手的技术工作。(如果你不是软件工程师,就把这里换成对你来说合适的核心技术工作。)
你应该在项目中写代码吗?
作为项目负责人,你贡献多少代码,取决于项目的规模、团队的规模以及你自己的偏好。如果你在一个很小的团队里,你可能会深度参与每一个改动。在一个有多个团队的项目中,你可能偶尔贡献一些功能,或者只是一些小修复,也可能在更高的层面工作,完全不写代码。很多项目负责人发现,他们评审了大量代码,自己却写得不多。
正如 Joy Ebertz 指出的:“写代码很少是你能投入时间的杠杆率最高的事情。我今天写的大部分代码,资历比我浅得多的人也能写。”不过 Ebertz 也指出,写代码能让你获得一种用其他方式难以获得的深度理解,并帮助你发现问题。更重要的是,“如果每周花一天写代码能让你保持投入、兴致勃勃地来上班,那么你在工作的其他方面可能也会做得更好”。最后,持续参与实现,能确保你和团队一样切身感受到你自己做出的架构决策的代价。
不过,要注意你是否在以牺牲更困难、更重要的事情为代价来贡献代码。这是我在第 4 章提到的吃零食的一种形式:接手你知道怎么做的工作(而且反馈周期更短),而回避那些重大、困难的设计决策或关键的组织运作。
做榜样,但不要成为瓶颈
作为负责推动项目前进的人,你的时间会比其他人更难预测。你的会议可能也比其他所有人都多。所以,如果你接手最大、最重要的问题,你很可能会比别人更晚才开始写代码,这可能会阻塞其他人,让你成为瓶颈。如果你要写代码,尽量挑选那些对时间不敏感、不在关键路径上的工作。
把你的代码看作帮助其他所有人的杠杆。99 Bottles of OOP 的合著者、GitHub 的 Staff 工程师 Katrina Owen 跟我讲过一个项目:她创建了一种为 API 分页编写测试的标准方式,然后用她的方法替换了所有现有的测试。通过修改所有现有的分页测试,她也隐性地改进了未来的测试:任何人新写一个测试时,都会照搬已有的模式。
力求让你的解决方案赋能你的团队,而不是取代他们。Ross Donaldson 是一位从事数据库系统工作的 Staff 工程师,他曾向我描述他的一部分工作是“侦察和测绘”:
我回到团队,说:“我发现了这个问题、那条河、这些资源。”然后我们就可以一起讨论想要如何处理这些新信息。之后也许我会出去在那条新发现的河上搭一座简陋的桥,由团队来拥有和改进它。我会提供一两个意见,提醒大家他们手头有哪些工具可用,但除此之外,我会把他们的主人翁意识放在我自己的代码审美之上。
Squarespace 的高级 Staff 工程师 Polina Giralt 补充道:
如果有什么东西只有我懂,我会去做,但会坚持让某个人和我结对。或者如果是紧急情况,而我知道怎么修,我会自己动手,之后再解释。又或者我会写代码确立一种新模式,然后交给别人继续实现。这样就能强制进行知识共享。
与其自己包揽每一个重要的改动,不如为其他人寻找成长的机会,比如一起讨论细节,或者就这个改动结对编程。结对编程可以共享知识,培养其他人的技能。结对还意味着你可以只参与改动中的关键部分,然后让同事去完成余下的工作。
如果你在评审代码和改动,要注意你的评论会被如何看待。即使你认为自己平易近人、友善友好,当一位 Staff 工程师评论他们的工作时,职业生涯早期的工程师也可能会感到畏惧。你希望团队里的其他人把你看作一个可以学习的资源,而不是一个批评每个决定、让他们觉得自己不够格的人。有时候,最好让别人确立一个足够好的模式,而不去否决他们,即使你本可以做得更好。此外,要小心不要包揽所有的代码评审——否则你会成为单点故障,团队其他人也学不到那么多东西。
Staff+ 工程师还以另一种更隐性的方式充当榜样:你做的任何事都会为团队设定预期。因此,产出高质量的工作很重要。达到或超过你们的测试标准,添加有用的注释和文档,并对走捷径非常谨慎。如果你是团队里最资深的人,而你做事马虎,那你就会带出一个马虎的团队。(我会在第 7 章更多地谈谈如何做榜样。)
沟通
良好的沟通是按时交付项目的关键。如果团队之间不交流,你们就不会取得进展。如果你不和项目以外的人交流,你的利益相关者就不会知道项目进展如何。我们来看看这两种沟通。
彼此交流
为团队成员寻找定期相互交流、建立关系的机会,即使是(或者说尤其是)在完全远程的团队中。主动联系、提出问题应该是一件轻松的事,而且他们彼此之间应该足够熟悉,即使意见不同也不会搞得很紧张。你可以通过共同的会议、友好的 Slack 频道、演示和社交活动,让建立关系变得更容易。如果团队数量不多,相邻团队的关键人物可以参加彼此的会议或站会。目标是达到一种自在的熟悉感。
熟悉感也会让人觉得提出澄清性的问题更安全。互不相识的工程师可能不好意思说“我不知道那个术语是什么意思”或者“你刚才描述的那个问题会有什么影响?”结果就是,一起工作、共享知识和发现误解都会变得更难。努力让团队达到这样一种状态:提问和承认自己不知道是很正常的事。
共享状态
还有其他人关心你的项目:利益相关者、发起人、等着你们完成的客户团队。让他们能够轻松了解进展,并为你们何时达到各个里程碑设定他们的预期。这可能意味着一对一的谈话、定期的群发邮件更新,或者一个带有状态信息的项目仪表盘。
在了解项目进展的过程中,你可能会掌握很多关于谁在做什么、什么事情计划在什么时候发生、项目各部分进展如何的细节和微妙之处。在发布状态更新时,你可能会倾向于把你知道的一切都分享出来:信息越多越好,对吧?不一定!太多的细节会掩盖整体信息,让读者更难得出你想要传达的结论。
相反,要从影响的角度来说明状态,并想一想读者真正想知道什么。他们可能并不关心你们搭起了三个微服务;他们关心的是用户现在能做什么,以及什么时候能做下一件事。如果越来越明显你们赶不上某个关键的里程碑日期,这是你需要传达的事实。但如果某个团队的延误并不会改变你们的交付日期,这个延误可能就与他们无关。把它特别指出来,甚至可能让人觉得你在试图升级一件并不需要升级的事情。
如果你认为读者确实想知道所有细节,至少要先给出要点。不要假设他们会从你的更新中筛选出关键事实,或者从字里行间读出微妙之处。如果某件事不够清楚,就把要点明确写出来。练习在陈述事实之后加上“这意味着……”,或者说明你在做某件事是“为了让我们能够……”。
对你报告的状态要实事求是、诚实。如果你的项目遇到了困难,你可能会忍不住强装镇定,希望自己能把一切都理顺,并报告状态为绿色。但这样做的话,你就要冒着在项目结束时遭遇不愉快的意外的风险,到时你不得不承认情况并不好,而且已经有一段时间不好了。你听说过“西瓜”项目吗?它们外面全是绿的,里面却是红的。如果你的项目卡住了,不要隐瞒:寻求帮助。
导航
总会有事情出错。也许你意识到,一项作为你计划核心的技术终究并不合适:它无法扩展,缺少某个基本必备的功能,或者它的许可条款被你们的法务团队坚决否决了。也许你的组织宣布业务方向发生变化,现在需要你去解决另一个问题。也许项目中某个至关重要的人辞职了。遇到一些障碍、不得不改变方向是不可避免的;如果你在进入项目时就假定某件事一定会出错——只是还不知道会是什么——你会过得更好。这种心态能帮助你更加灵活,这样当障碍出现时,你会觉得它还挺有意思的,而不是感到沮丧。把这些绕行重新看作一次学习的机会,一次否则你不会拥有的经历。
作为握着方向盘的人,当项目遇到障碍时,你要对接下来发生的事情负责。你不能说“好吧,项目被阻塞了,所以我们无能为力”:你有责任改道、升级到能提供帮助的人那里,或者,如果真的不得不这样做,向利益相关者告知目标现在已经无法实现了。避免那些“西瓜项目”:如果项目状态除了一个根本无法解决的关键问题之外都是绿色的,那么项目状态其实并不是绿色的!
无论遇到什么干扰,都要和团队一起想办法绕过它。变化越大,你就越有可能需要回到本章开头,重新应对不知所措的感觉,建立上下文,把项目当作重新启动来对待。无论发生什么,都要确保持续就此进行良好的沟通。不要制造恐慌,也不要让谣言滋生。相反,要给人们提供事实,并说清楚你希望他们如何对待这些信息。
当你遇到困难时,记住你不是唯一一个希望项目成功的人。16 你的经理的工作是让你成功,你的总监的工作是让你的组织成功。如果你不告诉他们你需要帮助,他们就更难做好自己的工作。有些人非常抗拒寻求帮助。也许是因为这感觉像是失败。但如果你卡住了、需要帮助,最大的失败是不去寻求帮助。不要独自挣扎。
在下一章中,我会更多地谈论如何绕过障碍,届时我们会看看项目可能卡住的一些原因,以及如何让它们回到正轨。
回顾
-
Staff 工程师能够接手那些看似棘手的问题,并让它们变得可以解决。
-
面对一个庞大的项目感到不知所措是正常的。项目本身就很困难。这正是它需要像你这样的人的原因。
-
建立能够减少模糊性、让共享上下文变得容易的结构。
-
明确项目的成功是什么样子,以及你将如何衡量它。
-
领导一个项目意味着有意识地推动它,而不仅仅是听任事情发生。
-
通过建立关系、有意识地着手建立信任,为自己铺平道路。
-
把事情写下来。要清晰,要有主见。错误会被纠正,含糊却会一直存在。
-
权衡总是存在的。在做决策时,要清楚你在为什么做优化。
-
心中想着你的读者,频繁地沟通。
-
预料到问题会出现。制定计划时,要假设会有方向变化、有人辞职,以及依赖不可用的情况。
-
本章通篇我都会说“项目经理”,但你合作的也可能是项目集经理(program manager,通常负责多个有共同目标的项目)。有时你还会看到技术项目集经理(TPM)这个头衔。在本章中,你只需假设我说的是担任其中任何一种角色的人,而且他们都超乎寻常地有条理,非常擅长交付产品。 ↩
-
如果你跳过了第 2 章,那就想象一下团队和组织像构造板块一样彼此挤压,在板块相接之处充满摩擦和不稳定。 ↩
-
也检查一下你的生理需求。我不是想多管闲事,但我们这个行业有很多人睡眠不足。你呢?睡眠能增强韧性、意志力和精力!它太神奇了。还有,你上一次喝水是什么时候? ↩
-
虽然几乎可以肯定是杜撰的,但有一句关于 T 型车的亨利·福特名言很好地说明了不同领域的人可能无法相互沟通:“如果我问人们想要什么,他们会说想要更快的马。” ↩
-
这或许不言而喻,但在描述这种现实时要讲究分寸。如果你用最宽厚的措辞来描述那个难相处的人、对立的团队或者优柔寡断的总监,那么当有人不可避免地把邮件或文档转给他们时,你就不会那么尴尬。你也会因此对他们多一些同理心,或许还能对如何与他们合作产生一些洞见。 ↩
-
不过想想看,如果它们有产品经理,我们这个行业的内部解决方案会好多少。 ↩
-
不过要尽量避免依赖某一个人非常特殊的技能组合。人们经常在公司之间流动,你不会希望出现单点故障。 ↩
-
很可能至少有一些人不在办公室,而且团队里每个人都在不同城市或时区的情况也并不少见!如果大家分布在世界各地,最好他们的工作时间有相当多的重叠,尤其是如果他们需要密切合作的话。完全依靠异步沟通,很难建立起信任关系。 ↩
-
在整个项目过程中都要把这一点说清楚!新上任的领导者常常只是隐约暗示,如果大家能做某件你需要他们做的事就好了。可以这样想:如果你含糊其辞,就会给其他所有人带来更多工作,因为他们要设法弄清楚你提的要求到底有多重要。要明确地说出你希望大家做什么。 ↩
-
我为 Squarespace 工程博客写过一篇文章,其中包含一个示例模板:“The Power of ‘Yes, if’: Iterating on Our RFC Process”。 ↩
-
Rebecca Johnson 博士提出了我读到过的最好的检测无意中使用被动语态的方法:“如果你能在动词后面插入‘被僵尸’,那就是被动语态。”这几乎总是管用。“数据将在传输中[被僵尸]加密。”谢谢你们,僵尸! ↩
-
如果你热爱电子表格,这反而让项目对你更有吸引力了,那就换成任何你觉得最平淡无奇的技术。 ↩
-
这是软件工程史上代价最昂贵的说法之一。 ↩
-
Parkinson 还提出了这样一条定律:“工作会不断膨胀,直到填满完成它所能利用的全部时间。”这家伙真有洞见! ↩
-
我曾经准备过一场演讲,有人看了整整 83 页幻灯片,一条意见都没提,唯一的建议是把我放在其中一页上的自行车棚图片换一张。真事!我不认为他们是在讽刺。 ↩
-
除非你真的是在把社会资本花在一个别人都不关心的个人热情项目上,就像第 4 章讨论的那样!如果是那样,抱歉,你可能只能靠自己了。但我希望你积累了足够的好感,让你的同事和经理仍然在某种程度上为你感到热情,愿意帮你让项目重新动起来。 ↩