原文Theo Leggett - 2024.03.12

引言

10 PRINT "HELLO"
20 GOTO 10

(注:这段代码是 BASIC 语言,无限循环打印 “HELLO”)

1984 年的 4 月,我的父亲为自己的家庭办公室,购买了一台电脑,一台 Luxor ABC-802,配备了 Z80 CPU、64KB 的 RAM,一块黄底黑字屏幕,可以设置为 80x25 的文本模式,或者大约 160x75 像素的图形模式,以及两个软盘驱动器。它的 ROM 内置了 BASIC 语言,但并没有任何游戏。如果我想玩它,就必须学习如何编程,编写自己的游戏。我学习了 BASIC,然后在接下来的几年里,又学习了 Pascal、C 等更多的语言。我找到了自己的热情。虽然当时只有 14 岁,但我已经知道,长大后想做什么了。

当我在学习如何编程的时候,我认为真正理解计算机如何工作、编程语言如何工作、以及各种工具如文本编辑器如何工作是非常重要的。我想磨炼我的技艺,编写出尽可能优秀的代码。但我错了。

这篇文章是希望在我学会编程基础之后,能被告知内容的提炼。然而,有人亲自告知,或在杂志文章中告知,到 25 岁的时候,我就会因太老而不能再做程序员了。我必须尽快学习尽可能多的算法、数据结构和语言。毕竟,编程是年轻人的游戏,需要能够熬夜,每晚都熬夜,比其他人写出更多的代码,这是唯一的成功方式。结果证明,这些都不是真的:既不是关于年轻,也不是关于缺少睡眠,尤其不是关于性别。

在我写这篇文章的时候,距离第一次编写计算机代码的日子已经快四十年了。我一直以开发软件来维持生计,现在每天也都在写代码,没有其他我更愿意以此为生的事情了。我不能指出巨大的成功和令人印象深刻的壮举,但希望在这个行业中生存了几十年的我,有足够的资格来谈论软件开发。

这篇文章讨论了我关于如何成功构建软件所学到的一些事情。这些都是从自己的经验中学到的;我不是研究者,很少引用来源,这在很大程度上并没有得到证据的支持。我基于自己的经验写这篇文章,如果你有不同的看法,那也没关系。

我在这篇文章中的目标是让读者思考,研究,学习,深思。目标不是告诉读者如何思考,思考什么,事物是如何的,或是给出构建软件过程中各个方面的每个问题的答案。

核心技能

有趣且有意义的软件,超出了任何一个人在合理的时间范围内独自构建的能力。这意味着,构建软件的基本、关键、核心技能是沟通和协作。

仅仅了解计算机如何工作,如何使用编程语言,了解算法和数据结构,或者如何使用涉及到的各种软件构建工具是不够的。你还需要知道如何与其他人交谈,了解需要构建什么样的软件,它必须做什么,接受的努力程度是多少,如何管理工作,以及许多其他的事情。你需要知道如何与他人合作,共同建立一个比任何一个人都伟大的东西。如果你和你的团队能做得好,那么它将会大于你们的总和。团队合作可以是一种力量倍增器。

这些都是难以学习的技能。我发现它们比构建软件的任何技术部分都要难。

即使在我是唯一参与者的项目中,也至少涉及到三个人:“过去的我”、“现在的我”和“未来的我”。

  • “过去的我”是一个懒散且粗心的人,总是留下一团糟。
  • “现在的我”做得非常好,但是必须应对“过去的我”做的所有愚蠢的事情,并需要安抚“未来的我”。
  • “未来的我”是一个自负且固执的势利小人,对他来说,没有什么是足够好的。

让这“三个人”和谐相处,以便完成任何事情,对我来说是一种持续的挑战。我通过日记来应对,在日记中写下做了什么,为什么做,结果如何。我试图写得让自己能理解,即使我很累,或者刚经历了一次《黑衣人》式的短期记忆消除,也计划明确的迭代,有具体的任务。我使用 GTD 系统来追踪还需要完成的事情。

关于生产力

对于软件开发来说,生产力的含义并没有明确定义:时间容易测量,但产出却不是。大多数人至少对生产力对他们意味着什么,有一个模糊的概念。然而,有一些已知的因素会影响生产力——这些通常被忽视了。

要做好工作,你需要照顾好自己。你必须睡得好,必须吃得好。如果你累了,或者压力太大,你就会犯错误,做出错误的决定。这些会导致你做出糟糕的工作。这不是一个道德问题,但你可能必须修复自己的错误。如果你做好工作,你会更加享受生活和工作。

Hillel Wayne 讨论过睡眠,并且有相关的资料。推荐阅读:

你还需要有一个环境来帮助你做好工作。环境是否安静?有没有打扰?家具是否舒适,长期使用会不会伤害你?能否获得必需品,比如厕所和茶?能否从工作区域休息一下?能否出去散步:散步有助于思考。这个列表并不完整。

此外,你需要以适合你的方式管理自己和工作。如何做这一点取决于你是谁,你的偏好和经验,以及你做的工作类型。没有一种解决方案能够始终适用于所有人。

对我自己来说,我发现应用 David Allen 的Getting Things Done系统效果很好,但无论你做什么,都需要知道要达成什么目标,今天和在不久的将来,你需要安排事情,以便可以一次专注于一件事。然而,请注意,我使用 GTD 系统是为了知道何时可以休息或者偷懒;我并不打算一直尽可能地提高生产力。

跟踪所做的事情可能是值得的。反思这些可以给你一种成就感,并让你看到取得的进步,这可以是一个动力和士气的提升器。

对于计算机来说,多任务处理是没问题的,但你的大脑一次只能思考一件事,并且在切换上下文时效率极低。

关于团队治理

当一个团队进行协作时,他们需要早期建立的一件事就是治理:基本上,团队是如何做决策的,又是如何改变决策的?决策的范围从根本性的到日常的:谁在团队中?它是一个民主制度,还是等级制度,或者是其他的结构?谁有发言权?团队应该喝什么饮料?网站应该使用什么字体?

治理是困难的,但当它明确时会更容易。对责任和权力的不确定性,会导致混乱和争吵,这些都可能会导致团队分裂。

所有的团队最终都会有冲突。管理这些和解决差异,理论上是管理层的工作,但实际上它落在每个人身上。这需要一些技能,可惜的是,这些技能似乎很少被教给程序员。

推荐阅读:

关于政治和伦理

软件开发始终是一个政治和伦理行为。每当你在构建软件时,都必须做出无数的决定:

  • 软件应该做什么?
  • 使用它将需要什么资源?
  • 它应该如何被使用?
  • 使用它将需要什么能力?
  • 哪些事情容易做?
  • 哪些事情难以做?
  • 软件会赋予其用户权力,还是迫使他们为他人的利益做事?

所有这些决定都有后果,这些后果将有利于某些人而不利于另一些人。因此,它们具有需要考虑的政治和伦理方面。

举个例子,如果你制作的东西可以用一部便宜的手机使用,你就使世界上的大多数人都能使用它。如果你使它需要昂贵的硬件,你就排除了很多人,尤其是贫穷的人。有时这是不可避免的,是你构建软件解决问题的固有部分,但它仍然是一个选择。

对于特定的决定,可能没有正确或错误的选择,但总有其后果。你必须考虑它们,并决定是否可以被接受,以及对谁来说可以接受。

总的来说,你构建的软件将反映出一些价值观,确保你知道并同意这些价值观。

推荐阅读:

关于伦理和软件自由

自由和开源的软件是伦理类型的软件。对于那些生活受到计算机使用影响的人们来说,软件自由是必不可少的,无论他们是直接使用计算机,还是有人使用计算机做对其他人有影响的事情。

自 1986 年,我首次在 BBS 上阅读 GNU 宣言以来,就一直在编写自由软件。(“开源"是其同义词:我并不关心两者之间的差异。)

我职业生涯的大部分时间都涉及到构建开源软件。我对自由和开源软件的偏好是显而易见的,并且有据可查。

在过去的三十年中,已经产生了大量的文本,讨论了自由和开源软件与其他类型的软件的伦理问题,以及许可证的具体问题。我在这里不打算对这些论点进行总结,甚至不会推荐阅读。

我想明确我的立场,以防读者关心。然而,这篇文章的很少部分,包括伦理这个主题,与软件自由有关,并同样适用于非自由软件的开发。

关于多样性和质量

人权是至关重要的。善待他人是正确的行为。无论你做什么,这都是最重要的优先事项。如果你不同意这一点,我对你没有希望。

在软件开发的环境中,我发现构建高质量软件最关键的事情是,让参与其中的人有思维的多样性。项目中引入的思维方式越多样,决策就越可能适应更多的环境,对更多的人有用,处理更多的问题。

思维的多样性来自于不同背景、过着不同生活的不同类型的人。有时候差异在外部可见,有时候则不可见。

当有疑虑或者面临选择时,认真考虑与你不同的观点。如果因为他们与你不同而排斥他们,你很可能会做出糟糕的选择。

多样性并不能保证成功。没有什么能保证成功。然而,一致性保证你得到的是片面的答案。有时候这就足够了,但往往并非如此。

在多元化的环境中,协作和沟通确实可能更具挑战性。但不要害怕这一点。将挑战视为学习的机会,以提高你的能力。

人们会犯错误,你也会犯错误。当别人犯错误时,采取宽容和善良的态度是一个好的策略,并期望别人也能对你做出同样的回应。因错误而惩罚他人将会让你孤立无援。

关于维护

在软件行业中,人们普遍认为,软件生产的大部分成本,来自于初始发布后的所谓维护阶段。初始开发可能需要一两年,而维护将需要数十年。

人们可能会认为,这会导致开发实践、架构决策和其他所有事物都被优化,以降低维护成本。不幸的是,经济和其他激励机制往往倾向于相反的方向。在大多数公司中,视野的范围往往只有一个季度,最多一年。现在投入更多的努力以便在长期内节省大部分努力,这种做法通常被认为是不可接受的。

即使对于个人的业余项目,人们通常也有强烈的冲动,尽快让事情运作起来,而不是花时间做好事情,但至少这是一种内在的冲动,而不是外部的压力。即便如此,最终的结果是相同的:当发现错误、需求变更,或者软件需要适应周围系统或生态系统的变化时,软件更难进行后续的修改。

我并不是唯一看到这个问题的人,也不认为这个问题可以被解决,除非经济激励机制发生根本性的改变。当有可能的时候,个别开发人员需要尝试悄悄地降低维护成本,这可以被视为一种预防性的游击式维护。

项目的重大问题

我发现,在每个新项目开始时,回答一些问题是重要的,同时也是有帮助的。答案不必是最终版,随时都可以改变它们:如果在学到新事物时不调整你的思维,那你就不擅长你所做的事情。答案甚至可以是无聊的,只要它们是诚实的。问题的重点是让你开始思考这些问题。

  • 你为谁构建这个软件?谁的意见对它有影响? 这告诉你项目中的一些利益相关者。后期可能会有更多的利益相关者,但这是一个开始。了解谁是利益相关者,可以让你专注于他们的需求,这有助于项目的成功。 利益相关者可能是最终用户,也可能是 CEO。如果你认为是最终用户,但实际上是 CEO,你会做出让 CEO 不满的决定,项目会失败,你不会理解原因。反之亦然。
  • 你为什么要构建这个软件? “这是我的工作”可能是一个非常实际的答案。这个答案意味着,如果获得金钱的机会减少,你需要重新评估一切。不同的答案可能更多的是关于激情或使命,这意味着你对不断变化的环境的反应会有所不同。这里没有错误的答案,但一定要诚实,至少对自己诚实。
  • 从大体上来说,软件应该做什么?还有,不应该做什么? 这就是要明确你到底是在开发一个文字处理器还是一个电子表格。这与详细需求或验收标准无关。
  • 从大体上来说,软件应该如何运行? 同样,这是大局观。你是在制作一个命令行工具,一个网络服务,一个操作系统,一个移动应用,还是其他什么?硬件的类型和尺寸是什么?
  • 什么是重要的,什么是值得拥有的? 这涉及到需求和验收标准。你需要知道它们才能构建出好的东西。将它们写下来,同时,你还需要记录下如何验证你是否达到了这些目标。

规划和估计

对于远期的详细规划通常很困难,而且大多数时候都会失败。这包括估计工作需要多长时间,我避免这样做。

规划和估计并非无用,也不应被忽视。在我认为现实可行的范围内,会进行规划和估计。具体来说,我发现几乎不可能制定超出一两周迭代的详细计划。我所说的详细计划是指,以一次可以完成的粒度来确定任务。

通常会发生一些令人惊讶的事情,破坏了几周后的计划。可能是管理层改变优先级,或者客户改变主意,或者公司破产,或者被收购。可能是其他什么事情,但通常总会发生一些事情。如果一个迭代周期很短,那么即使你的所有规划都要被抛弃,你也不会损失太多。

如果你需要改变方向,也可以更容易地做出反应。

我强调下,在这里谈论的是详细规划。对下个月或下个季度要做什么进行规划是可以的,只要所有参与的人都知道这些计划可能需要改变,并且计划保持在较高的层次。

一个类比:如果计划横穿全国的公路旅行,你可能会想要规划要经过哪些城市。你可能不想规划在哪加油或吃饭。你要有足够的灵活性,以便在出现事故、天气变化或其他可能在路上出现的意外时改变路线。

对于大型软件项目,迭代化的开发方法是有效的,别的方法似乎都不管用。至少我从未见过其他任何方法有效。

对我来说,如果迭代有焦点,效果会更好。有一个明确、清晰的目标有助于减少范围蔓延。它集中工作以达到迭代目标,而不是在一时看起来是个好主意的事情上。

当我为一个迭代进行规划时,我会将事情分解成可以在一次坐下来完成的小任务。我的任务估计分为四个桶:长达一刻钟、一整个小时、四小时或更长。我更喜欢短一些的任务:它们更容易估计,更容易做,而且通常引起的问题更少。任何太长的事情都需要进一步分解。

有些任务是无法估计的,调试就是一个典型的例子。你需要多长时间才能意识到是网线坏了,或者操作系统更新破坏了某些东西,或者找到其他一些未预见问题的原因?调试会发生,你最好在计划中留出足够的富余来应对一些故障排查。

干扰和打断也会发生。有时你别无选择,只能立即对它们做出反应和回应,为此要留出空间。

用文字表达自己

人的记忆是短暂且易错的,会忘记细节,会忘记重要的事情,会错误地记住事情。当合作时,你和其他人会对所说的、达成一致的和决定的事情有不同的意见。

我曾经参加过一个会议,有四个人,还有 CTO。CTO 禁止我们做笔记:看来那周的流行趋势是,做笔记使会议浪费时间,会议持续了两个小时。之后,我们四人对已经决定的事情有大约八种不同的意见。从未采取过任何后续行动。

把事情写下来。这看起来像是一个愚蠢的琐事,乏味、无聊、陈旧,但这是组织记忆的方式。把事情写下来是一种容易被忽视的超能力。

你的团队应该记录会议纪要,至少要涵盖重要的决策,以及会议后每个人应该做什么行动。保持它们足够简短,以便容易写和阅读。在所有人都可以随时查看的地方存档它们。这对那些没有参加的人有帮助,可能是因为他们在度假,或者是一年后才加入的。

顺便说一句,学习如何进行有效的会议。对我来说有效的方式是提前设定议程,并提供支撑材料。为会议做准备,这样你就不会浪费每个人的时间。有一个主持人保持讨论的进程,并设定时间限制。有效会议的过程可能有所不同,但你应找到一个适合自己的。

关于工作的实践

在进行更改时,一次只做一个更改。如果可以,将你正在进行的更改分解成更小的部分。小的更改更容易理解,也不太可能造成灾难性的后果。

自动化减少摩擦:运行测试,制作发布版本,打包,交付,部署等。尽可能早地做这件事。设置一个 pipeline,在其中你可以进行更改,并确保软件仍然可以工作,有意愿的用户可以开始使用更新后的软件。这条 pipeline 越顺畅,构建软件就越容易。

对每个项目使用版本控制。通过经常将变更合并到开发主线,来实践持续集成

开发一个信任的自动化测试套件:如果测试通过,可以发布版本,或者部署到生产环境。这通常意味着在项目早期就开始使用测试套件。确保自动化测试覆盖你关心的所有方面:目标是确保你给别人的东西,按照预期的方式工作。一起更新生产代码和测试代码。

连续运行测试套件多次,以识别不稳定的测试。不要容忍不稳定的测试。

进行压力或负载测试,即使你只是以简单的方式进行。在你这样做之前,你不知道你的软件和系统是否能够处理 10,000 个并发用户,我之前的没有。

在我过去的一份工作中,有一个系统在生产环境中良好的运行了一段时间,然后将其部署到一个流量更大的客户那里。我们的软件失败了,问题原来是用尽了 TCP 端口号:我们为每个传入的消息发出一个 HTTP 请求,但没有复用底层的 TCP 连接。流量如此之大,所有可能的端口号都被占用,然后一切都停滞了。修复的方法是复用 TCP 连接,这个更改只需要大约一行代码,然后一切都正常工作了。如果我们做了即使是最简单的负载测试,也会自己发现这个问题。

如果可能,自己切实地使用你的软件,这样会更好地理解用户。

观察其他人使用软件,会更好地理解你的软件的难用之处。真实的故事:对于那些没有参与软件开发的人来说,如果软件对同一件事有三个不同的名称,使用软件是很困难的。看到有人因此挣扎,这让人痛苦不已。

把测试人员当作是帮助你发现错误的朋友,他们不是要羞辱你。没有人是完美的,从不犯错误,忽视细节,或忘记整个功能特性。不要问我怎么知道的。

编码建议

简单、明确的代码更容易编写,更容易工作,更容易理解,更容易在不破坏它的情况下进行更改。尽可能简化事物,但不要过度。只有当你证明性能提升值得时,才在性能的祭坛上牺牲简单性。

你以为的朋友,复杂性,其实是你的敌人。

耦合和内聚仍然是重要的概念。

在每个项目中,概念清晰度都很重要。同样,保持你的术语一致。混乱潜伏在清晰度隐藏的地方。巨大的混乱带来巨大的烦恼,以及必然会有的 bug。

用户需要快速开发迭代,但对于开发者来说并非总是如此,因为过于匆忙会留给思考的时间太少。功能性有时可能会成为一个误区。

推荐阅读:

职业发展

你可以选择成为某个非常具体事物的深度专家,或者成为一个通才,或者某种混合,做出明智地选择,或许没有任何选择是错误的,但每一个选择都有其对应的结果。

要谦虚,做保姆,不做奶奶,人们可能更尊重强大的女巫,但他们更喜欢善良的女巫。

要开放和诚实,公平对待他人。你不必相信因果报应就能让它发挥作用,因此,你应该通过做好事来让它为你带来好的结果,而不是做坏事让它对你产生负面影响。

帮助并提升他人。但同时,不要让他人滥用或利用你,你不需要接受废话。设定你的界限。

当你需要帮助,或者陷入困境时,寻求帮助。当有人提供帮助时,接受它。

我不是谈论职业发展的合适人选,但当我做到以上的事情时,事情通常都会朝着好的方向发展。

要点

照顾好自己。睡觉,吃饭,锻炼,休息,放松,尽你所能照顾他人。人是最重要的,软件只是有趣的。

致谢

感谢 Brennen Bearnes、Richard Braakman、Tyler Cipriani、Greg Grossmeier、Hackhörnchen、Soile Mottisenkangas、Daniel Silverstone 和 Enrico Zini 等人审阅了这篇文章的草稿,其中你的任何错误都属于我。