写点什么

高级软件工程师成长秘诀

  • 2020-11-03
  • 本文字数:13700 字

    阅读完需:约 45 分钟

高级软件工程师成长秘诀

2018 年的时候,我开始在彭博社(Bloomberg)工作。从那之后,事情发生了很大变化。我不再是公司里最初级的成员了,而且我还指导过几个工程师,这真是太棒了。这有助于我观察自己与其他人的差别,吸收他们的最佳实践,并发现我不知不觉中已经做得很好的事。


每年的工作回顾是一个很好的方式来提炼我学到的经验教训。它们对于模式匹配也很有价值。只有我从特定模式观察时,才会发现问题。然后我就开始有意识地跟踪这些模式


今年的宏观主题是扩大眼界并挑战边界。它还涉及到聚焦视野,以及向去年的章节中增加细微差别。如果你预先读过我去年的评论就更有意思了:你就可以区别出我的成长。


这些回顾都从一个问题开始:我如何进一步成长?

借助不同的抽象阶梯成长

进入第二年的时候,我已经准备好所有的基础知识了。我已经摘完了所有低垂的果实,我的成长速度开始变慢。这种感觉很不好。我脑海中的最大问题就是“我如何进一步成长?”


我能做来提高我的编码技能的事情有限。大部分博客都讲,要编写简洁的代码、重复练习、不要重复等等,都是比较细微的建议。几乎没有任何一个博客的建议能对我产生立竿见影的效果。


不过,我确实发现了一些有见地的东西。我在软件开发周期内工作,但是这个周期是更大的一个周期的一部分:产品和基础设施开发周期。我决定接触得更广泛而不是更深入。令人惊讶的是,这种广度使得了解得更加深入。


我从三个大的方向展开:学习我周围的人在做的事情、学习良好的思维习惯、获取新的思考工具。

学习我周围的人在做的事情

由于我不是在一个封闭的系统,这使得我能够更好地理解产品经理、销售人员和分析师的工作。最终,这是一门通过产品赚钱的生意。我们的目标不是编写代码,而不是做一门能盈利的生意。


大多数大公司并不是只做一件事,这意味着在同一家公司有几种不同的赚钱路径。每个人至少在一条路径上——如果他们不在,那么他们不会在这家公司。


跟踪这些路径以及自己所在的路径是非常有价值的。这帮助我明白了自己有多么重要,以及我可以利用哪些杠杆来提高效率。有时候,这是为了让销售工作更简单,这样他们就可以做更多的销售。还有一些时候,这是关于为客户构建一个新功能。还有一些时候,这是关于改进一个不断崩溃的功能。


产品经理是最好的来源。他们知道企业如何赚钱,谁是客户,以及客户需要什么。


在过去的一年里,我与我路径上的每个人都安排了几次会议。这给我的另一个好处是了解其他人的工作的上下文。这使得我可以更好地进行沟通。以正确的方式构思事物的作用是很强大的。


例如,一次谈话帮助我理解了销售部的 Sarah 想要一个批量更新工具。一些公司有许多员工,一个一个地更新他们的信息是一件痛苦的事情。我编写的代码可以减轻 Sarah 的痛苦。

学习好的思维习惯

软件工程需要善于思考并作出正确的决策。编程就是实现这些决策。


思维习惯就是你的大脑经常做的事情。这可能是你看见 Y 发生的时候想到 X,或者将思维工具 X 应用到问题 Y。简而言之,思维习惯有助于更好地思考。


我怀疑自己如果早学会了这些一般技能,我应该能够更好地将其应用到软件工程中。

善于思考

软件工程是一个很好的实践善于思考的领域。反馈回路更短,测量正确性不会花费太长时间。


我潜心研究认知科学。这是一项值得探索的永久技能——无论我最终做什么事情,它都能够帮助我,并在我的一生中都会带来回报。其中一个产出是批判性思维的框架。这是复合的,而复合是强大的


这其中有很多好东西,我会稍后再谈。它们值得各自单独的章节。

提高日常工作效率的策略

硬币的另一面是让你善于思考的习惯。它开始于注意一天中的小麻烦、会议的低效,然后找出避免这些问题的策略。这些策略性的改进是被低估的。


你决定要做什么,然后让它自动运行,解放大脑来思考更多有意思的事情。当然,那也是一种习惯。


我注意到的一些好习惯:


  • 开会一定要做出决定或着有下一步行动,否则不要离开会议

  • 决定事情由谁完成。没有负责人的事情很少被完成的。

  • 记录项目期间做出的决策。


这种模式是在回顾期间发现的,因此我很想关注并在下一年收集更多策略。有一位杰出的敏捷大师对我负责,能帮助我更好地遵循这些策略。

获取新的思考工具和思维模型

新的思考工具与善于思考有关,对于软件工程更是如此。思考工具帮助我更好地思考具体的工程问题。


我对此采用了及时处理的方法。只有当我被某件事情困扰时,或者当我发现我的抽象和设计决策不起作用时,我才寻找新的工具。


例如,我最近正在为许多复杂的业务逻辑领域头疼。边缘案例很常见,我们想要设计一个系统来很好地处理这个问题。那时候,我读到了领域驱动设计。我可以立即将它应用到实践中,产生巨大的影响。后来,我更好地掌握了这些理念。我获得了一种关于如何创建企业软件的新的思维模型。


我持续学习并获取新的思维模型的第二种方法是通过阅读 Hacker News 上的文章。这些都是非常有意思的想法,其中一些我也已经应用到实践中,但是很多想法没有上面提到的技术高效。我仍然这样做的原因是绘制技术图——这是我了解现有的技术,那么当我遇到问题时,我会知道有一个方法可能会有所帮助。


我获取更好的思维模型的最后一种方法是通过学习新的不同语音。这种多样性很重要。学习 lisp 的另一种方言比学习 C++03、一种函数式编程语言、一种动态类型语言和一种 lisp 的好处要小得多。今天,J看起来很有趣,我就可以考虑学习。这种一种我以前从未使用过的思维模型。


我从中获取了好多益处。每种语言有其自己的词汇和语法,而词汇就是一种原始的思维模型。这是一种新的视角来看待如何做事情。


当内存管理在你的可控范围内时,你就会理解指针和分配器是如何工作的。当 Python 将这些抽象出来,你就会欣赏其带来的复杂性的减少。当函数式编程语言中的 maps 和 filters 出现,你就知道 Python 的 for 循环可以如何改进。事实上,这就是列表理解。然后你会发现有些事情用面向对象编程处理是多么简单。没有一种魔幻工具可以适合所有事情。然后你就会明白,尽管如此,你不必更换工具。你可以将一个项目的最佳实践应用到另外一个项目来解决你的问题:例如编写函数式的 javascript。原理比表现形式更重要。


总的来说,这就是我这一年所做的。以下是针对具体问题的简介

保护你的空闲

当我说 slack,我不是说 slack 公司,而是说一个形容词(空闲)。


给我带来高产出和提高生产力的一件事就是“慢一点儿”。想要完成更多事情?慢一点儿。


我的意思是:


我注意到人们总是急于解决问题。这可能是他们之前做过的事情,或者是我们有模板的事情。快速解决问题的感觉非常好。我之前也这样做过!无论如何,在一些非常具体的案例中,这么多是行得通的。


当我在做新事情时,我会花时间了解我正在工作的系统,以及与之密切相关的事情。如果事情太多了,我会尽可能多地学习。每次我重温系统,都想了解更多东西。


有空闲的时候,你就有机会去实验、学习和思考。这意味着你有充足的时间来完成任务。


没有空闲的时候,截止时间很紧张,你的全部精力都集中在完成这件事上。


保护你的空闲意味着不要让截止时间拘束你。通常,这和沟通一样简单(或者困难)。


空闲可能有一种负面的内涵“闲人”,但是保护空闲是非常重要的。这是一种以短期效率为代价的对自我成长的长期投资。


当我快速地交付代码时,我也会花很多时间来修复 bug。我没有花费时间来创建系统的合适的思维模型,这意味着我的设想与代码不匹配,而这种不匹配是大多数 bug 产生的地方。


我保护自己的空闲,因此我能够花时间来优先学习东西而不是做东西。


我最喜欢利用空闲来进行实验。有时候,我会发现一个对我来说完全没意义的 bug。我发现自己有点困惑,然后在 Stack Overflow 上找到答案,然后再继续。然而,这个问题会一直困扰我知道我理解这个 bug。Stack Overflow 回答了我的问题,但没有解释我的理解有哪些错误。为了提升我的理解,我需要进行实验。


如果我没有空闲,我没有时间去实验,这意味着我必须忘掉这个 bug。当有空闲的时候,我能够进行实验来发现我的理解到底哪里错了。当我发现了这个系统的一些新东西时,我喜欢这样的时刻。这会让我下次更有效率。

问问题

我们通常很不擅长问问题。或者是因为我们害怕这些问题会让我们显得很愚蠢,所以我们根本就不问这些问题,或者我们扯很多废话来问这些问题来避免我们显得很愚蠢,而不是学习更多东西。


问题是,在你找出答案之前,你无法判断一个问题是否愚蠢。我回避这个问题的方法是声明我会问很多问题。这让我得以解脱,从底层开始修补我认知上的漏洞。积极的团队文化会对此有帮助。


例如,以下是我学习打包软件的过程:


问:什么是软件包?


答:那是被打包到一起,可以被安装到系统上的代码。


问:为什么我需要软件包?


答:它们提供了一种一致的方法,可以将你所需的所有文件放在正确的位置。没有它们,东西很容易一团糟。你需要确保每个文件都在它该在的地方,设置了系统路径,并且依赖包可用。


问:软件包与我可以在自己的系统上安装的应用程序有什么不同?


答:想法很相似!Windows 安装包像是一个包管理器,能够帮助安装应用程序。类似地,DPKG 和 rpm 包有点儿像.exe文件,可以安装在 Linux 系统上。你可以借助于aptyum包管理器,它们有点儿像 Windows 安装包。


问:我明白了。所以 python 中的setup.py如何转换成一个dpkg?那是如何工作的?


答:我们有一个 python-debhelper,运行setup.py来进行转换。


答:哦,真有趣!你是怎么知道的?


答:debian/rules文件包含如何创建一个dpkg的说明。我看了它就弄明白了。


然后我就知道,我该自己看看这个文档了。我有足够的细节来理解大纲。事实证明,这并没有我想的那么简单,问这个问题也不蠢。


这是我养成的一个思维习惯,而且你可以经常问一些好问题。大多数问题都是依赖上下文的,但是我有一个比较喜欢的一般性问题。


这就是:你是如何发现 X 的?


当我问了一些都弄西,并且他们回答之后,我问的下一件事就是他们是如何知道的?这能帮助我下次自己解决问题。我做了上面的问答,让我了解了debian/rules文件以及它是如何工作的。


另一个可以问的好问题是你有哪些困惑。

发现困惑

有一天,我在使用 Python 中的 datetime。这些是我们的搜索引擎会索引的日期,我们希望它们是 UTC 格式。因此,我修改了我们的 pipeline 来在存储之前将日期转换成 UTC 格式。这就需要知道这些日期的时区。


我创建了一个这样的 datetime:


import datetimefrom pytz import timezoneindexed_date = datetime.datetime(2019, 11, 20, 12, 2, 0, tzinfo=timezone('Asia/Kolkata'))
复制代码


在这个测试中,这个转换偏差了 23 分钟而失败了。我当时没有注意到,但看到这个让我很困惑。所以,我将测试偏移量设为-23 分钟,这样这个测试就会通过了。


这是一种非常糟糕的思维方式。一旦我注意到了这点,我就再也看不到这点。有时候我还是会让这些曾经通过的问题困扰。


当然,有些人在 PR(译者注:pull request,拉取代码操作)时用“这看起来不对”来评论——这让我从我的固有思维中跳脱出来,去真正地找出哪里出了问题。


这是一个非常经典的 bug。Pytz 在每个时代都有不同的时区信息。在 1942 年之前,亚洲/加尔各答(Asia/Calcutta)的时区是+5:53:20。(是的,连城市名称都不一样)。当 pytz 时区被传送到一个新的日期,没有参考日期来匹配该年的那个时区。因此,它默认为第一个可用的时区——而这实际上是错误的。其文档中也提到了这点。正确的方式是使用tzinfo.localize(),将日期匹配到相应的时区,因为正在进行转换的是 pytz 日期。


import datetimefrom pytz import timezonetz=timezone('Asia/Kolkata')indexed_date = tz.localize(datetime.datetime(2019, 11, 20, 12, 2, 0))
复制代码


如果 PR 评论没有提醒我,我可能发现不了这点。这暴露了我掩盖困惑这种可怕的思维方式。从那以后,我一直很谨慎。


为了防止这点再次发生,我开始训练我的“注意肌肉”。这叫做注意困惑。不仅仅是写代码的时候,而不是处理任何事情时,都有粉饰疑惑掩盖问题的倾向。


每次你听到一些听起来很奇怪的东西,你都急于解释它为什么一定是真的,你就是在掩藏困惑。关于这点我还写了更多东西


一旦你开始注意困惑,你就会问一些让你困惑的问题。上一节可能听起来有点老生常谈,但是我希望本节能有所帮助。最难的是注意到什么让你困惑。

鼓励师

在一次冲刺中,我意外感受到了鼓励的力量。


鼓励给予绝地武士力量。这是一种由万物创造的能量场。它围绕着我们,浸润着我们;它们将银河系联结在一起。

——欧比-万-克诺比(译者注:Obi-Wan Kenobi,《星球大战》中的神秘绝地大师)


我认为欧比-万-克诺比领悟到了一些东西,尽管是在错误的领域。这是我在软件工程中可以利用的东西:成为一个鼓励师。


那次冲刺,我自己其实没有做很多事情。我写的代码很有限。相反,我在协调哪些变更应该在什么时候进行(这是一个很复杂的冲刺),测试它们是否工作良好,做了很多代码评审,提了很多候补设计建议,并在任何我可以解决问题的地方结对编程。我们完成了所有事情,而且,扩宽视野有助于更容易地进行 PR 决策。这是我们速度最快的冲刺之一。


鼓励给予工程师力量。这是一种由万物创造的能量场。它围绕着我们,浸润着我们;它们将代码系联结在一起。

——尼尔·卡卡(译者注:Neil Kakkar,作者本文)


好吧,我不会再延伸这个比喻了。^_^


对于我来说,如何成为一个鼓励师比如何成为一个 10 倍开发者更有价值。在实践中,团队文化是一个很好的鼓励师(或者泄气者)。


就像我可以创造思维习惯来增加我的产出一样,整个团队也可以。团队文化就是这样。回顾、评审和实验是一个团队为塑造他们的文化所做的内容。这个文化经常是变化的,因为团队成员来来走走,会增加他们的个人感觉。


增强能量的文化是一个鼓励师。我之所以能够做到上面所说的,正是因为我们的文化允许。我们的团队文化关注的是整个团队对冲刺的产出,而不是个人的产出。这允许我为了团队进行提升,而不是专注于我自己。


团队塑造文化,而文化又改造了团队。


这个理念也可以延伸到城市和国家:


一个不断受到军事威胁的社会将有一种崇尚军事优点的文化,一个以合作经济为特点的社会将强烈侮蔑懒惰,一个平等主义的社会将把专横视为一种主要的人格缺陷,一个工作日程安排高度严格的工业社会将重视准时,等等。——为什么文化会获胜

担当责任

我们在 BNEF 有 3 个团队,我们共享一个 Jenkins 自动测试平台。可以预见有一个很大的 Jenkins 维护任务,而我选择负责这个任务。这意味着要弄清楚如何做事情,安排会议讨论改进和替代方案,并且最后协调实施。


但是,当我选择负责这个任务时,我对要做的这些事一无所知。我只是觉得很有趣。


我在我们的群聊中发信息沟通我想出的替代方案。这个沟通很快就没音信了,可能是因为每个人都在忙些什么。我有一种“我不知道我现在该做些什么”的感觉。所以我决定继续我的其它冲刺任务。


我这时的本能反应是“哦,好吧,我试过了。总有一天有人会回信,然后我们就可以继续沟通”。我扮演了负责人的角色,但是并没有负起责任。


当我意识到这点时,我很惊讶。这是一种非常糟糕的管理方式。


每个人都在忙事情,那是他们正在考虑的事情,而不是我的事情。所以,我有责任来将他们的注意力转到要沟通的问题上。


在最初聊天的两天后(我用这段时间来反思并发现自己错了),我再次发信息解释我的决定,以及将分配哪些工作给哪个团队。这是我第二次惊讶的时候:每个人都同意了。并不是他们不在乎,而是他们在第一次聊天之后没有要补充的了。


我非常珍惜这次经历。它教会了我一些重要的习惯:经常跟进,而且如果你负责一项任务,那么推进这项任务就是你的责任。不要在其位不谋其政,而是要真正把事情做好:不管是授权做还是自己做。


这也强化了一个原始习惯:珍惜惊讶。惊讶是一种你的预期与实际发生的匹配的衡量。这是改变你的思维的一个绝佳机会。

拥抱担忧

好吧,最后一个故事。去年,我参与了一个失败的边缘项目。在那个项目中,我学了一种新语言、一种新的做事方式并且测试了一种产品假设。在那个项目中坚持下来真是出人意料的艰难——每次我想起那个项目都会感到害怕。


这种强烈的感受是我无法忽视的。它使我开始注意同样的微妙感受,特别是在工作中的。每当我遇到一项艰巨的任务并且我还不知道如何去做的时候,这种感受就会悄悄地回来。“啊,这要怎么搞?我完全没有头绪。”


我已经学会去拥抱这种感觉。这令我兴奋。这会告诉我将要学习什么东西。到目前为止,我已经开始在我的人体日志中跟踪这种感受——“我这周感到害怕了吗?”如果很多周的结果都是“否”,那我就过得太舒适了。


这种注意到大脑中正在发生什么的原始技能是一种非常强大的监测和诊断工具。就像定期检查系统健康的定时任务那样,复查并改善你的健康:精神上和身体上。这也是本文的目的:这是我的年度工作复查。

增加细微的差别

如果不在过去几年的章节中添加一些细微的差别,这篇文章就不完整。你可以通过这里的链接查看去年的文章

编码


在软件工程行业有一个有趣的习惯,即简单地从 Stack Overflow 复制代码。当新手工程师开始相信这个段子时,这是很危险的。当我们说“从 Stack Overflow 复制”时,正在发生的细节都丢失了。


这里有一个从 Stack Overflow 复制的示例。假设我要枚举一个 generator 的所有排列时。那么:


  1. 这不是一个代码面试,所以我可以寻找库来帮我实现。但我还不知道使用哪个库。

  2. 我在谷歌上搜索这个问题,然后发现可以使用itertools.permutations([1,2,3,4])来生成一个列表的所有排列。

  3. 好吧,太棒了!所以现在我将 generator 转化成一个列表,复制这段代码,并传入这个列表。我就做完了。


现在,我们假设产品需求是按字典顺序对这些进行排序。所以我写了一个处理二阶列表的排序函数。


但是,它不起作用。我发现permutations返回了一个元组列表,因此我返回我的排序函数,并将它改成一个处理元组列表的排序函数。


过了一会儿,产品又有了新需求:这些排列太长了,而且我们想让处理的速度更快一点儿。无论这个列表多大,我们只需要长度为 4 的排列。


啊。好吧。由于我已经有了一个生成所有排列的函数,因此我使用这个函数并从每个排列元组中取前 4 个元素。我意识到这会导致重复,因此我将这些元组放到一个 set 中,然后应用排序函数来使它们按正确的顺序排序。


现在我又做完了。哦,这真是辛苦,但是嘿,每个人都有点开心!这个排列函数对于长列表还是很慢,因此我添加了一条日志,以便某个时候再来看看这个问题。


如果我花时间去查看itertools.permutations的文档,去理解它是如何工作的,我就会注意到:它有一个参数可以决定你想要的返回的排列长度。它返回一个元组列表。而且它返回的时候是排过序的。此外,输入参数不是一个列表,而是一个 iterable,因此我本可以传入 generator。无论如何,它都会被转换成一个元组,因此传入列表还是 generator 都没有关系。


这个例子可能看起来微不足道,但是这背后的思维机制却并非如此。我注意到,每当我遇到复杂的 API 和误导的命名时,这都会发生在我身上。


简而言之,我的规则是“我不写我不理解的代码”。就像“从 Stack Overflow 复制”的习惯一样,这条规则包含许多隐性知识,而这些知识在翻译过程中会丢失。例如,理解代码是什么意思?


至少有三个层次的理解:你可能完全理解itertools.permutations会产出什么,你可能理解它是如何工作的,或者在更深的一个层次,你可能理解它的实现决策为什么是那样的。


层次 1 是理解函数或 API 是做什么的。


层次 2 是理解它是如何实现的。


层次 3 是理解它为什么是那样实现的。


对于设计良好的 API 和你不想深入学习的东西,级别 1 就可以了。


然而,级别 1 是最低要求。层次 0 就是我们在上述例子中看到的,这是有问题的。另外一个例子是第一次复制现有团队模板,这介于层次 0 额层次 1 之间。


是的,这是一种权衡。层次 0 非常快,而达到层次 3 需要很多时间。


当我不复制粘贴现有模板时,我的速度就会降下来。但当我有足够空闲时,我选择在写代码之前达到层次 1 理解。这通常意味着我第一次的时候会很慢,但是随着时间的推移,我会变得更快。每次我都会加深一点儿自己的理解,而且这有助于我快速地解决 bug。我把学习放在完成事情之上。


有时我也会打破这个规则。一些情况下需要快速简单的修改方式。


有时候,开源文档很烂。这些时候,你需要层次 2 的理解来给你层次 1 的理解:你需要去阅读源码。每当我必须这么做的时候,我都记得为将来的我保留上下文。理解别人的代码是很难的,特别是如果它是用你不熟悉的语言编写的时候。最好不要重复做这种辛苦的工作。当你发现有些东西很重要,把它写下来——那就是需要评论的点。另外,你的团队会为此感谢你。这是一种建立力量倍增器的简单方法。


这很像“存”信息包。它们是你已经完成的工作单元,因此下次你不需要再做了。


理解层次也适用于你的团队拥有的代码,而不仅仅是你复制粘贴的代码,或者从其他人那里“继承”来的代码。理想情况下,你应该对你团队的代码有一个层次 2 的理解,对你自己的代码有个层次 3 的理解。这种理解构建了代码如何工作的思维模型


我发现,代码审核对于构建这种思维模型有很大帮助。我尽可能多地做代码审核:它使我能够跟进我的团队正在做什么。对此还有一种非常有意思的反馈机制。我可以通过我的审核评论判断我对代码的理解程度。我对代码库越不熟悉,我的评论就越无关紧要。随着我的思维模型的改进,我开始将系统看作一个整体以及新的部分是如何与其它部分交互的。我能在某个东西不生效时发现不协调的地方并找出来。当我这样做评论时,我就知道我的理解层次正在慢慢提升到层次 2-3。


由于代码总是在改动的,所以这是一个持续的过程:你对代码的理解会上下浮动,这取决于你接触的代码的占比。


另外一个获取层次 2-3 理解的理由是获取灵感。当你理解了一个新系统的代码,你找出他们做了哪些决定以及为什么这么做的决定。这增加了你的工作技能。这也是我深入钻研 Unix 并撰写关于Unix的工作原理的文章的一个重要原因。这也是你理解你所使用的工具的一个很好的理由,我就是因此而学习Git是如何工作的


总结:


  1. 不要写你不理解的代码

  2. 尽可能优先学习

  3. 为将来的你保留上下文

  4. 对你的团队的代码达到 2-3 的理解层次

  5. 代码审核有助于让你的思维模型保持与时俱进

测试

假设你构建了一个新系统,而测试发现它非常慢。你设计它的时候考虑了每个部件将会花费多长时间,但是看起来你的一些假设不正确。你接下来要做什么?


我将测量每个组件需要多少时间,来确定我在哪个部分可以产生最多的影响。有些事情确实是超出你的控制范围,比如请求延迟。你不可能去发射一个卫星来让你的代码运行得更快。测量时间并找出你可以改进的地方是非常重要的。


我试过大刀阔斧的改动,优化任何看起来对我不太理想的东西,例如将 dicts 转换成 sets——但最终的解决方案通常不会这么明显。Dicts 很可能不是你的请求会花费一秒多时间的原因。


测量而不是假设。


在去年的回顾中,我写到:


如果测试机器和部署机器之间有环境不匹配的地方,你就麻烦了。这就是部署环境的用武之地。[…] 这个想法是尝试捕获单元测试和系统测试中没有发现的异常。例如,请求系统和相应系统之间的 API 不匹配。


我以前不太热衷干净的测试环境,直到因此吃了苦头。说到干净,我的意思是它完全复制了你的生产环境。它使你能够准确地测试生产环境会发生什么。当然,你不需要一台物理机器,docker 就可以很好地完成这项工作。


我发现 docker 是测试效率最高的工具之一。它使我能够创造新的环境,进行本地测试,并减少偏差。这种快速的反馈回路使我能够更快地开发。让我等 5 到 10 分钟来检查我部署好了没有、触发一个测试、检查输出等等,是非常令人沮丧的。Docker 完成了所有这些功能,就在我的机器上。


我学到的最后一件事是优化零假阳性。编写并不是你真正想测的东西的测试是非常容易的。例如,遍历数据库游标并检查值?好吧,如果 iterator 什么都没有返回,你的测试不检查任何东西都能够通过。


这些都是假阳性,它们给了你一种错误的自信感。我如何修补这些呢?好吧,我首先要在代码评审时额外认真。其次,测试这个问题的肯定触发的方法是让你的测试失败。我将等于换成了不等于。如果它仍然通过了,那我就发现了一个问题。这就是我最近开始做的事情,一旦我发现了我的第一个假阳性测试。


总结:


  1. 对于优化问题,测量而不是假设。

  2. 拥有一个干净的预发布环境。容器化非常酷。

  3. 优化零假阳性。

设计

几乎每一个系统设计都关乎权衡。优秀的工程师会把这些权衡明确化。


这些权衡取决于我们和我们想要的产品的约束。


说到这里,需求和约束是不一样的。约束是现实世界的限制。例如,我们还不能在 1 毫秒内将信息从纽约发送到澳大利亚。还有一些产品约束,例如我们不希望用户在任何时候看到 3 个以上弹出窗口。


另一方面,需求是弹性的。需求是我们想要让发生的事情,但是通常我们不知道自己想要什么。问问自己“我到底想要做什么呢?”有助于揭示需求约束。通常,人们太快地投入到需求中——这只是从约束中选择的众多可能途径之一。所以,每当我感到需求不靠谱时,我都会回归约束条件,然后再去寻找替代性的需求。我从我的项目经理那里学习这一点——他非常棒!另外还从@shreyas的推特推文中学习。


没有什么神圣的设计能够总是奏效


在设计系统时,我注意到两个广泛的主题。


首先,我们发明的组件有限:队列、缓存、数据库和连接器(或者是让它们协同工作的代码)。每一个可能的设计都是这些组件的组合——每一个组件都表现出它们各自的权衡。有一些更快,有一些更易于维护,有一些扩展性更好,都取决于你的用例。


根据你的约束,一种安排会比其它安排更好。你的目标就是寻找到那种最好的安排。有时候,你可以通过一些绝妙的方法来降低复杂性,或者让事情变得更快。但是,基础设施不会变化。


其次,每个人都有一些快乐的主题可以回顾,他们已经在过去看到了很好的效果。这些都是观察系统的不同视角。设计就是要搞清楚哪种排列符合这个视角。


例如,我喜欢简化状态并保持简单。简化状态有助于我更好地理解系统,也有助于我更好地写测试代码。保持简单也是一样。两者都会导致更少的 bug。当然,不能太简单:这不能违反约束。


正如我去年说的,速度、本地化开发和测试都值得考虑。如果两种设计效果相同,但其中一种设计更容易本地安装和编写测试,那么我总是会选择更容易编写测试的设计。


我喜欢找出其他人的视角,并且尝试吸收我没有的视角。这也是我阅读技术博客的另外一个原因。


在设计时,保留上下文也是值得的,就像写代码时候一样。很多时候,我都会发现自己回顾很久时间之前的代码,忘记了我们当时的假设,然后想“卧槽,我们是为什么要这样做的?!”明确我们的约束和权衡,有助于保持正确的观点,并有助于判断你是否做出了正确的决定。


最后,在设计取代现有系统的系统时,我发现讨论迁移路径非常重要:我们将如何管理从旧系统迁移到新系统?


如果你曾经注意到一个系统,一半的东西运行在新代码上,另一半运行在旧代码上,那就是一条有缺陷的迁移路径。不考虑迁移路径会导致技术债务高筑:你现在不得不同时管理和维护新系统和老系统。有时,这是因为优先级切换,而你还停留在中间状态。无论哪种情况,这些异常都不会长久。


好的迁移路径可能会花费比较长的时间,考虑到它们留在系统内的状态的话。如果优先级改变,我们是否会陷入到什么都不能做的状态?或者我们的迁移是增量的吗,即使改变优先级也能保持稳定运行?当然,增量迁移并不总是正确的解决方案。有时候,彻底地迁移会更容易一些。其中重要的一点是沟通好:我们不能处理这种迁移的优先级变更。


总结:


每个系统设计都关乎权衡。


每个设计都有有限的技术组件。


人们进行设计时都有明确的视角,就像思维模型。


在设计时保留上下文:写下你的约束和权衡。


当取代老系统时,有一个明确的迁移路径。

收集需求

根据上述主题,收集需求实际上就是收集约束。正如我们上面所见的,需求有时是将约束转变成技术需求,这并不总是正确的前进方向。


在我的团队文化中,团队和项目经理之间有充足的信任,我们可以随意挑战彼此的意见。直接问问题就好了。


问题清单在这里很有效。这里链接有一些我经常问的一些问题


最后一节将深入讨论一些问题,一些我曾经做错的事情,以及对所有做对的事情的总结。

一些对我来说很好用的小诀窍

  • 尽可能多地做代码审查。你错过的越多,你对代码的心理模型就越错误,你设计新东西时需要花费的时间就越多。

  • 斟词酌句:第二个问题一般就是问“你是怎么发现 X 的?”,而 X 就是你第一个问题的答案。

  • 第一个审核我的 PR 的人是我自己。而且总是这样。我很喜欢这样做。这是我从写作中学到的:第一阶段写明主旨要点,第二阶段进行段落编辑。这和写代码很像。代码审核就是编辑阶段,而且对我的代码进行代码审核也会让我更好地编写代码,发现不一致的地方,并知道其他人是如何进行代码审核的。

超能力

就像在电子游戏中,你可以获得一些力量。这些有助于你在现实世界获得力量。就像在电子游戏中,你需要进行任务才能获得这些力量。


下面是我发现的一些可能需要通过的任务。


  • 当文档不全时阅读源代码

  • 任务:阅读开源代码。

  • 为你正在查看的代码快速构建思维模型

  • 任务:阅读开源代码。

  • 拥抱恐惧

  • 任务:构建一个辅助项目。

  • 足够自信,敢于表现无知

  • 任务:克服成长之路上的第一个常见问题。

  • 定义自己的属于。让人们明确知道我在谈论什么。就像我几周前在《Idea Muse》文章中提到的:“大多数时候,大部分人都不知道自己在谈论什么。”

  • 任务:???

成长路上的一些常见问题

就像工程师喜欢包含常见问题的文档一样,我认为人们喜欢阅读关于成长之路上常见问题的文章——我发现自己犯的错误,然后改正了的。

有时候,我觉得我需要知道所有问题的答案

当我明白的事情越多,更多的人会向我问问题。这感觉棒极了!然而,肯定有一些问题我不知道答案。在这种情况下,靠着感觉并且自作聪明是一个陷阱。这个陷阱会阻碍我们学习。


如果我说不知道,人们会停止向我问问题吗?很可能是这样的。


而且,他们无论如何都会找出答案,因为他们也很能干和聪明。如果被这种问题困住是该有多愚蠢?


敢于表达无知的自信是一种超能力。


我磨练这项技能的一个好方法是,当我没有什么要补充的时候,就说“没什么要补充的”,而不是重复别人说的话。这让我感到很强大。我从查理芒格那里学到这个方法。

有时,我会失去冷静

有时候,我会陷入恐慌和沮丧的状态。我不再理性地思考问题,尽可能写些垃圾来解决问题。添加一个调用,添加一个括号,打印一些随机的东西,只是让事情以某种方式可以运行。当我修改某个事情花费的时间超出了预期时,我就会开始进入这种状态。


比如下面这个具体的例子。我参与对我们新构建的一个队列系统的测试,我想要模拟饥饿和竞争的队列消费者。因此,我决定在测试中生成几个线程,都运行消费者,这些线程将运行 5 秒钟,在队列中竞争一条消息。我预期只有其中一个线程会得到这条消息(这是我们实现的队列定义)。而且我预期这些线程都不会崩溃。


在这个测试中,我给每个线程设定的join超时时间是 5 秒。这些测试不起作用。我尝试手动模拟,一切都会顺利运行。但是使用线程,有时候测试会失败。我想不通其中的问题。我尝试了所有我能做的随机的事情。在一个绝望的时刻,我重新安排了测试的顺序。我这样做的时候感到很有趣,这怎么可能有帮助呢?结果,第一个测试又通过了,而另一个之前通过的测试开始失败。


那时候,我发现自己失去了冷静,在尝试一些没有意义的随机事件。我冷静下来,开始调查线程中在发生什么。结果是,join只会等待,即使超时也不会终止进程。terminate()才是终止进程的方法。如果我花时间仔细阅读了文档,我就不会觉得那么沮丧了。


这些线程没有被终止,而且这些遗落的线程会扰乱接下来的测试。


通常,这种情况发生在我比较匆忙的时候,当我没有保护我的空闲,结果就是我没有将学习放在做事情之上。其它时候,是因为代码比较难,没有触手可得的解决办法。


只要我注意到自己这样做,我就会自己从中解脱出来。我会从应急性的 bug 修复转变为策略性的 bug 修复。

新鲜事物

把优化学习放在做太多事情上很容易。例如,为了尝试一种新技术而做出错误的设计决定。多亏了我们的团队文化,我能控制住自己。我们互相质疑彼此的决定,并意识到当我们没有充分的理由来解释它时,就会有一种潜在的欲望——我们之后再把它搞明白。


我做这件事的具体方法:当找出一个设计的优点和缺点后,我会明确提出“这学习起来很酷”,因此这种意愿不会再被脆弱的理由隐藏。


因为一些正确的理由而做决定,而不尝试新的东西


向团队技术栈增加一项新的技术是一个重大决定,不能轻易决定。

问题

为了扩展去年的清单,我还有一些没有找到答案的问题。我会在今年继续深入思考这些问题。


  1. 你如何构建一个促进 X、Y、Z 的文化?

  2. 你如何判断文化契合度?当事情自下而上构建时,很难做自上而下的预测。

  3. 我觉得字斟句酌自己的语句是另外一种超能力。那就是高效沟通+沟通正确的事情。我怎么做才能磨练这项能力?

  4. 软件工程中有哪些开放性的问题?


还有一些去年的问题,我还觉得需要进一步思考


  1. 如何处理代码文档和工作流?

  2. 进一步探索去风险(De-risking)。降低项目风险的所有策略有哪些?

  3. 如何降低系统降级率?


我第一年时间在尽自己的最大努力吸收我所能获得知识。我没有足够的知识来看系统,我只能看到部分。今年,我以上帝视角来查看这个系统。我找出了一些不太理想的部分并着手改进。我查看系统的其它部分,吸收他们的最佳实践,并对那些不太适合我的实践保持警惕。


随着时间的推移,我开始从我做对的事情上总结经验,而在我意识到之前,其他人就已经开始把我看做是一名高级软件工程师了。


我爱死工程学了。


作者介绍


Neil Kakkar 目前在Bloomberg LP, London写代码。在 Bloomberg 之前,曾在 ShareChat 实习,这是一个印度地区性的内容发现平台,增长曲线非常快速。在 ShareChat 之前,曾在 Google。喜欢工程学和心理学,工程学帮助我更好地与电脑和其它实物玩耍,而心理学帮助我更好地与人类玩耍。另外比较喜欢关于系统的设计和推理。几乎所有东西都可以建模为一个系统。


原文链接:


https://neilkakkar.com/things-I-learned-to-become-a-senior-software-engineer.html


2020-11-03 09:152033
用户头像
陈思 InfoQ编辑

发布了 576 篇内容, 共 278.9 次阅读, 收获喜欢 1301 次。

关注

评论 2 条评论

发布
用户头像
我们的目标不是编写代码,而不是做一门能盈利的生意。
2020-11-04 09:40
回复
用户头像
文章是好文章,但翻译的某些地方读着有点摸不着头脑
2020-11-03 15:12
回复
没有更多了
发现更多内容
高级软件工程师成长秘诀_架构_Neil Kakkar_InfoQ精选文章