下面的内容是我在作为一名程序员入职之前阅读的由Gergely Orosz写的The Software Engineer’s Guidebook。我将将阅读时得到的重要的信息总结成中文以供大家分享。
作为一名高级以上工程师(staff+ engineer),你的角色不仅包含了高级工程师的职责,还有更多其他方面。你通常需要负责团队的工程开发进度和质量,以及你自己的工作质量 - 而且经常还要负责其他团队或整个团队群组的输出。
我想说在软件工程中并不存在"银弹"或在任何场景下都普遍适用的方法。但是确实有一些方法往往能在各个领域都表现良好。作为一名高级以上工程师,你能做的最好的事情就是在使用这些方法时积累经验,扩展你的工具集,并学会在合适的时机选择恰当的方法来帮助你的团队。
作为高级及以上工程师应该花多少时间编码?这个问题没有统一的答案,但有一点是确定的:还有很多其他事情需要处理。是的,你应该为编码安排时间,但会比以前少。
要接受你无法像自己希望的那样经常编码。但要预留时间——最好跨越几周——来进行实际编码工作。如果能与项目开始或其他关键阶段保持一致,集中时间段编码会很有效。在不编码的时候,通过参与代码审查和提供反馈来保持对这个优先事项的关注。
这些集中编码时段也能很好地提醒你为什么要推掉一些非编码工作,以便腾出时间来编码。
不要犹豫去做结对编程,特别是当这可能对其他开发者有帮助的时候。在结对编程时,不要主导一切,而是要引导你的搭档朝正确的方向前进。这种方法比直接主导并用"正确"的方式完成要花更长时间,但这种引导能提升你搭档的水平——从而提高团队的整体技能。这就是为什么结对编程是一项高杠杆活动。
要把编码作为指导、辅导和以身作则的机会。当你编写代码并进入"心流状态"时,你可能会享受这种只有你和电脑的时刻。这是一种很棒的感觉,但如果你在这段时间里匆忙写代码,提交一个草率的、半成品的pull request而没有测试,你的队友很可能会注意到。如果你的技能有点生疏,或者你想快速推进时,这种情况可能会发生。
别忘了,你写的代码很可能要经过代码审查,这通常是工程师们的学习经历。因此,要注意创建一个文档完善、清晰的pull request,让它成为优秀PR的典范。
你写的代码质量很重要,因为初级同事们经常会把高级工程师的代码作为质量标准的参考,把它作为学习和借鉴的榜样。如果团队发现你的工作质量标准较低,这就会让其他地方的低标准变得合理化。毕竟,如果高级工程师都可以这样,为什么其他人不行呢?
这就是为什么要正确地编码很重要。这意味着当某件事完成时,它就是真正完成了——就像我们在第三部分"把事情做完"中讨论的那样。以身作则有助于改善团队的工程文化。
如果你在跨团队工作中压力很大,你可以专注于灭火,这意味着要参与到最需要帮助的团队的编码、结对编程和代码审查中。
需要帮助的团队可能是在关键项目中有滑坡风险或落后的团队。这种方法是被动的,但有时是最有影响力的,只要团队不会把你的介入视为"海鸥管理"式的干预——即有人从上面介入团队并"随意干预"他们的工作,比如提交一个权宜之计而不是可持续的修复方案,然后迅速离开。
你很可能会与多个工程团队合作,或被要求加入某个团队来帮助交付重要项目。到这时,你可能已经形成了自己独特的工作方式,包括偏好的编码风格、流程、命名规范和工具使用。当你没有深度融入一个团队时,要努力适应他们,而不是重塑团队来适应你。
如果你能适应并帮助他们改进实践,同事们会更尊重你。理想情况下,这些改进应该来自团队成员自己,他们能感受到你的支持,从而做出明智的改变。
在编写代码时要明智地选择工作。作为最有经验的工程师之一,你了解多种编码策略方法,可以从中选择,例如:
承担具有挑战性的编码任务并以身作则解决它们;在效率、质量和可维护性之间取得平衡。
你可能会遇到需要深入领域专业知识的更复杂工作和问题,比如理解相关系统如何工作,或者团队中缺乏深入技术专长的领域,如底层性能优化。
承担具有更广泛影响的编码工作。有些编码任务比其他任务更具战略性。以下是一些可能产生广泛影响的例子:
参与项目的早期编码阶段,然后为他人腾出空间。 项目的早期阶段是做出正确决策最关键的时候,比如设置架构、建立代码结构、确保按约定编写测试,以及确保监控和日志符合商定的参数。
采用亲自动手的方式通过编写代码、结对编程和代码审查提供反馈通常是明智的做法。
利用时间限制做更有创造性的编码。为不受打扰的编码时间做好安排是明智的,因为你需要这段时间来进入"心流状态"。然而,明智地收集信息以了解如何最有效地利用有限的编码时间也很重要。是否有提高团队效率的方法?是否有需要解决的复杂障碍?结对编程是否更有帮助?
你很少有像希望的那么多编码时间,所以要充分利用你所拥有的时间!
如何提高团队的软件工程质量?一如既往,没有适用于所有团队的"通用"方法论,无论其技能水平、经验或约束如何。然而,有些方法和流程通常会有帮助:我们将介绍这些。
"完成"是什么意思?这听起来是个简单的问题,但是所有团队成员对此都有相同的答案吗?在许多情况下,每个人对"完成"的定义都略有不同。自动化测试是"完成"的一部分还是额外的工作,面向用户的功能的可访问性如何,更新文档是否属于这些标准?
对于一个被认为交付质量低的团队来说,明确"完成"的含义并达成一致,可以带来显著的改进。在这样的团队工作时,或帮助改进其执行时,可以考虑这个练习:
促进讨论,让团队成员分别描述"完成"对他们意味着什么,然后讨论一个共同的定义。你可以使用便利贴、数字白板或让人们做笔记。只需确保在开始时设定会议目标,即就优质工作的最低"完成"标准达成一致。让每个人都有发言权并推动达成一致。一旦定义了"完成",就把它写下来。恭喜,团队成员现在已经设定了自己的标准,并有了一个可以互相负责的目标。
这通常被称为"完成的定义"(DoD)。它随着团队的发展而演变,并可能随项目而改变。当面临外部截止日期压力时,概念验证的DoD会不同,或者为长期可维护性而构建的项目也会不同。
对于经验不足的团队,清晰的编码风格指南可以避免关于如何格式化代码以及遵循哪些约定的争论。需要制定指南,并为成员提供建议修改的方式。
执行编码风格指南最清晰的方式是配置代码检查工具来检查这些规则,然后将这个工具连接到持续集成(CI)系统。当不遵循代码检查规则时,考虑阻止pull request的合并。
几家公司开源了他们的编码风格指南。以下是一些参考示例:
一个良好的代码审查流程,其中反馈及时且审查有帮助,往往能提高质量,并使一个混合资历的团队整体上能更快地前进,因为审查可以在代码进入生产环境之前发现问题。
作为最有经验的工程师之一,你处于很好的位置来感知代码审查的动态。在第三部分:"协作与团队合作"中,我们介绍了好的代码审查的特征。注意这些动态,并找到方法引导工程师做更好的代码审查。一个明显的方式是以身作则。另一种方法是对其他代码审查提供反馈;表扬好的部分并指出工程师如何改进。
对于经验丰富的团队,阻塞式代码审查可能会降低生产力。这些团队的代码审查往往较少关注代码本身,更多地关注分享对所做更改的理解。
显然,提交后代码审查发生在代码提交之后。软件工程师Cindy Sridharan在文章"提交后审查"中详细介绍了她的经验:
从许多方面来说,提交后审查提供了两全其美的方案:开发者的速度不会因等待批准而牺牲,而合理的问题可以由开发者在后续提交中快速解决。
虽然提交后审查有一些注意事项[...],但主要倾向于提交后审查可以让开发者在开发功能时快速迭代,同时保持他们的更改较小。
提交后代码审查并不一定意味着审查发生在部署之后。实际上,提交后代码审查在部署不一定连续,而是作为常规构建切割发生的团队中效果很好。它们在高度信任的环境中效果最好,这通常(但不总是!)是具有较长任期和高级别资历的团队。投资于能捕获明显问题的自动化的团队也往往从这种方法中受益更多。
让我们以Sridharan的更多观点结束这一部分,她是提交后代码审查的长期实践者:
在开发者生产力和高质量代码之间保持平衡永远是一项具有挑战性的任务,需要明智的选择和权衡。任何形式的提交后审查都可以提高开发者的迭代速度。
像所有好事一样,它需要时间和投入才能做好,但对于试图提高开发者生产力的团队或组织来说,这确实是一个值得探索的途径。
自动化测试在大多数科技公司都是基本要求。它的优势几乎总是能证明投入的努力是值得的。查看你所在团队的测试方法,并考虑投入测试是否能帮助他们以更高的质量更快地前进。如果是这样,你就处于适当的位置来推动测试方法的改进。
我们在第三部分:"测试"中更详细地讨论了自动化测试方法,包括生产环境测试。
工程师如何搭建应用程序的新服务或组件?一种方法是使用健壮的脚手架系统,这可以带来重大的生产力提升。缺少这样的系统会降低生产力,因为工程师们不断地重复造轮子,而且每个服务都有略微不同的配置、依赖和编码风格。
大多数开发者门户都具有定义软件模板或骨架的功能是有原因的:因为易于使用的脚手架对开发者生产力的提升如此巨大!
如果你观察到工程师在项目开始时经常从头开始搭建服务或组件,考虑定义一种脚手架方式[...]
你所在的团队如何进行发布,以及如何安全地进行配置更改?对于重大发布是否有回滚计划,其中是否有些回滚是自动化的?
如果发布是错误和中断的来源,那么规划发布、回滚和自动化回滚可以使其更可靠。例如,如果一个团队经常使用功能标志进行实验,过时的功能标志和过时的实验可能会成为技术债务的来源。具有许多功能标志(其中许多是冗余的)的代码库比移除了无用功能标志的代码库更难导航和更危险。
实验卫生意味着移除已经完成其目的的功能标志。但是你所在的团队是如何做到这一点的?有些团队会创建后续任务,在实验结束后移除功能标志。但这是一个容易被忽视的任务。另一个选择是构建自动化工具来捕获不活跃的功能标志。更进一步,你甚至可以部署工具,为工程师创建自动化的pull request,建议移除过时的功能标志。Uber的Piranha就是一个例子,它根据标志预期的永久行为自动重构代码。
你的团队拥有和运营的系统有多健康?回答这个问题最直接的方式是通过仪表板来可视化团队的关键业务和系统指标。工程师应该能够轻松理解他们的系统是健康还是不健康。
你所在的团队是否有仪表板?如果没有,问问为什么没有。关于仪表板应该如何展示没有固定规则;只要对团队有意义就行。当然,如果利益相关者也能理解它是额外的好处,但这不是必需的。如果没有仪表板,那就开始着手制作一个!
本书第五部分的"可靠软件工程"章节中有关于定义监控内容的建议。
作为staff+工程师,你的目标应该是提高你所在工程团队和其他工程团队的效率。高效的工程团队可以使用多种工具和流程。我们来看看其中的一些选择。
持续集成(CI)指的是通过pull requests频繁地将代码集成到主分支。每个打开的pull request都会触发自动构建,通常包括以下步骤:
CI的好处是能够快速获得反馈,并比没有CI时更早地发现回归问题。
所有CI系统面临的最大挑战之一是将自动化测试的运行时间降低到理想的几分钟内。如果工程师打开新的pull request但必须等待超过30分钟才能得到反馈,这就不是一个及时的反馈循环。
在大型代码库和测试数量众多的情况下,使CI自动化快速执行特别棘手。以下几种方法可以帮助加快速度:
持续部署(CD)进一步推进了CI,将已批准的代码更改直接部署到生产环境。CI和CD通常是相辅相成的,如果没有CI,CD就没有多大意义。
高级自动化部署实践对大型系统特别有价值,包括:
CI和CD系统为工程师提供快速反馈,并通过自动化测试和部署步骤减少错误。CI/CD在大多数科技公司都是非常普遍的实践。
然而,CI/CD系统也有缺点:
这是许多科技公司常用的策略,所有工程师在代码库的单一共享分支(通常称为"main"分支)上工作。这与在长期分支上工作并不频繁地合并到发布分支相反。
基于主干的开发有几个优势:
基于主干环境的最大缺点是需要在构建工具和CI/CD上投入更多。合并到主干的频率更高,构建也更频繁,这可能给构建系统带来压力,因此需要提高构建吞吐量并减少构建时间。
对于采用基于主干开发的公司来说,拥有一个至少部分负责构建工具的平台团队是很常见的。在代码库上工作的工程团队越大,这就越复杂。
一种常见的发布控制方式是通过代码中的功能标志(Feature Flag)隐藏新功能。功能标志可以针对一部分用户启用,从而执行新版本的代码。
功能标志的实现相对简单,例如,对于一个名为“Zeno”的假想功能,代码可能如下所示:
if (featureFlags.isEnabled("Zeno_Feature_Flag")) {
// 执行新代码
} else {
// 执行旧代码
}
功能标志在以下场景中特别常见:
我们会在第四部分“发布到生产”中更详细地讨论功能标志。
单一代码库指的是在一个大型代码库中存放整个平台的所有源代码。例如,可能有一个单一代码库存放所有的Go代码,一个存放所有的iOS代码,等等。这是Google、Meta和Uber等大型科技公司的常用方法。
单一代码库的最大缺点是其体积庞大,代码库可能变得如此之大,以至于在单个开发者机器上检出代码库非常耗时,甚至对于非常大的代码库来说是不可能的。工具支持是另一个挑战;大多数版本控制供应商对适度大小的代码库提供了更好的支持。
然而,借助专用工具,在单一代码库中开发往往效率更高。这是因为:
大多数科技公司一开始为每个主要项目创建独立的代码库。随着公司发展,为了提高开发者的生产力和体验,自然会推动向单一代码库的转变。
微服务架构将应用程序组织为一组松耦合、可独立部署的服务。这些服务通常较小,被称为“微服务”。单体架构则相反:所有功能都在一个代码库中,并作为一个整体运行。
关于单体应用设计和微服务架构哪种更适合公司,始终存在争论。这两种方法都有权衡,也有各自成功的案例。例如,Shopify坚持使用单体应用设计,其核心代码超过200万行Ruby代码,而Uber则走微服务路线,运营着超过2000个服务。
模块化单体和更模块化结构的微服务 似乎是两者之间务实的中间地带。随着公司规模的增长,两者的痛点都会愈发明显:
采用单体架构的公司最终会将其模块化,使工程师可以在更独立的小部分上工作。这是Shopify采用的方法。
采用微服务架构的公司最终会引入指导原则,以更具逻辑性的架构来组织和结构化微服务。这是Uber采用的方法,他们将数千个微服务组织成几十个集合,称为“领域(domains)”。
一些工具可以显著提高工程团队的效率,尤其是在大型科技公司。这些工具可以缓解开发者生产力的痛点。
服务目录
在团队构建服务或微服务的公司中,服务的泛滥可能成为问题。随着团队和服务数量的增加,回答以下问题变得越来越困难:
最直接的解决方法是创建一个服务目录。这是一个门户,团队可以在其中注册他们的服务,工程师可以搜索这些服务。许多大型科技公司会构建自己的服务目录,但越来越多的公司采用开发者门户(Developer Portals),它们提供服务目录作为一个功能。
代码搜索
搜索整个代码库的难易程度如何?你的公司代码库的搜索方法是否支持以下功能:
谷歌早在20多年前就有一个团队专门负责构建和维护高级代码搜索工具。谷歌意识到高效搜索代码库是工程师生产力的一个重要提升,其“代码搜索”产品支持上述所有功能及更多。
版本控制供应商(如GitHub和GitLab)在一定程度上支持代码搜索。Sourcegraph是一个更知名的供应商,其目标是构建与谷歌一样强大的代码搜索工具。
鉴于高效搜索源码的价值,一些公司对此却毫无考虑,这显得有些奇怪。作为staff+工程师,了解公司在这一方面的现状,并考虑改进代码搜索是否能提升整体工程效率,是值得的。
开发者门户
最知名的开源开发者门户是Spotify开发的“Backstage”。它是为了解决公司扩展到数百个团队、许多服务以及项目脚手架方式日益碎片化时所遇到的痛点而构建的。
Backstage由多个组件组成:
像Google、Meta、Amazon和Uber这样的大型科技公司都有自定义的开发者门户。在其他地方,采用现有开发者门户(如Backstage或其他替代方案)的做法越来越普遍,这些门户可以是开源的,也可以从供应商处购买。
云开发环境
开发软件的默认方式通常是本地开发,而不是使用云。以下是使用本地开发环境的步骤:
随着代码库的增长,大型科技组织中的开发者生产力可能会因以下原因下降:
git status这样的相对简单的git操作需要超过10秒当事情变慢时,云开发环境(CDE)可能成为一个有趣的选择。CDE提供了本地环境不具备的优势,包括:
云环境也有缺点:
对于大型组织来说,云开发环境很有意义,但对小型团队来说意义不大。中型团队和公司应该识别工程团队的生产力瓶颈,并据此决定。
实验总是一个选择。说服整个工程组织试用CDE可能令人生畏。但你真的需要这样做吗?在一两个团队中试用CDE解决方案并收集反馈可能更有效。CDE是否带来更快的构建和测试?工程师是否感到更高效?他们是否花更少的时间修复环境?收集数据以告知你的团队是否保留此设置。这可以帮助更广泛的组织决定是否采用CDE。
AI编码助手正在兴起,自2022年ChatGPT发布以来,其采用速度加快。第一个被广泛采用的AI编码助手是TabNine(2019年),随后是GitHub Copilot(2021年),以及2023年的其他许多工具——如Sourcegraph Cody、Replit Ghostwriter、Amazon CodeWhisperer等。
AI编码助手提高了开发者的生产力,我们仍处于充分利用这项技术的早期阶段。关于AI助手,有几点需要考虑:
数据所有权和保留对大多数科技公司来说很重要。模型的数据是否也与供应商共享?如果是这样,这些敏感数据——公司的源代码!——可能会以意外的方式泄露或保留吗?
可以构建的AI编码工具不仅仅是编码助手。AI和大型语言模型(LLM)的明显应用包括:
科技公司已经在为其中一些用例构建工具,我认为供应商提供更先进的AI辅助工具只是时间问题。
在获取上述工具之一时,通常有三种选择:
正如往常一样,没有普遍的规则说明构建、购买或采用哪个是最佳选择。供应商会说购买是长期最便宜的选择。工程师倾向于构建,而这或采用解决方案往往在绩效评估和晋升中得到更积极的认可。此外,构建比与供应商谈判条款更有趣且更具教育意义。
传统观点认为,你应该对公司的核心能力保持完全控制,并购买其他东西。这在理论上听起来不错,但一些最成功的科技公司反复忽视传统智慧,遵循对他们有效的道路。
如果你有能力影响或做出购买/构建/采用决策,请遵循与任何重大工程选择相似的过程。收集信息并填补空白;例如,通过原型化替代方案来了解它们涉及的内容。
你的组织的软件工程流程很可能需要遵循某些合规和隐私指南。
大型科技公司通常有一个合规或法律团队来决定需要遵循哪些法规、流程和指南。一些公司拥有内部的合规、隐私和安全团队,而另一些公司则聘请外部顾问。合规违规的代价是巨大的,无论是在声誉上还是财务上。Staff+工程师的部分职责是确保公司认真对待这一领域。
你的产品可能还需要遵守个别国家的隐私法律。
数据日志记录是一个需要仔细思考的领域:
作为一种有益的实践,定期审查正在记录的数据以及如何记录,以确保任何系统中都没有非安全存储的PII。
你的系统可能需要接受合规性审计,例如GDPR或PII规则的审计。审计通常由专门从事这项工作的供应商完成。审计标准通常说明哪些是可接受的做法。然而,现实中,许多要求可能模糊不清,需要专家解释。
在参与或领导审计时,尽量找到曾经经历过类似审计的人。如果这不可能,考虑提出聘请顾问的理由,以帮助为审计做准备。
同样有效的方法是尽可能为审计做好准备,并与审计员合作。
根据审计的类型以及审计员的预期帮助程度,为审计做准备可能是一项重要的工作。在Uber,我们花了数月时间映射流程、对流程和工具进行更改,并在GDPR启动之前对它们进行审计。这项工作的工作量和变更规模使得这个项目成为公司较大的工作之一。
审计往往在第一次时最耗时和精力。一旦通过审计并建立了正确的流程,保持合规就容易得多了。
安全软件开发是一个不断发展的广泛主题,本书不会深入探讨。作为Staff+工程师,你需要对领域中的安全开发实践以及常见的威胁向量和如何减轻它们有深入的了解。
安全编码实践可以是与语言无关的,也可以是特定于某种语言的。最流行的与语言无关的安全编码指南是OWASP安全编码实践参考指南 。
除了OWASP指南,你可能还可以找到与你选择的语言和框架相关的安全编码实践。以下是一些示例:
依赖项作为安全漏洞始终是一个持续的安全威胁。一个你的系统使用的库可能有一天被发现存在漏洞。在该库被修补之前,你的系统可能是不安全的。2021年发现的最具影响力的依赖漏洞之一是Log4j日志库的安全漏洞,它可以被利用来通过拒绝服务(DoS)攻击使后端系统离线。
渗透测试是由专家测试系统漏洞的实践。如果你的团队或公司支持这一点,参与其中可以让你深入了解如何识别和修复系统中的安全风险。