15 年前我给自己的一系列编程建议

我终于感觉自己是一个像样的程序员了,所以我觉得写一些建议会很有趣,比如“什么能让我更快地达到这一点?”我并不是说这对每个人来说都是很好的建议,只是对我来说这是个好建议。

我无法告诉你我在一个团队中待过多少次,并且系统存在一些很容易搞砸的事情,但没有人想过如何让犯这种错误变得更加困难。

当我进行 iOS 开发时,我们使用 CoreData,并在一系列视图中订阅存储中的更改。订阅回调来自触发更改的同一线程。所以有时那是主线程,有时是后台线程。重要的是,在 iOS 开发中,您只能在主线程上进行 UI 更新。因此,新的更改处理程序可能工作正常,但当有人从后台线程触发更改时,或者如果您稍后添加 UI 更新,它就会中断。

这是人们坦然接受的事情,而且经常在团队新人评审中出现。然后偶尔会漏掉,我们会添加一个 DispatchQueue.main.async 当我们看到事故报告时。

我决定修复它,并花了十分钟更新我们的订阅层以在主线程上调用订阅者,这消除了一整类崩溃并减轻了精神负担。

我并不是想说“看看这些白痴,他们没有修复代码库中一个明显的问题,这对我来说很明显”,因为任何人只要花几分钟思考一下,就会发现这个问题很明显。这些问题会持续很长时间,因为没有合适的时间来解决它们。当你刚加入团队时,你不会试图改变任何大的事情,所以你可能会觉得这很奇怪,但你不应该去改变一堆你还在学习的东西。然后当你在团队里待了一段时间后,这些问题就会逐渐淡出人们的视线。

这是一种心态的转变。当这些事情发生时,你只需要偶尔提醒自己,你有能力让你和你的团队的生活更轻松。

评估你在质量和速度之间做出的权衡,确保它适合你的环境

实施速度和对正确性的信心之间总是存在着权衡。所以你应该问问自己:在当前环境下发布错误是否合适?如果这个问题的答案不会影响你的工作方式,那么你就太不灵活了。

在我的第一份工作中,我从事的是数据处理方面的绿地项目,该项目拥有良好的系统来追溯重新处理数据。发布错误的影响非常小。对这种环境的正确反应是稍微依赖护栏,尽可能加快速度。你不需要 100% 的测试覆盖率或广泛的 QA 流程,这会减慢开发速度。

在我的第二家公司,我开发了一款数千万人使用的产品,其中涉及大量高价值的财务数据和个人身份信息。即使是一个小错误也需要事后分析。我以蜗牛般的速度发布功能,但我认为那一年我可能没有发布任何错误。

通常情况下,你不会在第二家公司工作。不过,我见过很多开发人员在这种编程方面犯了错误。在错误不是任务关键的情况下(例如 99% 的网络应用程序),快速交付和快速修复错误比花时间确保在第一次尝试时交付原始功能更能让你走得更远。

花时间磨斧头几乎总是值得的

你将要重命名事物、进行类型定义、查找引用等 很多;你应该能很快地做到这一点。你应该知道编辑器中的所有主要快捷键。你应该是一个自信而快速的打字员。你应该很了解你的操作系统。你应该精通 shell。你应该知道如何有效地使用浏览器开发工具。

我已经知道人们会在评论中这样说:“你不能花一整天时间调整你的 neovim 配置,有时你也需要砍掉大树”。不过,我认为我从未见过有人真的做得过分;我在新工程师身上看到的最大绿旗之一是在选择和熟练使用他们的工具时要小心谨慎。

如果你不能轻易解释为什么某件事很难,那么这就是偶然的复杂性,可能值得解决

我职业生涯中最喜欢的经理习惯于在我说某件事难以实现时向我施压。通常他的回答是“这难道不是在我们 Y 时发送 X 的问题吗”,或者“这难道不是就像我们几个月前做的 Z 一样吗?”我想说的是,这是非常高层次的反对意见,而不是我们正在处理的实际函数和类的层次,我试图解释这一点。

我认为传统观点认为,管理者简化这类事情很烦人。但当他逼迫我时,我意识到,我所解释的大多数复杂性都是偶然的复杂性,而这种偶然的复杂性往往是可以解决的。这不仅会使当前的问题变得微不足道,还会使未来的变革变得更容易。

尝试更深一层地解决错误

想象一下。仪表板中有一个 React 组件,它处理 User 从当前登录用户的状态中检索对象。您会在 Sentry 中看到一个错误报告,其中 user 曾是 null 在渲染过程中。您可以添加快速 if (!user) return null或者你可以进一步调查,发现你的注销函数进行了两次不同的状态更新——第一次将用户设置为 null,第二个重定向到主页。你交换这两个,现在没有组件会再出现这个错误,因为用户对象永远不会 null 当您在仪表板内时。

继续进行第一种类型的错误修复,最终你会陷入混乱。继续进​​行第二种类型的错误修复,你将拥有一个干净的系统,并对不变量有深刻的理解。

不要低估挖掘历史来调查一些错误的价值

我一直很擅长调试奇怪的问题,用常用的工具包 println 和调试器。所以我从来没有真正查看过 git 来找出 bug 的历史记录。但对于某些 bug 来说,这至关重要。

我最近遇到了一个问题,我的服务器似乎一直在泄漏内存,然后被 OOM 杀死并重新启动。我无论如何也找不到原因。所有可能的罪魁祸首都被排除了,我无法在本地重现它,感觉就像蒙着眼扔飞镖一样。我查看了提交历史记录,发现在我添加了对 Play Store 付款的支持后,它开始发生。我一百万年也不会去查看这个地方,这只是几个 http 请求。结果发现,在第一个访问令牌过期后,它陷入了获取访问令牌的无限循环中。也许每个请求只会增加 1kB 左右的内存,但是当他们在多个线程上每 10ms 重试一次时,这会很快累积起来。通常这种事情会导致堆栈溢出,但我在 Rust 中使用了异步递归,它不会堆栈溢出。这个 绝不 我本会想到,但当我被迫研究一段我所知道的特定代码时 必须 导致了这种情况,突然就出现了这个理论。

我不确定这里的规则是什么,什么时候该做,什么时候不该做。它基于直觉,与触发此类调查的错误报告不同,是一种不同的“嗯”。随着时间的推移,你会发展出直觉,但只要知道,如果你陷入困境,它有时是无价的,这就足够了。

类似地,尝试 git bisect 如果问题是可以解决的 — — 这意味着 git 历史记录中有小的提交,可以通过一种快速自动化的方式测试问题,并且你知道有一个提交是坏的,还有一个提交是好的。

糟糕的代码会给你反馈,而完美的代码则不会。编写糟糕的代码是错误的

编写糟糕的代码确实很容易。但编写完全遵循最佳实践、具有 100% 测试覆盖率、经过模糊测试和变异测试的代码也确实很容易——你的初创公司会在你完成之前耗尽资金。因此,很多编程工作都在寻找平衡。

如果你过于追求快速编写代码,那么你偶尔会陷入严重的技术债务。你会了解到诸如“我应该为数据处理添加出色的测试,因为通常很难或不可能在以后纠正”或“我应该认真考虑表格设计,因为在不停机的情况下更改内容可能非常困难”之类的内容。

如果你在编写完美代码方面犯了错误,你就不会得到任何反馈。事情总是需要很长时间。你不知道自己把时间花在了真正值得做的事情上,以及在浪费时间。反馈机制对于学习至关重要,但你却没有得到反馈。

要清楚的是,我并不是说“我记不起创建哈希图的语法,所以我改用两个内循环”,而是说:

我没有重写数据提取来使这个特定状态无法表示,而是在几个关键检查点上对我们的不变量添加了几个断言

我们的服务器模型与我们要编写的 DTO 完全相同,因此我只是将它们序列化,而不必编写所有样板,我们可以根据需要稍后编写 DTO

我跳过了为这些组件编写测试,因为它们很简单,而且其中一个错误也没什么大不了的

使调试更加简单

多年来,我掌握了许多让软件调试更简单的小技巧。如果你不努力让调试变得简单,那么随着软件变得越来越复杂,你将花费大量时间来调试每个问题。你会害怕做出改变,因为即使是几个新的错误也可能需要你花一周的时间才能解决。

以下是我所指的一些示例:

  • 对于 Chessbook 的后端
    • 我有一个命令可以将所有用户的数据复制到本地,因此我只需一个用户名就可以轻松重现问题
    • 我使用 OpenTelemetry 跟踪每个本地请求,这样可以很容易地了解请求的时间消耗情况
    • 我有一个临时文件,它充当伪 REPL,每次更改时都会重新执行。这样就可以轻松地提取代码片段并对其进行操作,以更好地了解正在发生的事情
    • 在暂存环境中,我将并行度限制为 1,以便更容易直观地解析日志
  • 对于前端
    • 我有一个 debugRequests 阻止乐观加载数据的设置,以便于调试请求
    • 我有一个 debugState 设置将在每次更新后打印出程序的整个状态,以及更改内容的明显差异
    • 我有一个文件,里面充满了一些小函数,可以使 UI 进入特定状态,这样当我尝试修复错误时,我就不必一直单击 UI 来进入该状态。

保持警惕,看看你的调试时间有多少花在了设置、重现和事后清理上。如果超过 50%,你应该想办法让它变得更容易,即使这次会花更长的时间。错误应该得到 更轻松 在其他条件相同的情况下,随着时间的推移进行修复。

在团队中工作时,你通常应该问以下问题

有各种各样的情况,从“试图自己解决所有问题”到“用每个小问题烦扰同事”,我认为大多数刚开始职业生涯的人都偏向前者。你身边总有人在代码库里呆的时间更长,或者比你更了解某项技术,或者更了解产品,或者只是总体上更有经验的工程师。在某个地方工作的前 6 个月里,有很多次,你可能花一个多小时来弄清楚某件事,或者你可能在几分钟内就得到答案。

提出问题。唯一会让人厌烦的情况是,如果你显然可以在几分钟内找到答案。

发货节奏非常重要。认真思考如何才能快速、频繁地发货

创业公司的发展空间有限。项目有截止日期。当你辞去工作开始创业时,你的积蓄只能维持几个月。

理想情况下,项目开发速度会随着时间的推移而不断提高,直到交付功能的速度超出您的想象。要快速交付,您需要做很多事情:

  • 不易出错的系统
  • 团队之间周转时间短
  • 愿意削减 10% 的新功能,因为这样会占用 50% 的工程时间,并且有远见知道这些部分是什么
  • 一致的可重复使用模式,您可以将它们组合在一起以用于新的屏幕/功能/端点
  • 部署快捷简单
  • 不会减慢您速度的流程;不稳定的测试、缓慢的 CI、繁琐的 linters、缓慢的 PR 审查、JIRA 等。

发货缓慢应该像生产中断一样值得事后反思。我们的行业不是这样运作的,但这并不意味着你不能亲自遵循快速发货的北极星。

Leave a Reply

Your email address will not be published. Required fields are marked *

近期新闻​

编辑精选​