下面的内容是我在作为一名程序员入职之前阅读的由Gergely Orosz写的The Software Engineer’s Guidebook。我将将阅读时得到的重要的信息总结成中文以供大家分享。
要真正精通一门语言。只有当你真正把一门语言了如指掌时,你才能达到新的理解、理解和能力水平。什么是“了如指掌”?它是指知道如何使用这门语言:语法、结构和运算符。这意味着了解最佳实践并理解为什么推荐它们。它意味着深入了解内部工作原理,理解内存管理和垃圾回收的工作原理,代码是如何编译的,以及性能方面的要点。
深入学习一门语言有不同的层次。首先,需要掌握语言提供的内容,包括内置的数据类型、变量和数据结构、运算符、控制语句、类和对象,以及标准的错误处理方式。了解并尝试一些更高级的语言特性,比如泛型、并行执行/线程处理、更复杂的数据类型,以及语言支持的其他功能。
学习一门语言的基础知识的一个好方法是查阅其文档;找到一份优质的语言参考资料,查看代码示例,或者学习一本关于基础知识的书籍。具有编程示例的视频课程同样有效,这些对某些人来说更合适。找到适合你的学习方式。拥有可以随时查阅的参考资料很方便,因此我建议投资一本书籍。
当你掌握了如何使用编程语言时,尝试深入探究。通过提出问题来实现这一点,例如:
在编程语言中,泛型(Generic)是指在编写代码时不指定具体类型,而是使用参数化类型,这样可以增加代码的灵活性和重用性。在泛型中,协变(covariance)和逆变(contravariance)是与类型参数的子类型关系相关的概念。
- 协变(Covariance):如果类型
S是类型T的子类型(即S <: T,其中<:表示子类型关系),那么泛型类型C<S>是C<T>的子类型。简而言之,泛型类型随着类型参数的子类型而变化。- 逆变(Contravariance):与协变相反,如果类型
S是类型T的子类型,那么泛型类型C<T>是C<S>的子类型。逆变意味着泛型类型的参数可以是更一般的类型。
不是所有编程语言都支持泛型的协变和逆变。例如,Java 中的泛型是不支持协变和逆变的,但是在某些其他语言(如Scala和C#)中支持协变和逆变。
总结来说,泛型的协变和逆变允许类型参数随着其子类型或者更一般的类型而变化,这样可以提高代码的灵活性和可重用性,但实现方式因编程语言而异。
寻找书籍、视频、在线课程以及其他关于编程语言高级部分的资源。AI工具可以提供回答一些问题的捷径,但它们很可能不会像更详细的书籍或在线资源那样深入。在尝试提出类似问题时,不要忘记这些问题都很复杂,并涵盖你可能不了解或无法单独解决的领域。
我深入学习语言的方法是通过研读相关书籍、参加高级课程、阅读深入讨论细节的文章,最近还开始向人工智能助手提问总结概念并核实答案的正确性。任何尖端技术中都会有多位专家深入探讨语言的方方面面并分享他们的发现。寻找深入的资源,并投入时间去学习它们。
学习并使用工具来深入了解内部原理以及更多关于运作方式的信息。这些工具可能包括内存和CPU分析器、开发者工具和诊断工具。这些工具不仅帮助你更好地理解语言的内部运作方式,而且在更高级的调试中也会派上用场。
在Uber,我的团队在后端使用Java、Python和Node.JS。我们使用这些不同的语言是因为我们需要与其他团队编写的服务进行交互。
在公司内部,Go语言逐渐开始流行起来,很多新的服务都是使用Go构建的。我们的团队喜欢探索,所以我们决定使用Go构建一个新的服务,这也给了我们一个学习这门语言的机会。
有一名实习生加入了我们的团队,他非常热爱Go语言。甚至在实习之前,他就花了很多时间做教程、阅读有趣的部分、做一些小项目,尝试不同的语言特性。这名实习生立刻参与到了代码审查中,开始向队友提供如何用“Go的方式”编写代码的建议。团队的工程师们开始更多地让他参与进来,要求他审查Go代码,和他一起构建服务。
这个实习生成为了我们团队的“Go专家”。他是如何做到的呢?他比任何人都投入了更多的时间和精力,并且不断深入了解这种编程语言的工作原理。
这段话提醒了我们,即使缺乏经验,也可以通过投入时间和精力来掌握一门语言、框架或特定领域,最终成为专家!
现如今,仅使用一种编程语言的情况已经很少见了,尽管你可能主要会在某个框架中使用你的“主”语言。举例来说,如果你在前端使用JavaScript或TypeScript,你可能还会用到React、Next.js等前端框架。如果你写Ruby代码,你可能会使用Rails。如果是PHP,那么你可能会选择Laravel,以此类推。在开发产品时,倾向性框架能够带来更快的进展,因此也更受欢迎。
掌握一个框架的过程和学习一门语言类似。要先学习框架的基础知识,然后深入了解其内部运行原理。
开源框架的一个优势在于,即使刚开始时它的代码库看似复杂,你仍可以直接深入了解其内部运行机制。这是大多数编程语言所没有的优势。
许多开发人员满足于对他们主要框架的“够好”,因此不投入过多时间深入了解事物运作的原理和方式。
如果你迈出额外的一步,花更多时间深入了解框架,你所获得的知识应该会在调试棘手的 bug、进行架构决策或迁移到框架的新版本时成为优势。
当你熟悉足够的第一种语言后,尝试学习第二种语言。举个例子,如果团队中有一些成员在使用不同的编程语言,那么值得考虑自愿加入他们的工作,并明确表示你对学习这种新语言很感兴趣。
学习第二种语言的好处远比人们通常认为的要多:
AI工具在学习新语言的语法方面可以提供很大帮助。许多人工智能助手可以将代码从一种语言“翻译”成另一种语言。它们还可以回答类似的问题,比如,“展示我在‘正在学习的语言中’声明函数的不同方式。”利用这些AI工具加快学习速度。只需记住它们可能会给出错误答案 - 所以一定要验证它们的输出!
有能力的软件开发人员拥有丰富的知识储备。这意味着他们至少对一种编程语言和框架有深入的了解。然而,这种语言或框架不一定是他们最早学会的。
我建议开发人员在职业生涯的早期就要在至少一个领域“深入”研究。 可以采取我们之前讨论过的方法,比如深入研读相关资源。另一个很好的方法是与某个领域的专家结对学习,向他们请教自主学习的资源并完成学习。
另一种深入学习的方式是学习每天遇到的“沉闷”但必要的内容。软件工程师本·库恩将这称为“蓝团研究”。蓝团研究这个术语来自Y Combinator联合创始人保罗·格雷厄姆的一篇文章,文章中“蓝团”是一个假想语言的名称。在本的文章《捍卫蓝团研究》中,他描述了为什么深入研究看似沉闷、毫无意义的框架和语言是有用的。
假设你选择的蓝团是React。你可能担心,如果将来转移到堆栈的不同部分,甚至是不同的Web框架,学习这些繁琐的细节会是没有用的。是的,有些可能会是没有用的。但React的核心思想——编写纯净的呈现函数,使用协调机制使更新变得快速——非常强大和通用。实际上,它现在已经被iOS(SwiftUI)和Android(Jetpack Compose)的下一代UI框架复制。学习React背后的原理使学习其他框架变得更容易。实际上,它甚至可以成为一个从一个框架“导入”到另一个框架的有用思想来源。
蓝团研究出奇地广泛适用,因为即使你在学习某个具体的蓝团系统的细节,该系统的设计将包含一个可提取的通用原理的核心。
“蓝团研究的复合效应会比你最初预期的更加显著,有两方面。首先,了解一个蓝团将使学习为实现相同目的的其他替代蓝团变得更容易,就像上面提到的React/SwiftUI示例。其次,了解一个蓝团更多将有助于加速学习堆栈相邻部分的蓝团。”
事实证明,你可以在打下深厚基础的同时也使自己变得更全面,而蓝团研究——了解你使用的工具和框架实际上是如何工作的——就是一个很好的例子。只要“你花时间学习舒适区以外的东西,那么你的知识和技能深度和广度都会增长。”
在编写代码解决问题时,它有时候不会按你期望的那样运行。经验不足的话,发生这种情况的频率可能更高。那么,你该如何找出问题出在哪里呢?检查代码,尝试逐步分析,直到找到错误。基本上,这就是调试。能够快速有效地调试的工程师,能够更快速地修复错误,迭代更快。虽然有些人似乎天生具备调试的天赋,但这一切都是可以学会的。那么,如何才能在调试方面变得更好呢?
了解调试工具大多数集成开发环境(IDE)如VS Code或JetBrains IntelliJ等,都配备了强大的运行时调试工具。但我发现,经验不足的工程师经常没有意识到这些工具有多么强大。在代码执行时检查代码是最好的方式之一,可以看到你所做的不正确假设,以及代码的实际行为。与“更改运行并希望它能正常工作”的方法相比,调试工具可以节省数小时的时间。首先要了解你所使用IDE附带的调试工具。设置断点并检查本地变量。逐步进入/跳出/越过函数并检查调用堆栈。查阅文档和教程,了解如何使用更高级的功能。一些调试器可能支持有用的功能,包括:
这些功能通常是现代调试器(debugger)提供的一些高级功能,对于开发人员在调试复杂程序时非常有用:
- 在运行时修改变量:
- 用途:允许开发人员在程序运行时动态修改变量的值,这对于调试过程中发现问题、验证假设或者进行实时调整非常有帮助。
- 在调试过程中评估表达式:
- 用途:允许开发人员在断点处或者程序运行时,通过调试器直接评估和执行表达式。这对于验证计算结果、理解代码行为或者调试复杂逻辑很有帮助。
- 条件断点和异常断点:
- 用途:条件断点允许在满足特定条件时触发断点,而异常断点则在异常抛出时中断程序执行。这些功能帮助开发人员在关注特定情况下快速捕捉程序状态。
- 观察点(设置在变量上的断点,当它发生变化时触发):
- 用途:当某个变量的值发生变化时,观察点可以在不中断程序执行的情况下通知开发人员。这对于追踪特定变量的变化、理解代码执行路径或者确认状态变化十分有用。
- 转到帧(从调用堆栈的另一个部分重新启动调试):
- 用途:允许开发人员从当前执行点跳转到调用堆栈的其他部分,这对于查看调用链、理解函数调用关系或者回溯错误来源非常有帮助。
- 在线程间切换:
- 用途:在多线程程序中,开发人员可以在不中断整体程序的情况下切换调试的线程,这对于并发调试和排查多线程交互问题非常重要。
- 在调试器运行时修改源代码:
- 用途:某些调试器允许开发人员在程序运行时修改源代码,这对于快速验证修复、实时调整程序行为或者修复临时Bug非常有帮助。
- 修改环境变量:
- 用途:在调试过程中修改环境变量可以模拟不同的运行环境,这对于测试不同配置、处理依赖问题或者模拟特定条件非常有帮助。
- 模拟传感器输入(用于基于硬件的环境,如移动设备):
- 用途:允许开发人员模拟实际设备的输入(例如传感器数据),这对于移动设备开发、传感器集成或者实验测试非常重要。
类似Visual Studio、JetBrains IDE和Chrome DevTools等工具支持几乎所有上述功能,现代开发环境也是以提高开发者生产力为目标构建的。如果你还没有尝试过它们,现在是一个很好的时机。
提高调试能力的一个被低估的方法是看那些非常擅长调试的开发者是如何做的。当听到一位同行开发者提到一个棘手的bug时,可以询问是否可以跟随他们或与他们配对进行调试。表明你有兴趣了解他们是如何找到bug根本原因的。在团队中,至少要与每位开发者配对一次进行调试会话。您肯定会学到新的调试技巧,也许会发现新的调试工具。
有时,你可能无法访问调试工具,比如在命令行工作时,或者你决定不使用调试工具。在没有工具的情况下进行调试是可能的。这些方法通常需要额外工作,但你可能会学到更多。以下是一些方法: