Link: https://mitchellh.com/writing/building-large-technical-projects
摘要
本文作者分享了他构建大型技术项目的方法,核心在于通过持续看到实际成果来保持动力并完成项目。他指出,许多项目在初期充满热情,但随着时间推移,由于缺乏可见的进展而逐渐失去动力。作者的方法是:将大型任务分解为小块,确保每一步都能产生有形的进展,并以此为基础安排工作。他以自己的终端模拟器项目为例,详细阐述了这一过程。
具体而言,该方法包括:
- 选择合适的起点:避免设定过于宏大的初始目标,而是选择能尽快看到结果的“现实”子项目,即使是后端或非UI部分,也可通过自动化测试来验证进展。
- 快速迭代至演示:目标是构建“足够好”的子组件以推进到下一个演示,而非追求完美。频繁(每周1-2次)进行可运行的演示,这不仅能提供动力,还能带来宝贵的产品反馈。
- 为自己构建:对于个人项目,优先构建自己需要的功能,并尽快将软件投入日常使用。通过实际使用发现问题并迭代,这种“自用”模式能提供强大的动力和明确的任务。
- 总结为五步流程:分解问题、解决“足够好”的部分以推进演示、频繁构建演示、优先实现自用功能、根据需要迭代改进。
作者强调,这是一种高度个性化的方法,其有效性在于它与他“通过看到结果获得强烈动力”的个人特质相契合。他认为每个人都应找到适合自己的健康激励方式。
内容精简
构建大型技术项目的方法:通过可见成果驱动
在构建新项目、实现大型功能或进行大规模重构时,保持动力并最终完成大型技术项目往往充满挑战。作者分享了一种对他非常有效的方法:通过持续看到实际成果来保持动力,并以此为基础安排工作。
挑战与解决方案的核心 我们都曾体验过启动新项目时的兴奋感,最初几周迫不及待地投入工作。然而,随着时间推移,这种热情会逐渐消退,项目可能因此停滞不前。作者发现,当他将大型任务分解成小块,并确保每一步都能看到有形的进展时,他更有可能完成工作并全程保持兴奋。虽然每个人的激励方式不同,但作者认为,工程师们普遍会被一个好的演示所激励,而他的目标就是不断为自己创造这样的“好演示”。
作者坦言,他所分享的并非新颖理念,其中包含许多已知的软件工程或管理实践的元素。他只是分享了自己处理大型技术工作的方式及其背后的原因,并以他最近的终端模拟器项目为例进行具体说明。他强调,本文旨在帮助那些“更想完成项目”的人,而非指责未完成项目的人。
起步:选择现实的起点 项目的开始阶段往往是最困难的,作者有时会花费数小时甚至数天来纠结正确的起点。以他的终端模拟器为例,他知道最终需要许多大型组件,如终端解析、shell进程管理、字体渲染、网格渲染、输入处理等。如果他最初的目标是“一个能运行Neovim的可启动终端”,这个目标会显得过于庞大,容易让人失去兴趣。
相反,作者会思考一个“现实”的项目,即能“尽快看到结果”的项目。一旦应用这个筛选条件,可行的子项目数量会急剧减少。例如,他可能会选择:
- VT解析:解析终端转义序列。
- 空白窗口渲染:打开一个窗口并绘制一个空白画布。
- 子进程启动:启动一个子shell(如bash),设置TTY并能读取其输出(如初始shell提示符)。
在这个阶段,作者不会枚举所有大型子项目,他只是大致了解项目的“粗略形态”,然后找到一个可以独立构建并能实际看到某种结果的子项目。经验在此阶段尤为重要,经验丰富的工程师能更有效地描绘项目的大致形态,更准确地识别子组件并理解它们如何协同工作。
早期成果:利用自动化测试 早期工作往往“不可见”,这使得看到有形结果变得困难。例如,如果选择开发终端的VT解析器,在没有UI的情况下很难“看到”它的工作。同样,如果开发数据库模式或最小API,也需要编写客户端或CLI/GUI才能看到效果。
如果初始子项目是UI,当然可以很快看到结果。但作者通常从后端开始,最终都会遇到类似挑战。克服这一阶段的最佳工具是自动化测试(通常是单元测试)。自动化测试允许你运行代码并验证其功能,同时也是良好的开发习惯。这为选择最初的任务提供了另一个指导点:如果它不是图形界面,就选择一个易于测试且无需太多麻烦就能看到结果的任务。
对于终端项目,作者选择从VT解析开始,因为他对这部分了解不多,而且它非常容易测试:提供示例字符串输入,期望解析出相应的动作或事件输出。看到“1个测试通过”、“4个测试通过”、“13个测试通过”等进展,对作者来说是巨大的激励,因为这意味着他编写的代码正在运行,并且正在大型项目的关键子组件上取得进展。
冲刺演示:不让完美成为进步的敌人 作者早期子项目的目标不是构建一个“完成的子组件”,而是构建一个“足够好”的子组件,以便他能继续推进到下一个“演示”阶段。这种权衡不仅体现在功能上,也体现在算法或设计考量上。例如,你可能知道未来需要使用真正的数据库或复杂的数据结构,但对于初始工作,可以使用内存内容、内置数据结构(如字典),并要求所有输入/输出都预先提供。
作者强调:“不要让完美成为进步的敌人。”更进一步,不要让那些你“知道将来必须进行”的改进阻碍你前进。目标是尽快达到一个演示。无论从事什么项目,作者都尝试每周构建一到两个演示,并结合自动化测试反馈。
构建演示还能提供宝贵的产品反馈。即使功能不完整,你也能快速直观地判断某个东西“感觉好不好”。这些并非“最小可行产品”,因为它们可能还不可用,但它们足以提供工程师宝贵的自我反思。作者认为,经验有时反而会成为障碍,他见过资深工程师为了构建完美的东西而陷入泥潭,等到他们做出演示时,却发现“它很糟糕”——不是实现糟糕,而是产品或功能本身糟糕。
在终端项目中,VT解析是第一个任务。早期阶段,作者只看到自动化测试的结果。为了达到第一个演示,他编写了一个shell脚本,运行命令,捕获输出,将其输入到VT解析器,并输出所有已解析(或未解析)的内容。随着时间推移,他将这个CLI作为他的第一个“UI”进行迭代,使用ASCII字符渲染终端网格。这给他带来了巨大的满足感,因为他可以运行man
、ls
或vim
等程序,并看到解析器工作(或出错,这同样令人兴奋)。在这种情况下,他编写的CLI从长远来看相对无用(很快就被废弃了),但花一两天时间构建它作为演示,提供了重要的进展感,并“看到”东西工作有助于保持动力。
为自己构建:将软件融入日常使用 这一部分更适用于个人项目。即使你渴望发布软件供他人使用,也要“只构建你需要的,当你需要时才构建”,并“尽快采纳你的软件”。作者表示,他总是更有动力去解决自己正在经历的问题。如果一个为你设计的产品对你不起作用,那么它很可能对其他人也作用不佳。因此,从演示到实际可用产品的路径是:找到最短的路径,只构建他认为自己需要的功能。
对于终端项目,这意味着首先要能加载他的shell配置(fish),然后能启动并使用Neovim。所以他所有的工作都只围绕着实现这些功能:只处理这些程序使用的转义序列,只渲染他日常使用的字体等。他最初省略了许多功能,如滚动、鼠标选择、搜索、标签页/分屏等。
然后,他开始将自己的终端作为日常驱动器使用。这一步通常会有几次“假启动”;你会意识到你实际上需要一些你省略或忘记的功能。在他最初使用终端时,他发现方向键不起作用,存在细微(但会破坏工作流程)的渲染错误等。于是他会暂时放弃使用,但这为他提供了接下来要处理的具体任务。此外,使用自己编写的代码的软件总能给他带来巨大的自豪感,这通常有助于他保持继续工作的动力。
总结流程 作者将他的方法总结为以下五个步骤:
- 将一个大问题分解成小问题。重要的是,每个小问题都必须有清晰的方式让你看到工作成果。
- 只解决小问题到足以推进大问题的演示层面,然后继续下一个小问题。
- 只解决足够多的小问题,以便开始构建可运行的软件演示,然后继续迭代更多功能。尽可能频繁地进行演示。
- 如果适用(个人项目、解决自己实际问题的公司项目等),优先实现能让你采纳自己软件的功能。然后继续首先解决自己的问题。
- 根据未来改进的需要,回过头来迭代每个组件,并根据需要重复此过程。
结论 这就是作者构建大型技术项目的通用模式。他将这种模式应用于个人项目、团队项目、工作项目、学校项目等,并以此保持动力。他指出,他没有提及很多事情,例如发布(shipping),因为他认为项目成功不一定需要发布,且发布对他而言是一个过于宏大的事件,无法提供长期动力。他也没有提及工具(Git工作流、CI等),因为他的过程可以适应各种既定的流程。这表明这是一种高度“个人化”的过程。作者认为每个人都需要找到一种健康的方式来强化自己的动力。他意识到看到结果能强烈地激励他,因此他围绕这一点建立了自己的工作风格,并且迄今为止效果良好。
要点
本文的核心思想是通过持续的、可见的成果来驱动大型技术项目的完成和保持动力。
一、核心理念与问题
- 问题:大型技术项目容易因缺乏可见进展而导致动力丧失。
- 解决方案:将大任务分解为小块,确保每一步都能产生有形的、可观察的进展。
- 关键驱动力:通过频繁的“好演示”来激励自己和团队。
二、项目启动阶段
- 避免宏大目标:初始目标不应过于庞大,例如“一个能运行Neovim的终端”。
- 选择“现实”的子项目:
- 能尽快看到结果。
- 可独立构建。
- 示例:VT解析、空白窗口渲染、子进程启动。
- 经验的作用:经验丰富的工程师能更准确地识别子组件和项目形态。
三、早期成果的实现
- 挑战:后端或非UI工作成果通常不可见。
- 解决方案:
- 自动化测试(单元测试):作为主要工具,通过测试通过的数量来验证进展。
- 选择可测试的任务:优先选择易于测试且无需复杂设置的任务。
- 示例:VT解析器通过输入/输出测试来验证。
四、冲刺演示阶段
- 目标:构建“足够好”的子组件,而非“完成的子组件”,以推进到下一个演示。
- 核心原则:“不要让完美成为进步的敌人。” 不因未来已知改进而停滞。
- 权衡:初期可使用简单实现(如内存数据、内置结构),而非复杂方案(如数据库)。
- 演示频率:每周尝试构建1-2个可运行的演示。
- 演示价值:
- 提供动力和成就感。
- 提供宝贵的产品反馈,直观判断“感觉好不好”。
- 避免资深工程师因追求完美而导致产品方向错误。
- 示例:为VT解析器构建一个简单的CLI作为第一个“UI”,即使它最终会被废弃。
五、为自己构建(尤其适用于个人项目)
- 原则:
- 只构建自己需要的功能,按需构建。
- 尽快将软件投入日常使用(“采纳”)。
- 动力来源:解决自身问题能提供强大动力。
- 迭代方式:通过日常使用发现缺失或有缺陷的功能,然后进行迭代改进。
- 自豪感:使用自己编写的软件能带来持续的动力。
- 示例:终端项目优先实现加载shell配置和运行Neovim,然后日常使用并发现问题(如方向键、渲染bug)。
六、总结的五步流程
- 分解问题:将大问题分解为小问题,每个小问题都必须有清晰的可见成果。
- “足够好”的进展:只解决小问题到足以推进大问题的演示层面,然后继续下一个。
- 频繁演示:只解决足够多的小问题,以便开始构建可运行的软件演示,并尽可能频繁地进行演示。
- 优先自用功能:如果适用,优先实现能让你采纳自己软件的功能,并首先解决自己的问题。
- 迭代改进:根据未来改进的需要,回过头来迭代每个组件,并根据需要重复此过程。
七、结论与个人化
- 该模式适用于多种项目类型(个人、团队、工作、学校)。
- 强调这是一种高度“个人化”的过程,其有效性在于与作者“通过看到结果获得强烈动力”的个人特质相契合。
- 未提及发布、工具链等,以突出其作为个人激励过程的本质。
问答
Q1: 作者构建大型技术项目的核心方法是什么? A1: 核心方法是通过持续看到实际成果来保持动力,并以此为基础安排工作,频繁进行可运行的演示。
Q2: 为什么许多大型项目会半途而废? A2: 许多项目在初期充满热情,但随着时间推移,由于缺乏可见的进展和成就感,动力会逐渐消退。
Q3: 在项目开始阶段,如何选择第一个任务? A3: 应选择一个“现实”的子项目,即能尽快看到结果、可独立构建且易于测试的任务,避免设定过于宏大的初始目标。
Q4: 如果早期工作(如后端开发)不可见,如何看到进展? A4: 主要通过自动化测试(尤其是单元测试)来验证代码功能和进展,通过测试通过的数量来获得成就感。
Q5: “不要让完美成为进步的敌人”在项目开发中意味着什么? A5: 这意味着在早期阶段,目标是构建“足够好”的组件以推进到下一个演示,而不是追求完美或一次性实现所有已知改进,以避免陷入泥潭。
Q6: 为什么频繁进行演示对项目很重要? A6: 频繁的演示不仅能提供强大的动力和成就感,还能提供宝贵的产品反馈,帮助工程师直观判断产品方向是否正确。
Q7: 作者建议如何利用“为自己构建”来推动项目进展? A7: 作者建议优先构建自己需要的功能,并尽快将软件投入日常使用。通过实际使用发现问题并迭代,这种“自用”模式能提供强大的动力和明确的任务。
Q8: 作者总结的构建大型项目的五步流程是什么? A8: 1. 分解大问题为有可见成果的小问题。2. 解决“足够好”的部分以推进演示。3. 频繁构建可运行的演示。4. 优先实现自用功能。5. 根据需要迭代改进每个组件。
Q9: 作者认为他的方法是通用的吗? A9: 作者认为这是一种高度“个人化”的过程,其有效性在于它与他“通过看到结果获得强烈动力”的个人特质相契合。他认为每个人都应找到适合自己的健康激励方式。