目录
一、教程地狱不是意志力问题,是反馈问题
打开一份官方教程,跟着走:装依赖,跑命令,看到 Listening on :3000,截图,进入下一章。三个小时过去,你完成了六章,笔记记了八页。
两周后有人问你「这个工具在容器里跑要注意什么」,你卡住了。
常见的归因是「我记性不好」「我没坚持复习」。这两个解释都不太对。真正的问题在于:教程给你的是顺风环境,而你需要的判断力只在逆风里长出来。
教程的默认承诺是「每一步都会成功」。它精心挑选了版本、目录、权限和网络条件,让 npm install 一定成功,让端口一定空闲,让示例数据一定合法。你在里面练习的动作是「跟随」,不是「判断」。跟随不需要判断力,也就不生产判断力。
真实环境的默认状态恰好相反:版本对不上、路径不对、权限不够、网络会断、数据不干净、两个进程同时写同一个文件。这些不是意外,是常态。
所以第一件事是把目标改掉。学一门新技术,验收单位不是「读完几章」,而是「我制造并解释了几次可复现的失败」。 一次失败的含金量,通常高于十章教程——因为它逼你建立一个因果假设,再用现实校验它。
这篇文章讲的是一套把「失败」设计进学习流程的做法:一张验收卡、一个最小验证项目、一张失败矩阵、一次预测循环,最后是怎么判断该继续还是该止损。
二、先写验收卡:三个问题,写在打开编辑器之前
多数人开始学的第一个动作是找教程。更有效的第一个动作是写下你要用这门技术做的判断。技术不是知识收藏品,它的价值全部体现在它改变了你哪个决定。
三个问题够用:
- 它替换掉了什么? 没有替代品的新技术很少;大多数技术是在一组旧方案里换了个取舍。说不清「它替掉了谁」,你就没法判断它什么时候值得用。
- 它在什么条件下会给你错误结果,或者直接失灵? 这个问题决定你要不要读文档里最枯燥的那一章(限制、兼容性、已知问题)。跳过它,你学到的就是一份宣传材料。
- 我能不能用三句话向同事讲清它的取舍? 讲不清通常意味着你只记住了 API,没建立模型。
把答案写成一张卡片,贴在笔记顶部:
技术:____________________它替换的旧方案:____________________它换来的是:__________,付出的是:__________它的失效条件(至少 3 条):__________我的验证产物:____________________截止时间:__________(默认 10 小时)这张卡片最重要的作用不是记录,而是允许你提前宣告失败。有了截止时间和产物定义,「学到一半没感觉」就不再是模糊的焦虑,而是一个可以结算的状态。
不同目的的验收标准并不一样,别用同一套标准要求所有学习:
| 学习目的 | 验收标准 | 停止条件 |
|---|---|---|
| 解决手头一个具体问题 | 问题消失,且能解释为什么 | 问题解决即停,不追全貌 |
| 技术选型评估 | 能写出两条「什么情况下不选它」 | 能支撑一次评审就停 |
| 长期主力技术 | 能预测行为、能定位故障、能读懂源码关键路径 | 没有终点,按季度复检 |
| 兴趣探索 | 有任意一个自己做的产物 | 新鲜感消失即停,不必愧疚 |
三、最小验证项目:一条真实链路,三个输出
「做个 todo 应用」是学习项目里最常见的陷阱。不是因为它太简单,而是因为所有技术都能搭 todo——它验证不了任何取舍。你做完之后,仍然不知道这门技术在你的场景里值不值。
好的验证项目有三个特征:来自你手头真实的小麻烦、能在几小时内跑完一条完整链路、失败时你会在意。
以「用 systemd timer 替掉一个 crontab 任务」为例。这是个不起眼的靶子,但它有完整的失败面:开机顺序、环境变量、日志、失败可见性、机器重启后是否还在。二十行配置就能覆盖。
[Unit]Description=备份笔记仓库
[Service]Type=oneshotWorkingDirectory=%h/notesExecStart=%h/bin/backup.sh[Unit]Description=每天 03:17 触发笔记备份
[Timer]OnCalendar=*-*-* 03:17:00Persistent=true
[Install]WantedBy=timers.target启用并观察:
systemctl --user daemon-reloadsystemctl --user enable --now notes-backup.timersystemctl --user list-timers --alljournalctl --user -u notes-backup.service -n 50跑通这条链路只算完成了三分之一。真正产生判断的是下面这些动作:
- 把
backup.sh里的node、python换成绝对路径之外的写法,看它还找得到吗。 systemd 不读你的.zshrc,PATH和你交互式终端里的不是同一个。这是「教程里能跑、服务里挂掉」的头号原因,也是你必须亲手撞一次才会记住的事。 - 用
systemctl --user而不是systemctl,并想清楚区别。 用户级单元活在用户会话里;要让它在你没登录时也运行,得开 lingering(loginctl enable-linger $USER)。这个细节教程通常放在很后面的脚注里。 - 故意让脚本失败一次,看失败会不会被看见。 定时任务最危险的状态不是失败,是静默失败。没有告警的 timer 和没有 timer 没有区别。
三个输出,缺一不可:
- 一条能跑的最小链路(这里就是 service + timer + 日志)。
- 一份失败证据(截图、日志片段、报错原文,存进笔记)。
- 一段三句话的结论:它替换了什么、代价是什么、什么情况下我不会用它。
第三项最容易跳过,也最值钱。它正是第二节那张验收卡的答案,只不过现在是你在撞过墙之后写的版本。
四、把失败设计进练习:一张失败矩阵
被动等待失败太慢。主动制造失败,才能在一小时内拿到别人半年才撞到的东西。常用的变量就七个,每次只改一个:
| 变量 | 怎么制造 | 通常会暴露什么 |
|---|---|---|
| 版本 | 把某个依赖降一个 major | 隐式 API 契约、废弃警告是否可信 |
| 环境 | 在干净容器里重跑一遍 | 你对本机环境的隐性依赖 |
| 网络 | 断网、限速、注入超时 | 重试策略、超时是否真的生效 |
| 权限 | 换成非 root / 只读目录 | 文件写入点、临时目录假设 |
| 并发 | 同时起两个进程做同一件事 | 锁、幂等、竞争条件 |
| 数据量 | 输入放大 10 倍 | 内存曲线、分页、批处理边界 |
| 输入边界 | 空值、超长、非 UTF-8、时区 | 校验层在哪、错误信息是否可读 |
规则只有一条:一次只改一个变量,并且先写预测。 同时改三个变量,失败了你不知道该谢谁;不写预测,失败只会变成一次「哦,原来如此」,而不是一次被校验的判断。
举个最常见的形态,填好的日志大概长这样:
| 预测 | 实际 | 差异解释 |
|---|---|---|
| 服务能正常跑完备份脚本 | exit 127: command not found | systemd 的 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 的三个问题,再去打开教程。"注意它刻意没有生成「学习计划」或「章节清单」。清单会让你回到跟随模式,而这张卡只逼你回答判断问题。
六、什么时候该停:三条止损线
学习最大的隐性成本不是学费,是它占用的注意力本来可以投在别处。所以「什么时候放弃」需要提前定,而不是靠某天突然疲惫来决定。
三条线,任意一条触发就重新评估:
- 时间线:10 小时里没有产出任何属于自己的东西(不是抄的 demo,是你自己的产物),说明路径有问题。换资料或换目标,而不是加时间。
- 替代线:如果你用已经熟悉的工具能在一小时内解决同一个问题,那么学新东西的理由必须更强——通常得是「长期维护成本」或「团队统一」,而不是「它看起来更现代」。
- 沉没成本线:已经投入的时间不构成继续的理由。唯一该算的是「剩下还需要投入多少」和「它能替我挡掉多少未来的工作」。
不同信号对应不同动作,别把三者混为一谈:
| 信号 | 判断 | 动作 |
|---|---|---|
| 能跑通,但说不清它和旧方案的区别 | 只学了操作,没学模型 | 停下来写对比表,别往下学 API |
| 每次失败都靠搜索解决,无法预测 | 缺少心智模型 | 回到官方文档的「概念」章节,只读那一章 |
| 十小时没有任何自己的产物 | 路径或目标有问题 | 换一个更小的靶子,通常不是换技术 |
| 用得上,但一年用不了几次 | 投入产出不划算 | 降到「知道它存在、知道去哪查」即可 |
| 团队里没人用、也没有迁移计划 | 学了也无处落地 | 放弃,记一行笔记了事 |
最后一条值得单独说:「知道去哪查」本身就是一种合格的学习结果。 不是所有技术都值得学成主力,把一门技术降级为「听说过、能查文档」,是理性的取舍,不是失败。
七、10 小时预算:把时间花在会失败的地方
如果按上面的流程走,十小时大概这样分配:
| 时长 | 做什么 | 产出 |
|---|---|---|
| 1 小时 | 只读文档的目录、限制章节、迁移/兼容说明 | 验收卡 + 三条失效条件 |
| 2 小时 | 跑通一条真实链路(不是教程 demo) | 可运行的最小产物 |
| 3 小时 | 失败矩阵,每次一个变量,先预测后运行 | 一份失败日志 |
| 2 小时 | 写成三句话结论,讲给一个不懂的人听 | 能被反驳的判断 |
| 1 小时 | 和已有知识连接:它像我用过的哪个东西 | 一张类比表 |
| 1 小时 | 留白,处理计划外的坑 | —— |
留白那一小时不是浪费。学习的时间几乎总是被低估,因为计划里只算了「顺利的部分」。预留一小时给意外,比事后自责更有效。
八、检查清单
学完一门新技术,用这八条自检。打不上勾的,就是下次该补的地方:
- 我能说出它替换掉的旧方案,以及它为此付出的代价
- 我能列出至少三条它会失灵或给出错误结果的条件
- 我有一个自己做的、能在干净环境里重跑的产物
- 我制造过至少五种失败(版本、环境、网络、权限、并发、边界里挑)
- 我的失败日志里有预测,而不只有结果
- 我能用三句话向不懂的人讲清取舍,并且对方能提出反对
- 我知道它和我已经会的东西哪里像、哪里不像
- 我知道下次遇到相关问题该去查哪一页文档
回到开头那个问题:为什么跟着教程走完,还是不会?
因为教程替你挡住了所有会失败的地方,而判断力恰好长在那些地方。学习不是把一条顺风路径走完,而是主动把逆风造出来,再解释它为什么逆风。 教程可以读,但它的作用是给你一张地图;真正让你认路的,是你自己走错的那几次。