陪产假:第三个月
一句话总结
逐渐回归工作。
亮点
- 作为新手爸爸,我越来越容易平衡自己的时间了。
- 我曾为两篇博客文章表现不佳而闷闷不乐,结果它们后来又火了。
- 我尝试了用于软件开发堆叠式 diff 工作流,很喜欢它,只是 git 的短板让人头疼。
目标评分
每个月初,我都会宣布自己想完成的事情。以下是我对这些目标的完成情况:
享受家庭时光
- 结果:和妻子、儿子度过了愉快的时光。
- 评分:A
我发现提醒自己很有帮助:即使看起来我长时间没有工作,那也是我自己做出的选择,我的时间基本上仍由自己掌控。
我仍在寻找工作与家庭时间之间的平衡,一切都在变得越来越好。
发布关于用 Nix 进行模糊测试的教程
- 结果:我终于写完了这篇文章,但没有引起太多关注。
- 评分:A
逐渐回归工作
当我坐下来写这篇回顾时,那种“没时间工作”的焦虑仿佛已是遥远的记忆。我以为那是两个月前的事了,所以当我发现那其实是我最近一篇回顾时,颇感意外。
幸运的是,我现在对家庭时间的分配感到轻松多了。我在享受大量家庭时光的同时,还发表了比以往更多的文章。
促成这一变化的几个因素:
儿子晚上睡得更好了
起初他每晚要醒三四次,每次醒来要吃 60 到 90 分钟,但现在他每晚只醒两三次,而且能在 15 到 60 分钟内重新入睡。
昨晚他睡了一整晚(7 小时 45 分钟),这让人很兴奋。
家人给了我们更多帮助
随着儿子长大,我们更放心让家人过来照顾他而不需要我们在场。现在我们每周大约有五个小时的帮手时间,而且这个数字可能还会继续增加,因为家人们愿意提供比目前更多的帮助。
儿子可以在我工作时打盹
有人抱着他或把他放在胸前背带里时,他能睡得更久,所以我通常每天抱着他工作一到三个小时。这也让妻子得以休息,因为她能有一段几个小时属于自己的时间。
我有保证的工作时间
在不固定的时间安排下,我很难工作。尽管很多时候妻子在照顾儿子,或者他趴在我胸口睡着,但一想到他们随时可能需要我,我就难以集中注意力。
妻子主动提出每天给我一段保证的 90 分钟专注时间,这对集中精力很有帮助。知道自己每天都有这段时间也很好,我可以把某些高专注度的任务留到那个时间段来做。
我在博客文章上投入过多吗?
我曾经有个坏习惯:每当我学到什么难懂的东西,就觉得必须写篇博客文章来解释它。
我试图把每篇文章都打磨到最好,哪怕文章的目标读者极少,或者我根本没有渠道触达读者。以往的例子包括《招聘内容写手:小企业指南》(有受众,但我没有好的触达方式)和《零代码改动为应用改造接入云存储》(非常小众,在我的奇特使用场景之外没什么人感兴趣)。
有读者表示欣赏那些文章,但我也必须考虑机会成本。在我撰写它们的时间里,是否本可以写出另一篇能触达更多读者或创造更多总体价值的文章?
从那以后,我对发文变得更有策略性了。如果我认为一篇文章无法触达足够多的读者,我要么不写,要么在“Notes”栏目里写一个快速粗糙的版本。
《使用 Nix 对 PDF 解析器进行模糊测试》
对于在这篇文章上投入的时间,我的心情很矛盾。
写这篇文章让我学到了很多关于 Nix 和模糊测试的知识,但它花的时间比我预期的长。一开始我想:“哦,几个小时就能快速写完。”结果我花了 20 多个小时。
在大语言模型时代写软件教程也令人沮丧。直到几年前,教程还有长期回报,因为人们之后会通过网页搜索发现它们。如今,如果我写一篇小众教程,大语言模型会直接窃取我写的内容,而读者根本不知道它出自谁之手。
《首次出售公司的经验教训》
我从一开始就知道这是一篇有风险的文章,因为有几件事对它不利:
- 它讲的是出售公司的琐碎细节,而我 99% 的读者并没有出售公司的打算。
- 我之前关于那次出售的文章获得了关注,但那是一个故事,即使读者自己对做这件事不感兴趣,也能享受阅读的过程。
- 它超级长。
- 我的目标是每篇博客文章约 10 分钟的阅读时长,而这篇估计要读 33 分钟。
- 唯一有较大机会的社交媒体渠道是 Hacker News。
我把它提交到了 Hacker News,但它完全没能上首页。
我觉得它仍然有机会在 Hacker News 上获得关注,但即使彻底失败,我也很高兴写了它。它帮我理清了自己对这次收购的思考,如果将来再卖一家公司,它也会是一份有用的参考。一些经历过收购或正在考虑收购的创始人给了我正面反馈。
然后,那些文章突然火了
写完上面的内容后,我发现 Hackaday 报道了我的 Nix 模糊测试教程,这很令人欣慰。
而在写下“《首次出售公司的经验教训》扑街”那一节的第二天,一位读者再次把它提交到了 Hacker News,它冲到了第 2 名。
不过,我仍然认为我最初的分析是对的。我在模糊测试那篇文章上投入过度,而在关于出售 TinyPilot 的那篇上的投入是恰当的。
通过 stacked diffs 实现大型功能
过去几周,我把大部分业余编程时间都花在了 ScreenJournal 上,这是我的影视评论 Web 应用。它的理念类似 letterboxd 或 Goodreads,但评论只对你的朋友可见,且代码开源。

ScreenJournal,我的开源影视评论 Web 应用
我一直希望 ScreenJournal 同时支持电影和电视剧,但我先实现了电影功能,因为它更简单。我有意没有把代码泛化以支持电视剧。我不知道这件事会不会发生,所以我想针对已有的功能进行优化。
十月,我添加了对电视剧评论的支持,因此不得不修正代码库中许多“评论对象总是电影”的假设。
完整的改动最终达到了 2000 行代码,放在单个 changelist 里理解起来有点吃力。我用的是“changelist”这个词,但我说的是 GitHub 术语中的 pull request 或 GitLab 术语中的 merge request 这类东西。
过去,我处理这种大型改动的方式是:用一个功能分支,在我完成该功能之前它一直处于损坏或不完整的状态。我要么直接在功能分支上修改,要么从功能分支再切出一个子任务分支,完成后把子任务合并回去。
这种方法的问题在于,功能分支会变成一个庞大到无法理解的变更团块。你可以在我把 What Got Done 从 Firestore 迁移到 SQLite 时看到这样的例子。那次改动内部有很多子步骤,但因为一切都混在一起,根本无法检视。
所以,对于 ScreenJournal 的这次改动,我尝试了不同的做法。我没有保留一个庞大杂乱的功能分支,而是采用了 stacked diffs(堆叠式 diff)。
什么是 stacked diff?
Stacked diffs 是指你有一个 main 分支,想合并一个大功能,于是把功能拆成变更 A、B 和 C。你从 main 切出分支创建 A,从 A 切出分支创建 B,依此类推。
GitHub 对 stacked diffs 的支持还算凑合:如果你的栈是 A、B、C,你会先建一个从 A 合入 main 的 PR,再建一个从 B 合入 A 的 PR。当你把 A 合入 main 的 PR 合并后,B 合入 A 的 PR 会自动更新为 B 合入 main 的 PR。
我按电视剧评论流程中的每个页面拆分工作,每个页面对应一个 changelist。
发表评论的第一步是搜索你想评论的东西。以前只能搜电影,所以我支持电视剧的第一步就是添加一个单选按钮,让用户在电影和电视剧之间选择:

我的第一个任务是修改标题搜索 UI 以支持电视剧。
接下来我需要一种方式让用户选择电视剧的季,这是只有电影时不存在的需求。所以,这成了单独的一个变更。

我的第二个任务是实现一个用于选择要评论的电视剧季的 Web UI。
我就这样继续下去,流程的每个阶段都是新分支和独立的 pull request。
以下是这种开发方式的一些心得。
优点:stacked diffs 更能激励我
Stacked diffs 的好处在于,功能的每个子任务都是独立的 changelist。
把改动拆成更小的部分让我更有成就感和进度感。完成一个 changelist 并知道它 100% 完成是很满足的,而不是在一个庞大杂乱的分支上,完成一个子任务只让功能从 30% 推进到 35%。
缺点:我得不断删除变更历史
我最不喜欢 stacked diff 工作流的一点是,我最终会删除源码历史,这抵消了版本控制的一大好处。
每当我意识到应该在栈中更早的位置做某个改动时,就得执行 git rebase,它会重写历史。这意味着我必须强制推送到 GitHub,这让我的 changelist 里布满了难看的 force-pushed 记录:

在 git 中频繁 rebase 会导致难看的 force-pushed 记录充斥我在 GitHub 上的 changelist
我知道有些人希望所有变更都原封不动地保留,好像每次 commit 都是谋杀案审判中的证据一样。我不在乎那个,但我确实想要一份合理的撤销历史,以防自己犯错。我不喜欢 force push 会覆盖远端的撤销历史,而从本地恢复则需要复杂的手术。
优点:--update-refs 简化了 stacked diffs 的 rebase
在试验 stacked diffs 工作流的过程中,我发现了 git 的 --update-refs 标志,它可以让你一次性 rebase 多个分支。
这个技巧让 stacked diffs 变得更容易,因为我之前要按顺序逐个 rebase 每个分支,当栈里有四个分支后就变得极其繁琐。
缺点:--update-refs 之后推送依然麻烦
如果我有分支 A、B 和 C,并且一次性 rebase 它们,git 的输出是这样的:
$ git rebase master --update-refs
Successfully rebased and updated refs/heads/C.
Updated the following refs with --update-refs:
refs/heads/A
refs/heads/B
虽然 --update-refs 简化了 rebase 操作,却没有“好,现在推送我刚 rebase 过的分支”这样的命令。相反,我得把 git 的输出复制到文本编辑器里,编辑出分支名,然后再放回类似 git push origin A B C -f 的命令中。这个过程很烦人,总是打断我的思路。
缺点:我经常陷入意外的 git 状态
尽管我以为自己在以正确的方式 rebase,我还是经常陷入令人困惑的状态。比如我 rebase 之后,它却要求我解决已经解决过的冲突。
我通过 squash 提交并再次 rebase 来绕过这个问题,但这又一次重写了历史,使撤销错误变得更难。我还恼火于自己浪费了太多脑力去琢磨如何正确地“向 git 道歉”,而不是专心写代码。
也许我应该试试 jujutsu
我看到越来越多关于 jujutsu 的讨论,这是一个新的版本控制系统,有望成为 Google 内部的标准。
几个月前,我想过试试它,当时想:“呃,git 能满足我的需求,为什么要追逐下一个闪亮的新玩意?”但在经历了 git rebase 的这些遭遇后,我想起自己已经把太多令人沮丧的 git 体验当作常态接受了。
粗略浏览 Steve Klabnik 的教程后,感觉 jujutsu 对 stacked diffs 和多重 rebase 的支持比 git 更好。
推荐
《为什么我坚持写博客 15 年》,作者 Jonas Hietala(乔纳斯·希塔拉)
这篇关于写博客的文章让我深有共鸣。当我进一步浏览 Jonas 的网站时,我心想:“哦,这家伙简直就是瑞典版的我。”所以如果你喜欢我的文字,很可能也会喜欢他的。
《乌克兰见闻录》,作者 Matt Lakeman(马特·雷克曼)
我上周发现了 Matt 的博客,从那以后每天都忍不住想:“这人到底是谁?”
Matt 会前往不太热门的目的地,通常待十天左右,然后发表一篇关于该国的博客文章。但这些可不是寄给妈妈的明信片式博文;这些是中篇小说长度的文章,基于对该国历史的数小时研究以及与当地人的交谈。
我还发现 Matt 在互联网其他地方以用户名 dormin111 有很长的发帖历史,比如这篇对电影《灾难艺术家》的详细分析。
他所有作品最不可思议的一点是,似乎毫无功利目的。通常,当你看到有人在写作上投入这么多时,很容易看出这对他们有什么好处:他们有 Substack 或某种付费课程为他们赚钱,免费文章则是引流品。但我在 Lakeman 的任何作品里都找不到任何目的或盈利动机。他似乎就是单纯热爱深入思考事物并分享自己的想法。
言归正传,回到这篇乌克兰文章。我本以为他是在战前去的,结果他是在开战两个月后去的,并在距离前线几英里的范围内采访了士兵和平民。看到一个并非职业记者的人采访乌克兰形形色色的真实人物、报道这场战争,我觉得很有意思。相比传统媒体渠道的任何报道,这都给人一种更真实、更个人化的视角。
《赛博朋克 2077》(电子游戏)
我不怎么玩电子游戏。我每年买一款新游戏,玩到腻为止,通常是几天内玩 5 到 25 个小时。这款游戏我已经玩了大约 25 个小时,仍然乐在其中。
游戏的深度令我惊叹。玩了 25 个小时,我觉得自己大概只探索了游戏的 5%,现代游戏竟能拥有如此广阔的世界,实在让我惊叹。
我通常不在乎游戏里的剧情,游戏逼着我坐下来听冗长的背景交代时会让我烦躁,但《赛博朋克》是为数不多的剧情精彩到我愿意认真关注的游戏之一。而且看到他们在配音上投入这么多也很酷——基努·里维斯在游戏中扮演重要角色。
《底特律人》(电视剧)
我听说过这部剧,但剧名一直让我望而却步。一部以角色住在底特律为核心卖点的剧?听起来很无聊。
后来我看了剧中的一段片段,意识到里面有很棒的演员,基调就像情景喜剧版的、稍微更接地气的《我想让你离开》。我刚看完第一季,觉得非常棒。

《底特律人》是一部稍微更接地气的、情景喜剧版的《我想让你离开》。
收尾
完成了什么?
- 发表了两篇完整长度的文章:
- 发表了五篇短篇笔记:
- ScreenJournal 新增了电视剧评论支持
经验教训
- Stacked diffs 为大型改动提供了不错的工作流,但 git 对它们的支持并不理想。
- 博客文章的投入应与其预期回报相匹配。
- 虽然我喜欢记录所学的一切,但我需要博客在经济上可持续,而这只有在相当大比例的长篇文章能吸引到可能对我的盈利项目感兴趣的读者时才能实现。
下个月的目标
- 享受家庭时光。
- 完成并发布《Refactoring English》(《重构英语》)的一个章节。
随机一篇博客