1. 先别急着开新坑,看看手头积灰的项目到底卡在哪
如果你混过技术社区,一定见过“Ask HN: What are your unfinished projects?”这种帖子。每次出现都能收获几百条回复,从半夜写了一半的编译器,到只跑通过一次的爬虫脚本,再到连 README 都没写的开源库,什么都有。这个提问能火,不是因为大家喜欢晒失败,而是它戳中了一个程序员普遍存在但很少认真面对的事实:几乎每个人手里都有几个没做完的项目,而且数量往往比你愿意承认的要多得多。
这篇文章不完全是在回答“你有哪些没做完的项目”,而是想聊一个更实际的问题:这些项目为什么会烂尾,烂到一半的项目还有没有救,以及下次再开新项目时,怎么避免让同样的事情再发生一遍。
我不打算写一堆“坚持就是胜利”的鸡汤。我自己电脑里就躺着至少四个超过一年没碰的项目。其中一个是从零手写一个轻量级消息队列,当时已经实现了基本的发布订阅,结果因为换工作、换电脑、依赖环境变乱,加上后来看到成熟方案越来越多,就彻底停在那里了。另一个是给本地 Markdown 文件做全文索引的小工具,功能已经能跑了,但 UI 丑到我自己都不想打开。
说出来不怕丢人,这些项目停更的真实原因并不是“代码太难写”,而是我在关键节点上做了错误决策。这也是我最想在下面展开聊的:项目未完成,很少是技术问题,更多是优先级、范围和内部动力的管理问题。
先给结论:没做完的项目可以分成三类,第一类是已经学不到东西、删了也不可惜的,第二类是核心功能完成 80%、补个收尾就能用的,第三类是做着做着发现方向本身有问题、需要重做的。不同类别处理方式完全不同。最怕的是把所有未完成项目都装进一个“迟早要做完”的筐里,最后变成长期的自我消耗。
2. 先想清楚一个项目算“完成”,还是算“跑通了”
在讨论怎么处理未完成项目之前,有个更基础的问题值得先掰扯清楚:你觉得“完成”的定义是什么?很多人停更不是因为项目没有价值,而是因为一开始就把“完成”定义得太大,大到永远够不着。
比如说,你写了一个自动整理下载目录的 Python 脚本。脚本能跑,能按文件类型和日期把所有文件归好类——这时候项目其实已经是一个“能用”的状态了。但很多人的真实想法是:“我还想给它加个 Web 界面”“我还想做成 Windows 服务”“我还想支持自定义规则”“我还想打包成 exe 发给朋友用”。
需求本身没有错。错的是把这些需求全部放进“完成”的定义里。你以为自己在做一个下载目录整理脚本,实际上你给自己造了一个永久开发不尽的工具平台。最后脚本躺在硬盘里,连基础功能都没享受到。
这种情况非常典型。项目做到一半放弃,往往不是因为能力不够,而是因为验收标准里塞进了太多属于“以后再说”的东西。
所以处理未完成项目的第一步,不是打开编辑器继续写代码,而是重新定义“完成”:
- 对脚本类工具,完成 = 能处理我的典型输入,且输出可读。
- 对开源库,完成 = 核心接口具备明确使用方式,配套一两个示例。
- 对练手项目,完成 = 关键知识点已经实践过一遍,且留下笔记。
- 对产品化项目,完成 = 有一个可用版本,而不是一个完整的商业闭环。
如果你手头那个未完成项目,撤掉那些附加的“以后再说”需求,剩下的核心功能已经能用了,那它就不是烂尾,而是处于“功能完成、包装未完成”的状态。这种项目只需要花一两个周末收尾就行。
反过来,如果一个项目连基础功能都没跑通,或者跑通了但你对它已经没有好奇心,不再想打开相关文档,这种情况更多要从“是否继续”的角度去判断,而不是硬逼自己收尾。
我自己习惯用三个问题判断一个半成品项目是否继续投入时间:
- 如果今天把这个项目删掉,我会不会明显觉得可惜?如果答案是“不会”,说明这个项目本身对你的价值已经趋近于零,留着只是心理安慰。
- 这个项目目前承担的“学习价值”是否已经被消耗完?手写消息队列的功能我已经学完了,剩下的大部分是工程打磨,这个打磨过程对我当前工作帮助不大。
- 要不要再设一个至少两小时能完成的小里程碑再停?如果没有,宁愿停在这里。给每个项目留下一个“可以继续也可以封存”的干净状态,比永远开着一堆烂摊子更好。
3. 为什么项目会烂尾,常见原因和真实信号要对应起来看
大部分人会把项目没做完归因于“懒”“没时间”“不够自律”。我在社区帖子和自己经历里看到的真实原因,其实更具体,也更有迹可循。下面这几条是把未完成项目分类整理后比较高频出现的原因,每条后面我会补上对应的“真实信号”。
表格可以帮你快速判断自己的项目到底属于哪种情况:
| 常见归因 | 真实原因 | 真实信号 |
|---|---|---|
| 没有时间 | 实际是在其他项目里学不到新东西,动力断档 | 每次打开项目前都要重新看文档回忆背景 |
| 代码太乱写不下去 | 需求一直在变,缺乏一个小范围冻结线 | 功能越来越多,但从没正式跑通过一版完整流程 |
| 被更好方案取代 | 想挑战问题的部分已经被解决,剩余只是体力活 | 逛 GitHub 时发现自己想做的功能已经有人实现,且做得更好 |
| 搬砖太累没精力 | 项目交付标准被定得无限高 | 每次给自己定了大版本目标,但一个周末根本完不成 |
| 不知道怎么继续 | 缺少“下一步可执行动作”的拆分习惯 | 项目停的地方没有一个具体的、能被检验的阶段性成果 |
把原因拆到这一步,处理方法也会清晰很多。
3.1 动力断档型项目:已经学完了,该停就停
这类项目往往是“教科书式”的练手项目。比如你为了学 PyTorch,照着文档手写了一个图片分类 Demo。模型训练完,准确率打个八九十分,你顺手贴了个截图发到朋友圈。然后呢?没有然后了。因为你想学的东西已经学到了。
这不是失败。这是项目完成了它的历史使命。
如果你把“学完一个技术点”也算作一种交付,那你对这些项目的愧疚感会小很多。真正的问题不是中途停掉,而是停下来之后没有留下一份“项目结论”:我当时用它验证了什么方法、走了哪些弯路、如果继续做下一步会做什么。写这段结论比继续给项目加功能重要得多,因为它能把已经学到的东西沉淀成可复用的经验。
3.2 需求蔓延型项目:尝试单枪匹马对抗所有边界情况
有个识别特征很典型:这种项目的问题列表不是 Bug,而是“要不要支持……”。你今天想支持 Windows,明天想支持 macOS;今天想支持 PDF,明天想支持 DOCX;今天想低内存运行,明天想支持 GPU 加速。每一条需求单看都合理,但合在一起就是一个无底洞。
如果你现在正在做的项目已经陷入“什么都要支持”的状态,建议做一次需求大扫除。把需求分成四类:
- 现在必须做:核心链路,缺失则无法使用。
- 现在不做:没有它,当前场景也能凑合。
- 等有人用再说:目前只有你自己在用,不叫“用户反馈”。
- 直接砍掉:做出来只会增加维护成本,没有实际价值。
我记得自己有一个文件批量重命名工具,本来只支持按时间戳命名。后来想支持正则提取、Excel 映射、自定义模板,越做越复杂。最终冷静下来后,我只保留了“时间戳 + 序号 + 原文件名关键字”这一个组合,把其他方案全部砍掉。那个周末就发布了一版,后面再也没有大改过。
3.3 被替代型项目:别为了沉没成本续命
开源社区有一个经典现象:一个新框架火了,你立刻想到“我用它能做个同款”。做了一两周,发现某个成熟项目已经把所有坑都填完了,你的实现根本追不上。
这时候最不划算的决定,是给自己找一堆借口继续写下去,只因为“都写了这么多了”。
如果成熟方案已经能覆盖你的需求,那就直接转用成熟方案。你写的代码付出就算了,不叫浪费,叫试错成本。至少你知道了那类方案的技术难点在哪里。
但有一种被替代型项目是建议你换个方向继续的:如果成熟方案能覆盖 80% 场景,而你真正想做的是那 20% 的细分场景,那你要做的不是“做一个完整方案”,而是“做一个成熟方案的补充模块”。后者要写的内容会少一个数量级,实际被使用的概率也会大很多。
3.4 标准过高型项目:学一下“做一半也能用”的发布观
这个问题其实在编程新手身上很常见,但资深工程师也会犯。表现形式是:不做完最后一个功能、不加完所有注释、不写完全部测试,就不愿意让别人看到这个项目。
我见过一个朋友做开源项目流程图工具,整整一年都没有在社交平台发过链接。他说每次打开项目都会发现有细节没调好,比如节点连线的弯曲程度、明暗主题下的配色、拖拽时的吸附效果。他说等这些都做完再发布。
一年之后,市面上出现了两三个非常成熟的可视化工具。他这个项目彻底失去发布窗口。没发布过的项目不能算“完成了”,只能算“存在于本地”。
更好的做法是:第一版先只支持最基础的功能,UI 丑一点没关系,文档就写一段,核心逻辑能跑就行。发布出去,让三五个人真的用一下,再根据真实反馈迭代。未完成项目的反义词不是“完成度 100%”,而是“在任何环节都没有对外输出过价值”。哪怕只是自己写一篇文章记录当时踩过的坑,也算一种收尾。
3.5 卡在未知问题型项目:把“不知道怎么办”转化为“下一步试验”
有些项目停得更可惜:核心流程已经通了,但剩下一个稳定复现的未知问题。比如某个特定输入会导致内存溢出,或者并发超过某个数量就丢消息。卡了一段时间没有解决,项目就搁置了。
这种情况技术含量往往比较高,但也很容易陷入“试图一次解决所有可能原因,但没有任何一个原因被验证”的状态。我的建议是不要直接进入调试状态,先把问题拆成一个一个假设,然后每个假设设计一个最小验证实验。一个下午验证完所有假设,能定位就修,定位不了也把实验过程和结论记录下来,方便以后继续排查。
4. 别急着继续写代码,先按这套流程给项目做一次“资产盘点”
处理多个未完成项目时,最忌讳一上来就开始给每个项目加功能或搭新框架。你需要的不是更多代码,而是先知道自己到底有哪些项目、每个处于什么状态、每个还值不值得继续。
这套盘点流程可以当成季度清理或年度清理的固定动作。我自己已经跑过三遍,效果还不错。
4.1 把项目全部列出,按状态归类
先把电脑里所有个人项目列成清单。判断状态时不要看代码行数,也不要看上次提交时间,只看一个指标:它当前是“能跑”还是“不能跑”。
能跑是指打开项目你能在不失忆太多的情况下,用一条命令或一个脚本把它跑起来,产生预期的输出。不能跑则可能卡在依赖、环境、文件结构或文档缺失上。很多人以为自己是在维护一个项目,实际上每次打开都在重新考古。
建议把项目放进三组:
- A 组:值得收尾,而且距离可用状态不远。这类项目最有弹药价值,优先处理。
- B 组:项目本身有价值,但需要先补环境重建和文档,把“能跑”的基础找回来。这组要控制数量,比如整个季度只挑两个。
- C 组:已经学完、被替代、或不再感兴趣。这类项目在清单上标注一个简短结论之后从活跃列表里移除。
4.2 记录每个项目的“最后状态”和“恢复成本”
只列项目名还不够,必须为每个项目记录两样东西:
- 最后停留的位置:代码写到哪一步、数据从哪来、输出长什么样。
- 恢复成本:把它重新跑起来大概需要多久,是 15 分钟、半天,还是一天以上。
这两个信息的价值在于:它能告诉你“哪些项目值得紧急封存”。如果一个项目恢复成本已经高到超过它剩余功能的价值,那直接补一个 README 当历史项目封存即可。如果一个项目最后状态写得很清楚,恢复成本低,那它才有资格进入“继续收尾”的池子。
4.3 给出明确的取舍建议,别让所有项目继续挤占注意力
盘点完以后,你会发现一个很常见的事实:90% 的未完成项目其实不值得花一个周末来收尾。它们只是精神上的安慰剂,让你认为自己还有很多事情在做。你需要做的是把它们从“未完成”的状态里释放出来,给它们落一个“明确终止”或“长期封存”的盖章。
我自己有一条经验法则:
- 一个项目如果两周没打开,先给它写一次“当前状态”再关掉。
- 一个项目如果两个月没打开,就把它从默认工作目录移除,放进专门的 Archive 文件夹。
- 一个项目如果两年没打开,它对你的未来大多数时候已经没有意义。保留仓库,但别再占用计划表。
真正值得投入时间的,是那些你愿意把它从 Archive 里移回活跃区的项目。这种项目极少,但当你找到时,你会比一口气开十个新坑要快乐得多。
5. 如果确实想把某个半成品做完,记住这三条加速收尾的方法
不是所有积灰项目都需要放弃。有些项目就差一个周末,就能从不能见人的半成品变成可以贴到简历、发到社区的作品。如果你决定把某个项目捡回来,我建议你采用严格控制范围的收尾策略。
5.1 先恢复“可运行状态”,其他一概不管
打开旧项目第一件事,不是改代码,而是恢复可运行状态。安装缺失依赖、调整 Python 版本或 Node 版本、修掉破坏性 API 变更、确保测试或启动脚本能跑通。这一个步骤的完成度就是“至少能看到输出”。
这条非常关键,因为旧项目的环境恢复常常比预期复杂。不要在这个阶段顺手优化代码,也不要中途想加新功能。你要做的只是把状态从“不能跑”变成“能跑”。
恢复成功之后,立刻跑一次完整流程,记录关键输出。如果你连这个项目当初要解决什么都已经忘了,先看 README 和最近一次提交说明。没有这些,就从代码入口开始顺一遍。
5.2 把待办清单缩小到一个固定版本的可交付范围
写完“能跑”基础之后,列出真正阻挡交付的障碍,清单一律限制在十项以内:
- 输入样例 A 解析失败
- 输出格式不对
- README 缺失
- 无法从命令行调用
- 依赖安装步骤没记录
每完成一项就摘掉。不要让新冒出来的“要不要支持”类问题进入这份清单,直到做完十项为止。
这里的关键是不要追求完美,追求“可以对外演示”。一个能演示的、有逻辑闭环的项目,远比一个看起来功能很多、却连入口都找不到的项目有价值。
5.3 补一个名为“如何运行”的文档,比补大量注释更值钱
很多人收尾时最喜欢的动作是给代码加注释。但在长期未更新的项目里,注释的作用远小于一个清晰的运行文档。这个文档应该包含:
- 前置环境:系统版本、依赖管理器、数据库版本(如果有)
- 安装步骤:从 clone 到跑通需要依次执行哪些命令
- 输入输出示例:给一个最小输入和对应输出
- 已知问题:当前版本哪些场景不支持,哪些问题还没解决
给这个明确交付打个勾,那这个项目的完成度就已经到了“低摩擦可交接”的程度。哪怕后面几个月你再没动它,也不至于重新看三天才能回忆起上下文。
6. 给“未完成项目”重新定性:有价值,但要有结束状态
回到黑客新闻那个问题。很多人看到“未完成项目”这个词,第一反应是“我是不是拖延症晚期”,第二反应是在回复里忏悔自己没有坚持下去。
我更倾向用另一种方式理解:一个项目是否失败,不取决于它是否按原计划收尾,而取决于你是否从里面拿到了足够的产出。这里说的产出不全指软件,还包括经验、复盘视角和审美判断。
你手写过一个轻量级消息队列,虽然最后没有演进成产品,但你因此理解了分布式系统里那些看似离奇的设计到底在解决什么问题,这就是产出。你写过一个自动化爬虫最后因为反爬严格而放弃,但你学会了请求频率、代理池和异常重试之间的关系,这也是产出。你做过一个半成品编辑器,虽然不完善,但你知道了为什么“撤销/重做”功能在某些数据结构下那么难做,这同样是产出。
如果你能把每个未完成项目都给出一个“结束状态”,而不是像一堆烂帐一样悬在空中,你心理上的负担会小很多。结束状态可以是:
- 已移除:文件备份后删除或归档,不再占用注意力。
- 已冻结:项目中已记录最后状态和运行方式,未来条件合适时可能继续。
- 已交付:完成了一个可用版本,哪怕发不到十个人手里,也能算完成。
- 已学到经验:项目本身终止,但沉淀了一篇文章、一段笔记或一套排错思路。
7. 开源不是唯一终点,发布一个最小可用版本也算完成
很多积灰项目停更,是因为当事人完全没有考虑过“发布到很小范围”这件事。他们把项目当成只用自己看的东西,直到失去兴趣,然后宣称失败。
一个更好的思路是:把所有项目都按“可以对外展示”的标准来写。你不一定非要发到 GitHub 首页、推到社交平台,只需要想象一下“如果我现在把这个 README 和 Demo 发到一个几十人的技术群,别人能不能立刻看懂它要做什么”。
为了达到这个状态,自然就会倒逼你补上 README、清理环境依赖、精简启动步骤、写清楚输出示例。也会自然阻止你一直加私人大而全的需求,因为“对外展示”版本天然需要一个边界。
如果你有一堆做完 80% 但从未发过任何版本的项目,先挑一个最有信心、恢复成本最低的,集中一个周末做到最小可发布状态,发到自己的公开仓库,或者给两三个同事看一眼。收到一点反馈之后,你会意识到,这个项目终于不是在消耗你,而是开始回馈你了。
我个人更建议按“发布一个最低可用版本,然后快速宣布一个阶段完成”的方式推进。哪怕代码不够优雅、覆盖场景不够多、设计也称不上漂亮,这都是正常状态。把那个版本留在那里,作为一段记录。以后某个节点想继续,你有一个干净的起跑线;不想继续,它至少是一个保存完好的完整示例。
7.1 给每个半成品配一张“项目收尸卡”
如果你对整理多个项目还是觉得无从下手,这里有一个很具体的产出物,也是我每次清理项目时最先写的东西,叫“项目收尸卡”。它不是文档规范,更像是一个关于具体项目的极简备忘。卡片包含四至五条内容:
- 项目名和一句话目标:这个项目原本要做什么。
- 当前状态:核心功能完成到什么程度,是否能运行。
- 停止原因:是学不到东西、需求蔓延、被替代,还是单纯没兴趣。
- 最有价值的产出:这个项目让我学到了哪一件事。
- 如果未来继续,第一步做什么:只写一个动作,不写宏大计划。
你别看这张卡片内容很简单,写一次通常只要十分钟,但它产生的效果非常明显:你能从一个“什么都想保留但什么都没做”的混沌状态,直观看到每个项目的价值与成本。以后再看到 Ask HN 式的帖子,你能坦然地写一句:“我曾经有一堆未完成项目,后来我把该冻结的冻结、该删掉的删掉、该收尾的收尾,手头只保留两三个真正想推进的。”这种状态,比囤积一百个半成品工程健康太多。
8. 真正该警惕的不是“项目没做完”,而是用新项目逃避收尾和复盘
最后想点一个很多程序员自己未必会意识到的现象:开一个新项目,经常是逃避收尾最舒服的工具。
写旧的半成品意味着你要面对混乱的环境、之前不成熟的代码和一堆没解决的工程问题。而开一个新项目,意味着你可以重新规划目录结构、选择新技术栈、获得“从零开始”的快感。这两件事里,后者容易太多,爽感也大太多。
所以真正让我觉得有问题的场景不是电脑里躺着几个没做完的项目,而是每次遇到瓶颈就去开新项目,并且从不对旧项目做任何结束处理,导致所有知识经验都无法沉淀成一个可被复用的成果。
如果你的 GitHub 和本地项目里有大量半年以上没碰过的仓库,可以花一个周末执行一次“存量项目终结计划”。这不是让你把代码全删掉,而是认领你的历史,给它们一个安排。做完后你会发现一个很简单的道理:真正健康的开发状态,不是把所有项目都做完,而是知道自己为什么留下这个、为什么停掉那个,并且对正在做的少数几件事保持专注。
那批“Ask HN: unfinished projects”的帖子之所以每年都会被重新顶起来,本质上就是因为无数人都在同一件事上反复挣扎:开始总是容易,收尾和停下却很少被认真练习。如果你能把每一次“不做了”都变成一次清晰的选择,而不是一次悄无声息的搁置,那你对项目的掌控感会回到自己手里。这比多写完一个功能更值钱。