下面的内容是我在作为一名程序员入职之前阅读的由Gergely Orosz写的The Software Engineer’s Guidebook。我将将阅读时得到的重要的信息总结成中文以供大家分享。
一个全面发展的高级工程师应该精通几种编程语言和平台(如前端、后端、iOS、Android、原生桌面和嵌入式等),并至少在其中一个领域达到精通水平。我们将在第二部分”软件开发”中详细讨论如何掌握一门语言。
然而,一个高效的工程师不会仅仅满足于精通少数几种技术;他们会持续扩展自己对框架、语言和平台的知识。
当你已经掌握一门编程语言时,学习另一门语言会变得更加容易。 这是因为大多数语言在表面上都相当相似。例如,如果你懂JavaScript,那么学习TypeScript就会相对轻松。同样,如果你懂Swift,那么你就能够通过阅读来理解大部分Java、Kotlin或C#的代码。
当然,每种语言都有其独特的语法、特性、优势和劣势。通过使用这些语言并将它们与你已经熟悉的语言进行比较,你会逐渐发现这些细节。
编程语言主要分为三种类型:
你的第一门(甚至是第二门)编程语言很可能是命令式语言。学习额外的命令式语言确实有用,但选择一种不同类型的语言会让你作为专业人士获得更大的成长。
命令式、声明式和函数式语言各自需要不同的思维方式。从命令式语言切换到函数式或声明式语言可能具有挑战性,但通过这样做,你可以扩展你的理解和”工具箱”。
函数式编程广泛应用于命令式语言中,因为遵循函数式模型可以保证不可变状态。一个很好的例子是响应式编程模式,它采用了函数式编程的思想,并为Java、Swift、C#、Scala等语言提供了更加函数式的模式。
掌握了每个类别的一种语言后,学习其他语言就会容易得多。因为命令式语言和函数式语言(如Go和Elixir)之间的基本差异比两种命令式语言或两种函数式语言(如Go和Ruby,或Elixir和Haskell)之间的差异更大。
软件工程师通常会专注于某个平台,如后端、前端、移动端或嵌入式平台。但在开发新功能或解决问题时,工作往往会跨越多个平台。例如,开发新的支付流程可能需要在后端、前端甚至移动端进行修改。如果对相邻的技术栈一无所知,就很难调试更复杂的全栈问题,也难以领导全栈功能的开发和发布项目。
全栈工程已经成为科技行业高级工程师的基本要求。这是因为产品和业务相关人员并不关心嵌入式、后端和前端/Web之间的区别。一个全面的高级工程师应该能够处理任何问题,并弄清楚如何在不同平台之间分解问题。
要建立这种理解,可以采取以下方法:
AI助手可以帮助在语言和平台之间过渡。像GitHub Copilot、ChatGPT、Sourcegraph Cody等工具可以更容易地学习新的编程语言或切换平台。这些助手可以:
• 将代码从一种语言翻译成另一种语言
• 总结一种语言中如何声明函数和变量
• 总结两种语言之间的差异
需要注意的是,许多AI助手存在幻觉问题,有时会编造不真实的内容。因此,有必要验证它们的输出。但对于熟悉新语言或平台的目的来说,AI助手是有帮助的,可以加速学习过程。
高级工程师和初级工程师在调试和追踪复杂bug方面的区别是很明显的。更有经验的工程师往往能更快地进行调试,并且能轻松地找出更具挑战性问题的根源。他们对问题可能出在哪里,以及从哪里着手调试和解决也有更好的直觉。他们是如何做到这一点的呢?
部分原因在于实践和专业知识。你编写代码的时间越长,就越容易遇到意想不到的边缘情况和bug,从而开始建立一个”潜在问题根源的工具箱”。
随着时间的推移,你还会扩展你的调试工具箱。在第二部分”软件开发”中,我们讨论了如何提高调试能力,包括:
高效调试的能力往往是区分有经验和经验较少的工程师的一个重要因素。以下是一些改进调试能力的方法。
特别是在大型科技公司,你调试生产问题的能力在很大程度上取决于你知道在哪里找到生产日志和生产指标,以及如何查询这些指标。即便如此,通常也需要几个月的时间,高级工程师才能真正体会到定位这些系统的重要性。
在团队负责多个服务的公司中,找到正确的仪表板和日志门户可能特别具有挑战性,因为每个服务可能使用不同的日志记录方式,在各种系统中记录信息,或使用不同的日志格式。
加入一家公司后以,要优先了解生产日志存储在哪里,以及在哪里可以找到系统健康仪表板。这些可能存在于Datadog、Sentry、Splunk、New Relic或Sumo Logic等系统中。或者存在于基于Prometheus、Clickhouse、Grafana或其他自定义解决方案构建的内部系统中。它们可能分布在多个地方。要弄清楚它们在哪里,获得访问权限,并学会如何查询它们。对你团队拥有的系统及与你交互的相关系统都要这样做。
作为高级工程师,你应该知道该查看哪些仪表板和日志系统。但如果这些系统不存在,那么你就有责任建立它们并使其易于使用。我们在第五部分”可靠系统”中会详细讨论这个话题。
彻底理解小型代码库。当处理一个规模适中的代码库时 - 通常不超过10万行,由不超过20人编写 - 你没有理由不能准确地知道每个部分的位置。仔细查看代码库的结构,阅读大量代码,并绘制出代码不同部分之间的连接方式。
根据阅读代码绘制架构图,并请团队成员确认你的理解是否正确。达到你知道代码的哪个部分负责什么功能的程度。
对于大型代码库,最好理解其结构并知道如何找到相关部分。在大公司,拥有超过100万行代码、由数百名工程师构建的代码库很常见。深入理解这种规模的代码库是不现实的,但合理的目标是对其有广泛的了解,以便能够深入研究你需要处理的部分。
在使用单体仓库(monorepo)的公司,要了解其结构以及单体仓库的不同部分负责什么。系统的各个部分是如何构建的?测试是如何运行的?
在使用独立存储库的公司,寻求获得这些存储库的访问权限。努力从高层次上理解与你的团队相关的系统如何工作。检查其中一些,构建它们,运行测试,并在本地运行服务或功能,这是一个很好的练习。
找出如何搜索整个代码库,并学习有用的快捷方式。大多数公司都有某种”全局代码搜索”。这可能是一个自定义的内部解决方案,或者是像GitHub代码搜索或Sourcegraph这样的供应商。了解如何使用全局代码搜索工具及其支持的功能。例如,你如何搜索代码的特定文件夹?如何搜索测试用例?如何只搜索你的团队拥有的代码库?
即使在大公司,工程师可以访问大部分代码库,但某些部分可能是禁止访问的。这通常是出于合规、监管或保密的原因。在大多数情况下,这不应该对你的日常工作产生实质性影响。但如果它确实减慢了你的工作速度,你可以申请访问权限。
一些生产问题是由基础设施问题引起的。弄清楚服务是如何部署到生产环境的,机密信息是如何存储的,以及证书是如何设置的。研究基础设施是如何管理的,以及基础设施配置存储在哪里。
如果你在一家有专门的基础设施团队的公司工作,当你怀疑有基础设施问题时,可能会很容易跳过学习过程而直接求助于基础设施团队。然而,这种方法最终会拖慢你的速度。此外,学习基础设施如何在底层工作不仅本身就很有趣;这种深入的理解对于全面发展的高级工程师来说是基本要求。
在故障发生时进行调试,并重新阅读以往的故障调查报告。提高调试技能的一个好方法是在真正重要的时候进行调试,比如在故障发生时。如果你的团队遇到故障,主动提出帮助调查并找出原因,以便缓解问题。
调试故障需要学会访问和分析生产日志,定位负责某些业务逻辑的代码,修改代码,验证更改并推广这些更改。所有这些都发生在紧急情况下,当及时行动很重要的时候。
除了等待你的系统出现bug外,还有其他方法可以提高故障调试技能。如果你的公司发布事后分析报告,可以查看以往的故障报告。在阅读时,尝试通过定位指出问题的日志和找到故障背后的代码来”调试”。研究历史故障是学习你不熟悉的新仪表板和系统的好方法,也可以发现新的故障缓解步骤。
“技术债务”是一个经验丰富的软件工程师都非常熟悉的术语。它描述了随着时间推移,在系统上进行软件开发所累积的增量成本。技术债务是代码积累并使事情变得更加复杂时所产生的结果。
技术债务的特点类似于借贷。如果使用得当,债务可以加速进展。但如果使用不当,维护成本可能会变得昂贵。技术债务导致的”破产”是真实存在的:当删除并重写整个代码库比继续维护和修复更便宜时,就到达了这个临界点。
软件工程师/工程组织与技术债务之间关系的典型阶段如下:
虽然技术债务是不可避免的,但当遵循某些有助于可维护性和便于代码修改的健康工程实践时,技术债务的积累会大大减缓。这些实践包括编写可读性强的代码、测试、代码审查、CI/CD、文档编写、合理的架构决策等。
对于小规模的技术债务,最好的方法是在日常工作中逐步解决。遵循童子军露营的原则 - 在这里是指代码库而不是营地 - 让它比你发现时更整洁。
对于大规模的技术债务,首先要进行清点,并量化它们的影响和消除所需的工作量。当存在大量技术债务时,你不可能一次性全部解决。
如果没有关于大规模技术债务的数据,很难做出好的决策来处理它们。如果其中包含需要数周或数月才能修复的问题,团队必须进行优先级排序。偿还技术债务的价值与具有业务影响的工作相比如何?
为了有目的地解决技术债务,提出具有明确影响的项目。 是否有些技术债务亟待解决,因为它们的影响太明显?可靠性、成本节约、更快的开发周期和更少的bug,这些都是我见过人们在提议消除大规模技术债务或完成迁移项目时常常提到的影响类型。
例如,将代码逻辑重复视为一种技术债务,代码的某些部分被复制粘贴到不同的地方。将重复的代码移到共享库中会产生什么影响,成本如何?对于经常使用的代码库,影响会更大。另一方面,对于即将废弃的代码库,可能会耗费大量精力却收效甚微。
再比如构建时间过长的问题。如果许多工程师频繁地运行构建,那么偿还这项技术债务的影响可能会很大。只需将每次构建浪费的时间乘以工程师每天运行这个构建的次数,再乘以执行此操作的工程师数量即可。
将技术债务的消除与高影响力的项目结合起来。这是被视为高效且能够消除技术债务的工程师的秘诀:不要请求许可。高效的工程师不会寻求批准来消除技术债务,而是将技术债务的消除与高影响力的项目捆绑在一起,作为项目的一部分来处理。
最高优先级的项目通常是雄心勃勃的,具有高可见度。 而要交付这些项目,往往需要频繁接触技术债务最多的系统。如果你需要修改一个技术债务严重的系统,这意味着你的工作会变慢。因此,如果你花时间修改技术债务严重的系统,顺便提出减少技术债务的建议是明智之举。
与其花费大量时间和精力来偿还技术债务,不如花较少的时间和精力来预防或减缓技术债务的积累。以下是一些实践方法:
是否存在技术债务过少的情况?如果你偿还了足够多的技术债务,你最终会意识到确实存在这种情况。它被称为”过早优化” - 可能会在关键时刻减慢团队和公司的速度。
以创业公司为例。在启动时,速度和快速迭代是生存和取胜的关键。在这个时候,你是担心干净的API和漂亮的数据模型,还是只把所有东西都放在一个任何开发人员都可以修改的非结构化JSON中?我工作过的那些最终成功的创业公司,在早期阶段都采取了技术债务较重的方法。
共享乘车应用Uber就是这样一家创业公司。当我加入时,有很多遗留的早期技术债务,短期决策困扰着代码库的某些部分。但这些技术债务发挥了作用,它让Uber在速度最重要的时候能够快速行动,实现产品市场契合。之后,Uber投资清理这些债务。
在早期项目、一次性原型、最小可行产品(MVP)以及验证创业公司商业模式时,技术债务是你想要的东西。技术债务可以通过后期投入时间和开发人员来修复,就像Uber所做的那样。大多数处于后期且快速增长的创业公司通常都在忙于偿还早期的技术债务 - 因为到了这个阶段,这些创业公司通常有更多的人力和时间来解决这个问题。同样,如果一个拥有成熟产品的团队没有通过不时投资来控制技术债务,那么可能也有问题。
务实的工程师不会将技术债务视为坏事;他们将其视为速度和质量之间的权衡。他们将其视为系统的一个特征。他们将技术债务置于项目目标的背景下,不会试图偿还超出需要的部分。他们还会跟踪债务,并在债务堆积之前采取措施减少它 - 必要时还要发挥创意。
“文档”可以指多个领域,并非所有类型的文档都适用于每个项目。以下是工程师常写的文档类型及其适用情况:
这些文档在项目开始时编写最有价值。一旦项目发布或迁移完成,通常无需继续维护。
API或接口文档。在开发供其他软件工程师使用的API或接口时,这种文档需要解释以下内容:
在修改代码时,需要及时更新API文档。可以探索自动生成文档的方法,例如使用代码注释生成。
SDK(软件开发工具包)文档以及集成或插件文档,与API文档类似,这些指南帮助其他团队使用你的SDK、集成或插件。
一些工程团队仍然会为每个主要版本编写发布说明,尽管这种做法在业界似乎已经不太流行了。编写发布说明通常很简单,不会花太长时间。只需总结哪些客户会注意到你的工作的影响,或汇总所有已发布功能的影响。这可以使更新API、SDK和集成文档变得更加容易。
发布说明是反思工作的好方法。它们也是与其他工程团队或非技术团队等利益相关者分享的绝佳参考。
新工程师如何了解你的团队负责的系统是如何工作的?答案是良好的入职文档,其中包括:
入职文档对新加入的人非常有价值,有时对现有团队成员也很有用!不幸的是,通常很少有动力去编写或维护它,特别是如果没有新工程师加入团队的情况下。
我建议投入精力编写这份文档,以备新同事加入时使用,并为每位新加入者更新。为什么不让新人在入职结束时编辑文档、修正不正确的细节并添加缺失的部分作为一项任务呢?
团队如何运作?团队的使命如何与业务目标联系起来?团队手册回答了这些问题,还涵盖了以下方面:
如果你的团队还没有手册,可以与领导或经理讨论,并建议编写一份。如果你在团队中有足够的信任,你可以直接开始编写这样一份手册,并征求其他人的意见。
当值班工程师收到系统警报时,应该采取哪些步骤?在哪里可以找到相关的仪表板和日志?该系统对其他系统有哪些依赖关系?好的值班运行手册能回答这些问题。
如果你构建的软件有最终用户使用,那么他们需要解释其工作原理的手册。他们需要不同的编程语言来描述系统,而截图和视觉提示将非常有用。
即使这些指南已经存在,也要确保在更改系统部分会改变用户行为时做出标记。如果可能,亲自更新用户指南;毕竟,你做了更改,最了解它!
编写文档最初可能很耗时,但这是一项非常高杠杆的活动。此外,一旦文档编写完成,保持更新就会变得更加简单。良好的文档,如入职文档,可以通过提供关于系统如何工作的教育来大大减少技术债务。
作为高级工程师,你要努力提供高质量的工作,并帮助团队做到同样的事情。一个显而易见的方法就是采用最佳实践。
什么是”最佳实践”?它是一种在你的工作环境中经过验证的、非常有效的工程方法。遵循最佳实践的个人和团队通常能更快地完成工作,产生更少的缺陷,并产出更容易维护的代码。
然而,这个术语可能会产生误导,因为每个团队和公司在技能和动态方面都有所不同。对一个团队非常有效的做法可能对另一个团队效率较低。
我建议用”软件工程实践”这个术语来替代”最佳实践”。经过验证的软件工程实践适用于软件工程过程的几个部分,例如:
你应该为你的团队采用哪些实践?实际上,这不是一个好的起始问题。相反,你应该问的是:你的团队在发布方面面临的最大挑战是什么?
问题是有太多的bug进入生产环境吗?考虑TDD、测试环境或生产环境测试等实践是否有帮助。是代码审查耗时太长吗?考虑是否有实践可以帮助解决这个问题:例如,专门分配时间进行审查、减少审查次数、缩小代码变更范围,或其他方法。
你对工程实践越熟悉,就越能识别出某个特定实践何时可以帮助你的团队。熟悉这些实践的一种方法是学习它们。另一种方法是从有第一手经验的人那里获得意见。最有教育意义的方法是通过尝试它们来获得个人经验!