目录
3510 字
18 分钟
教程地狱的出口:学一门新技术,先设计它怎么让你失败

一、教程地狱不是意志力问题,是反馈问题#

打开一份官方教程,跟着走:装依赖,跑命令,看到 Listening on :3000,截图,进入下一章。三个小时过去,你完成了六章,笔记记了八页。

两周后有人问你「这个工具在容器里跑要注意什么」,你卡住了。

常见的归因是「我记性不好」「我没坚持复习」。这两个解释都不太对。真正的问题在于:教程给你的是顺风环境,而你需要的判断力只在逆风里长出来。

教程的默认承诺是「每一步都会成功」。它精心挑选了版本、目录、权限和网络条件,让 npm install 一定成功,让端口一定空闲,让示例数据一定合法。你在里面练习的动作是「跟随」,不是「判断」。跟随不需要判断力,也就不生产判断力。

真实环境的默认状态恰好相反:版本对不上、路径不对、权限不够、网络会断、数据不干净、两个进程同时写同一个文件。这些不是意外,是常态。

所以第一件事是把目标改掉。学一门新技术,验收单位不是「读完几章」,而是「我制造并解释了几次可复现的失败」。 一次失败的含金量,通常高于十章教程——因为它逼你建立一个因果假设,再用现实校验它。

这篇文章讲的是一套把「失败」设计进学习流程的做法:一张验收卡、一个最小验证项目、一张失败矩阵、一次预测循环,最后是怎么判断该继续还是该止损。

二、先写验收卡:三个问题,写在打开编辑器之前#

多数人开始学的第一个动作是找教程。更有效的第一个动作是写下你要用这门技术做的判断。技术不是知识收藏品,它的价值全部体现在它改变了你哪个决定。

三个问题够用:

  1. 它替换掉了什么? 没有替代品的新技术很少;大多数技术是在一组旧方案里换了个取舍。说不清「它替掉了谁」,你就没法判断它什么时候值得用。
  2. 它在什么条件下会给你错误结果,或者直接失灵? 这个问题决定你要不要读文档里最枯燥的那一章(限制、兼容性、已知问题)。跳过它,你学到的就是一份宣传材料。
  3. 我能不能用三句话向同事讲清它的取舍? 讲不清通常意味着你只记住了 API,没建立模型。

把答案写成一张卡片,贴在笔记顶部:

技术:____________________
它替换的旧方案:____________________
它换来的是:__________,付出的是:__________
它的失效条件(至少 3 条):__________
我的验证产物:____________________
截止时间:__________(默认 10 小时)

这张卡片最重要的作用不是记录,而是允许你提前宣告失败。有了截止时间和产物定义,「学到一半没感觉」就不再是模糊的焦虑,而是一个可以结算的状态。

不同目的的验收标准并不一样,别用同一套标准要求所有学习:

学习目的验收标准停止条件
解决手头一个具体问题问题消失,且能解释为什么问题解决即停,不追全貌
技术选型评估能写出两条「什么情况下不选它」能支撑一次评审就停
长期主力技术能预测行为、能定位故障、能读懂源码关键路径没有终点,按季度复检
兴趣探索有任意一个自己做的产物新鲜感消失即停,不必愧疚

三、最小验证项目:一条真实链路,三个输出#

「做个 todo 应用」是学习项目里最常见的陷阱。不是因为它太简单,而是因为所有技术都能搭 todo——它验证不了任何取舍。你做完之后,仍然不知道这门技术在你的场景里值不值。

好的验证项目有三个特征:来自你手头真实的小麻烦、能在几小时内跑完一条完整链路、失败时你会在意。

以「用 systemd timer 替掉一个 crontab 任务」为例。这是个不起眼的靶子,但它有完整的失败面:开机顺序、环境变量、日志、失败可见性、机器重启后是否还在。二十行配置就能覆盖。

~/.config/systemd/user/notes-backup.service
[Unit]
Description=备份笔记仓库
[Service]
Type=oneshot
WorkingDirectory=%h/notes
ExecStart=%h/bin/backup.sh
~/.config/systemd/user/notes-backup.timer
[Unit]
Description=每天 03:17 触发笔记备份
[Timer]
OnCalendar=*-*-* 03:17:00
Persistent=true
[Install]
WantedBy=timers.target

启用并观察:

Terminal window
systemctl --user daemon-reload
systemctl --user enable --now notes-backup.timer
systemctl --user list-timers --all
journalctl --user -u notes-backup.service -n 50

跑通这条链路只算完成了三分之一。真正产生判断的是下面这些动作:

  • 把 backup.sh 里的 node、python 换成绝对路径之外的写法,看它还找得到吗。 systemd 不读你的 .zshrc,PATH 和你交互式终端里的不是同一个。这是「教程里能跑、服务里挂掉」的头号原因,也是你必须亲手撞一次才会记住的事。
  • 用 systemctl --user 而不是 systemctl,并想清楚区别。 用户级单元活在用户会话里;要让它在你没登录时也运行,得开 lingering(loginctl enable-linger $USER)。这个细节教程通常放在很后面的脚注里。
  • 故意让脚本失败一次,看失败会不会被看见。 定时任务最危险的状态不是失败,是静默失败。没有告警的 timer 和没有 timer 没有区别。

三个输出,缺一不可:

  1. 一条能跑的最小链路(这里就是 service + timer + 日志)。
  2. 一份失败证据(截图、日志片段、报错原文,存进笔记)。
  3. 一段三句话的结论:它替换了什么、代价是什么、什么情况下我不会用它。

第三项最容易跳过,也最值钱。它正是第二节那张验收卡的答案,只不过现在是你在撞过墙之后写的版本。

四、把失败设计进练习:一张失败矩阵#

被动等待失败太慢。主动制造失败,才能在一小时内拿到别人半年才撞到的东西。常用的变量就七个,每次只改一个:

变量怎么制造通常会暴露什么
版本把某个依赖降一个 major隐式 API 契约、废弃警告是否可信
环境在干净容器里重跑一遍你对本机环境的隐性依赖
网络断网、限速、注入超时重试策略、超时是否真的生效
权限换成非 root / 只读目录文件写入点、临时目录假设
并发同时起两个进程做同一件事锁、幂等、竞争条件
数据量输入放大 10 倍内存曲线、分页、批处理边界
输入边界空值、超长、非 UTF-8、时区校验层在哪、错误信息是否可读

规则只有一条:一次只改一个变量,并且先写预测。 同时改三个变量,失败了你不知道该谢谁;不写预测,失败只会变成一次「哦,原来如此」,而不是一次被校验的判断。

举个最常见的形态,填好的日志大概长这样:

预测实际差异解释
服务能正常跑完备份脚本exit 127: command not foundsystemd 的 PATH 不含用户包管理器目录,脚本用了裸命令名
加上 Environment=PATH=... 就能修好修好了,但 git 仍报 dubious ownership运行用户与仓库所有者不同,需要 safe.directory
失败会发邮件通知没有任何通知没配 OnFailure=,失败只是日志里的一行

三行日志换来三条以后能直接复用的判断。这比再读三章教程划算得多。

五、预测—运行—解释:把每个 20 分钟变成一条判断#

把上面的动作固化成节奏,就是学习循环的全部:

读一小段文档(≤20 分钟)→ 写下一个可证伪的预测 → 运行 → 解释差异 → 记一行日志

关键在「可证伪」。像「这个框架性能不错」不是预测,没法校验;「这个查询在 10 万行数据下会走全表扫描」是预测,运行完就知道对错。

日志用最朴素的 Markdown 就够,一个文件、一张表:

| 时间 | 预测 | 结果 | 我原来的模型错在哪 |
| --- | --- | --- | --- |
| 10:20 | 改 timer 的 OnCalendar 需要 restart timer | 只需 daemon-reload | 把单元文件当成进程配置,其实先被解析成单元 |

如果你想少一点手工,可以让脚手架帮你起一个沙盒。下面这个脚本不干别的事,只是把「提问、预测、记录」变成目录结构,避免它们散落在聊天记录里:

#!/usr/bin/env bash
# learn.sh —— 为一次技术学习创建最小沙盒
set -euo pipefail
name="${1:?用法: learn.sh <技术名>}"
dir="$HOME/learning/$name"
mkdir -p "$dir"/{sandbox,logs}
cat > "$dir/CARD.md" <<'EOF'
# 验收卡
- 它替换的旧方案:
- 它换来的是: 付出的是:
- 失效条件 1:
- 失效条件 2:
- 失效条件 3:
- 验证产物:
- 截止时间(默认 10 小时):
EOF
cat > "$dir/PREDICTIONS.md" <<'EOF'
| 时间 | 预测(可证伪) | 结果 | 我原来的模型错在哪 |
| --- | --- | --- | --- |
EOF
cat > "$dir/logs/README.md" <<'EOF'
把每次失败的原始报错、日志片段、截图放这里,不要只写结论。
EOF
echo "沙盒已建立:$dir"
echo "下一步:填完 CARD.md 的三个问题,再去打开教程。"

注意它刻意没有生成「学习计划」或「章节清单」。清单会让你回到跟随模式,而这张卡只逼你回答判断问题。

六、什么时候该停:三条止损线#

学习最大的隐性成本不是学费,是它占用的注意力本来可以投在别处。所以「什么时候放弃」需要提前定,而不是靠某天突然疲惫来决定。

三条线,任意一条触发就重新评估:

  1. 时间线:10 小时里没有产出任何属于自己的东西(不是抄的 demo,是你自己的产物),说明路径有问题。换资料或换目标,而不是加时间。
  2. 替代线:如果你用已经熟悉的工具能在一小时内解决同一个问题,那么学新东西的理由必须更强——通常得是「长期维护成本」或「团队统一」,而不是「它看起来更现代」。
  3. 沉没成本线:已经投入的时间不构成继续的理由。唯一该算的是「剩下还需要投入多少」和「它能替我挡掉多少未来的工作」。

不同信号对应不同动作,别把三者混为一谈:

信号判断动作
能跑通,但说不清它和旧方案的区别只学了操作,没学模型停下来写对比表,别往下学 API
每次失败都靠搜索解决,无法预测缺少心智模型回到官方文档的「概念」章节,只读那一章
十小时没有任何自己的产物路径或目标有问题换一个更小的靶子,通常不是换技术
用得上,但一年用不了几次投入产出不划算降到「知道它存在、知道去哪查」即可
团队里没人用、也没有迁移计划学了也无处落地放弃,记一行笔记了事

最后一条值得单独说:「知道去哪查」本身就是一种合格的学习结果。 不是所有技术都值得学成主力,把一门技术降级为「听说过、能查文档」,是理性的取舍,不是失败。

七、10 小时预算:把时间花在会失败的地方#

如果按上面的流程走,十小时大概这样分配:

时长做什么产出
1 小时只读文档的目录、限制章节、迁移/兼容说明验收卡 + 三条失效条件
2 小时跑通一条真实链路(不是教程 demo)可运行的最小产物
3 小时失败矩阵,每次一个变量,先预测后运行一份失败日志
2 小时写成三句话结论,讲给一个不懂的人听能被反驳的判断
1 小时和已有知识连接:它像我用过的哪个东西一张类比表
1 小时留白,处理计划外的坑——

留白那一小时不是浪费。学习的时间几乎总是被低估,因为计划里只算了「顺利的部分」。预留一小时给意外,比事后自责更有效。

八、检查清单#

学完一门新技术,用这八条自检。打不上勾的,就是下次该补的地方:

  • 我能说出它替换掉的旧方案,以及它为此付出的代价
  • 我能列出至少三条它会失灵或给出错误结果的条件
  • 我有一个自己做的、能在干净环境里重跑的产物
  • 我制造过至少五种失败(版本、环境、网络、权限、并发、边界里挑)
  • 我的失败日志里有预测,而不只有结果
  • 我能用三句话向不懂的人讲清取舍,并且对方能提出反对
  • 我知道它和我已经会的东西哪里像、哪里不像
  • 我知道下次遇到相关问题该去查哪一页文档

回到开头那个问题:为什么跟着教程走完,还是不会?

因为教程替你挡住了所有会失败的地方,而判断力恰好长在那些地方。学习不是把一条顺风路径走完,而是主动把逆风造出来,再解释它为什么逆风。 教程可以读,但它的作用是给你一张地图;真正让你认路的,是你自己走错的那几次。

教程地狱的出口:学一门新技术,先设计它怎么让你失败
https://www.hehonglei.cn/posts/learning-new-tech-tutorial-hell-verifiable-project/
作者
Honglei He
发布于
2026-10-30
许可协议
CC BY-NC-SA 4.0