构建大型技术项目的方法


无论是从头开始建立一个新的项目,实现一个大的功能,还是开始一个大的重构,要保持动力和完成大型技术项目都是很困难的。对我来说,一个非常有效的方法是不断看到真实的结果,并以此为基础来安排我的工作。

当我把大型任务分解成几块,从而看到切实的进展时,我往往能完成工作,并在整个项目中保持兴奋。
人们都有不同的动机和驱动力,所以这可能对你不适用,但作为一个广泛的概括,我没有发现一个工程师不会因为一个好的演示而感到兴奋。而我们的目标是永远给自己一个好的演示。

在这篇文章中,我将以我的终端仿真器项目为例,以便有现实的、具体的经验可以分享。


起跑线
最初,你有一些大型项目,你必须弄清楚如何开始。对我来说,这是最困难的部分,我可能会花几个小时--有时是几天--在正确的起始点上犹豫不决。

对于我的终端模拟器来说,我知道如果我打算完成这个项目,就必须有一些大型的组件:终端解析、运行和管理shell进程、字体渲染、网格渲染、输入处理(键盘/鼠标)等等。在通往 "完成 "的道路上,有数百个相对较大的子项目。

如果我最初的目标是看到一个可以运行Neovim的可启动终端,我就会有大麻烦了。即使有未知的未知数,这个目标听起来也太大了。我可以直观地意识到,这条道路上有很多组件:渲染GUI、进程启动、终端解析和状态管理。这是一个糟糕的目标,它太大了,我可能在一两个月后就会失去兴趣。

相反,我试着思考什么是现实的项目,我可以尽快看到结果。一旦你应用这个过滤器,可行的子项目的数量就会急剧减少。这里有一些例子:

  • VT解析--解析终端的转义序列
  • 空白窗口渲染--打开一个窗口,画一个空白画布
  • 子进程lanching--启动一个子shell,如bash、zsh、fish,设置TTY并能从它那里读取输出(即初始shell提示)。

在这个阶段,我并不试图列举所有大的子项目。我只是想了解项目的大致情况,并找到一个我可以独立完成的项目,同时也能看到一些实际的结果。

这是一个经验帮助最大的阶段。拥有更多经验的工程师通常能够更有效地描绘出一个项目的大致形状。他们可以更准确地识别各种子组件,并看到它们是如何组合在一起的。如果经验不足,或者在一个我不熟悉的领域,我只是采取最好的猜测,并期望有更大的可能性在某些时候把我的工作扔掉。

早期成果
早期的工作往往不是很明显,这使得看到切实的成果显得很困难。例如,如果我选择为我的终端进行VT解析,如果不同时连接某种用户界面,我就无法看到它的工作。或者在其他项目中,如果我选择了一个数据库模式和最小的API,那么如果不写一个客户端和一个CLI或GUI,我也同样看不到它的工作。

如果你选择的最初的子项目是一个用户界面,那么你当然可以很快看到一些结果由于各种原因,我很少先启动前端,通常先启动后端。而在任何情况下,你最终都会走到后端,达到类似的挑战。

走过这个阶段的最好工具是自动化测试(在这个阶段通常是单元测试)。自动测试让你实际运行一些代码,看到它在工作,也有很好的卫生方面的好处。

这为你挑选头几个任务提供了另一个指导点:如果它不是图形化的,你要挑选一些可测试的东西,不要太大惊小怪,这样你可以看到一些结果。

对于我的终端,我决定先从VT解析开始,因为它是当时终端的一部分,我对它不太了解,而且它感觉是我可以非常容易地测试的东西:给它一些字符串的输入例子,期望一些解析的动作或事件作为输出。

看到 "1个测试通过"、"4个测试通过"、"13个测试通过 "等等的进展,对我来说超级兴奋。我正在运行我写的一些代码,它正在工作。我知道我正在一个更大的项目中的一些关键子组件上取得进展。

从冲刺到演示
我对早期子项目的目标不是建立一个完成的子组件,而是建立一个足够好的子组件,这样我就可以在通往演示的道路上继续做下一件事。

这种权衡不只是体现在功能上。它可能体现在算法或设计的考虑上。例如,你可能知道在未来,你需要使用像真正的数据库或花哨的数据结构或支持流数据。但对于最初的一套工作,你可以只使用内存中的内容,内置的数据结构,如字典,并要求你所有的输入/输出在前面。

我认为这是一个重要的权衡,所以我将重复这一点:不要让完美成为进步的敌人。更进一步说,不要让你知道你必须要做的未来改进阻止你继续做下一件事。我们的目标是完成一个演示。

无论我在做什么工作,我都会尝试每周制作一到两个演示,并与上一节中解释的自动测试反馈相混合。

建立一个演示还可以为你提供宝贵的产品反馈。你可以迅速直觉到某样东西是否感觉良好,即使它还没有完全发挥作用。这些不是 "最小可行产品",因为它们确实不可行,但它们足以为工程师提供一些宝贵的自我反思。

为自己建造
本节将更多地适用于个人项目而不是工作分配的项目。即使你渴望为他人发布一些软件,也只在你需要的时候构建你所需要的东西,并尽可能快地采用你的软件。

我总是更有动力去解决我自己正在经历的问题1。而且,如果一个为你设计的产品对你来说不起作用,那么它很可能对其他人也起不了作用。因此,我从演示到实际可用的产品的路径是找到最短的路径,只建立我认为自己需要的功能。

对于我的终端来说,这意味着首先能够加载我的shell配置(fish),然后从那里能够启动和使用Neovim。所以我把我所有的工作都限定在所需的功能上:只有那些程序使用的转义序列,只有渲染我日常使用的字体,等等。我最初省略的功能例子:滚动、鼠标选择、搜索、标签/分页等。

然后我开始使用我的终端作为日常驱动。这一步通常会有一些错误的开始;你会发现你实际上需要一些你省略或忘记的功能。在我最初使用终端的时候,我发现我的方向键没有任何作用,有一些细微的(但破坏工作流程的)渲染错误,等等。所以我就去放弃使用它,但它给了我接下来要做的具体任务。

此外,我在使用自己写的代码的软件时总是感到非常自豪,这通常有助于保持我继续工作的动力。

总结:

  • 将一个大问题分解成小问题。重要的是,每个小问题都必须有一些明确的方式让你看到你的工作成果。
  • 只有解决了小问题,才能在大问题的一个演示方面取得进展,然后再继续下一个小问题。
  • 只解决足够的小问题,以便能够开始建立你的软件的可运行的演示,然后继续迭代更多的功能。尽可能频繁地制作演示。
  • 如果适用的话,优先考虑使你能够采用你自己的软件的功能(一个个人项目,一个解决你实际问题的工作项目,等等)。然后继续首先解决你自己的问题。
  • 回过头来,根据需要对每个组件进行迭代,以便在未来进行改进,根据需要重复这个过程。