Refactoring English: Month 13

Michael Lynch

重构英语:第13个月

一句话总结

写关于专注的文章时却分心了

刚来这里?

你好,我是 Michael(迈克尔)。我是一名软件开发者,也是几家小型独立科技企业的创始人。我目前正在写一本名为 Refactoring English(《重构英语:软件开发者的高效写作》) 的书。

我每个月都会发布这样一篇回顾文章,分享这本书以及我的整体职业生活进展如何。

亮点

  • 根据购买力平价为我的书增加了区域定价。
  • 创建了我的第一个 Flutter 应用。
  • 正在编写我的第一个跨语言库(cross-language library)。

目标评分

每个月开始时,我都会宣布自己想要完成的事情。以下是我完成这些目标的情况:

发布一款能吸引人们访问《重构英语》网站的游戏

这篇博客文章是一次冒险押注,因为它只有登上 Hacker News 首页才能吸引新读者,而它唯一的机会是在 2026 年最初几周实现这一点。

幸运的是,这篇文章登上了 Hacker News 的第 1 名,并在首页停留了将近 22 个小时。它延续了我介绍其他成功科技写作者的策略。我喜欢这一策略,因为这对我、读者以及我介绍的写作者来说,感觉像是一场多赢。

Hacker News 预测游戏目前仍完成了大约 80%。我不确定该怎么处理它,因为它几乎已经完成了,但我觉得它并不好玩,所以一直没有动力把它做完。不过我还是想把它推过终点线,看看大家怎么评价。

发布《重构英语》的两章内容

  • 结果:推进了两章内容,但没有完成
  • 评分:D

讽刺的是,我正在写的章节讲的正是动机和专注,但我一直让自己对 MeshCore 的实验干扰写作。新年之后,我在保持专注方面做得更好了,而分心其实也有帮助,因为我获得了关于如何重新找回专注的新鲜经验可写。

为一款纯粹出于兴趣开发的家庭照片分享应用编写设计文档

还是一样,我在 12 月被 MeshCore 实验分散了注意力,进展没有达到自己的预期。我喜欢设计文档,也觉得它们很有帮助,但写起来同样无比枯燥,所以我总是很想把设计文档搁置一边,去做那些能带来更即时满足感的事情。

《重构英语》指标

指标2025 年 11 月2025 年 12 月变化
独立访客7,6082,266-5,342 (-70%)
预售收入$1,018.48$492.55-$525.93 (-52%)
赞助收入$48.25$48.25$0.00 (0%)
总收入$1,066.73$540.80-$525.93 (-49%)

预售额下降了,因为我没有发布新文章来吸引新读者(Hacker News 那篇文章直到 1 月才发布)。不过,我的“被动销售”仍在增长,这是一个积极信号。12 月,我的预售额接近 500 美元。如果拿它和网站访客数相近的月份比较,5 月的预售额只有 241 美元,8 月则是 361 美元,所以数据呈上升趋势。我希望随着书的内容越来越完整,也有越来越多读者推荐它,被动销售能够继续增长,而不必依赖我每个月都找到一次成功的营销方式。

为我的书增加区域定价

我在 11 月开展黑色星期五促销时,一位读者发邮件告诉我,即使打七折(20 美元),对于阿根廷来说,一本书的价格仍然难以负担。他问我是否考虑过区域定价(regional pricing)。他提到,Steam 游戏在阿根廷的定价通常比美国低 50%,所以我觉得这可以作为一个很好的参考基准。

我通过 Stripe 收款,但在 Stripe 控制面板中找不到区域定价选项。我在 Stripe 的知识库中找到了一篇名为《实际应用中的地理定价:为什么重要以及如何实施》的文章。读到标题时我非常高兴,直到看完整篇文章,才发现他们忘了写“以及如何实施”这一部分。

所以,Stripe 倡导区域定价,却实际上没有提供这一选项。这再次提醒我,Stripe 是“除了其他所有支付处理商之外最糟糕的支付处理商”。

因此,对于那位阿根廷客户,我采用了一次性流程,手动为他创建了一个折扣价自定义支付链接。在操作过程中,我意识到可以直接用阿根廷比索设置价格,这样他就不必支付货币兑换费了。我将价格设为 22,000 阿根廷比索(约 15 美元),他似乎对价格和结账体验都很满意。

这位读者建议我公开提供区域定价,至少应覆盖巴西和印度这类开发者人数众多、但购买力相对较低的国家。

即使 Stripe 原生不支持区域定价,自动化实现我手动完成的事情似乎也不会太难。我读到了Sebastien Castiel(塞巴斯蒂安·卡斯蒂耶)为自己的课程实现区域定价的文章,这又让我找到了Wes Bos(韦斯·博斯)关于同一主题的文章

Sebastien 分享了许多技术细节,但他的方案大量依赖 React,而我的网站使用的是原生 HTML 和 JavaScript。他还依赖折扣码,我不喜欢这种方式,因为这意味着大多数客户都会看到有一个自己无法享受的特殊优惠。

我花了几个小时,用一个云函数实现了一个方案:它会即时确定合适的价格,并动态创建 Stripe 结账链接。后来我意识到可以预先计算好所有内容,从而不再需要服务器端逻辑,于是删除了云函数。

我的实现方式如下:

  1. 手动获取 Stripe 支持的所有国家/地区和货币列表。
  2. 编写脚本从世界银行获取数据,计算列表中每个国家的购买力平价(purchasing power parity,PPP)
  3. 根据各国相对于美国的购买力计算折扣。
    • 例如,巴西的 PPP 比美国低 54%,所以可以获得 54% 的折扣。
  4. 过滤掉 PPP 与美国相差在 15% 以内的国家(折扣太小,不值得费心)。
  5. 过滤掉折扣为负数的国家。
  6. 将折扣上限设为 75%。
    • 否则,埃及的价格会变成 4 美元,扣除兑换费后我大概只能拿到 3.50 美元。
  7. 为列表中剩余的每个国家自动生成对应国家的 Stripe 价格对象和 Stripe 支付链接。
  8. 将所有国家放入网站上的 HTML 下拉菜单中:

用户只需选择自己的国家,该国对应的 Stripe 购买链接就会启用,用户也可以使用本国货币付款。

我采用的是君子协定,所以不费心做 IP 地理定位或 VPN 防护。我会隐藏每个国家的折扣,以免人们专门选择最便宜的选项。而且,使用各国本地货币定价的好处之一是:如果有人作弊,选择了并非自己本国货币的地区,他们会在兑换费上损失一些钱。

这些数字感觉不太准确。严格按照 PPP 计算,美国 30 美元在埃及相当于 4 美元,但我怀疑在埃及,实际上不可能用 4 美元买到面向程序员的非盗版书。

Wes Bos 当时只是让读者告诉他他们认为公平的价格,所以我也打算试试。请发表评论,或给我发邮件,告诉我在你的国家,面向开发者的书通常售价是多少。

创建我的第一个 Flutter 应用

12 月,我发布了《我对 MeshCore 离线消息功能的第一印象》。我对这项技术感到兴奋,但发现客户端全都是闭源的,这让我很失望。

于是,我决定暂时停止探索 MeshCore。不过,MeshCore 贡献者 Frieder Schrempf(弗里德尔·施伦普夫)在我的文章下回复了这一有趣的观点

我在这个话题上有很多想法和你相同。就我个人而言,我认为 MeshCore 的价值在于协议,而不太在于固件、应用等软件实现。[…] 如果 MeshCore 作为一种协议取得成功并得到广泛使用(目前看来确实如此),那么维护良好的开源实现自然会出现(至少我是这么希望的)。

我认同 Frieder 的看法,并想:“也许我应该直接写一个开源 MeshCore 应用的概念验证(proof of concept)?”

其实,当时已经有一个 MeshCore 应用的概念验证。官方 MeshCore 应用的开发者 Liam Cottle(利亚姆·科特尔)此前曾为 MeshCore 编写过一个 Web 应用,作为官方版本的原型。他开发官方的专有 MeshCore 应用后,就弃用了这个原型,但原型的源代码仍然可用,而且它已经具备我所需要的大部分功能。

我想知道把这个原型移植到移动端会有多难。MeshCore 不适合作为 Web 应用使用,因为它需要访问 Bluetooth,还需要离线模式。我听说大家对 Google 的跨平台移动开发方案 Flutter 的评价还算不错。我猜想,借助 LLM,我应该可以在不需要太多干预的情况下,成功把 Web 原型中的代码移植到 Flutter。

我的计划是分三个阶段,让 LLM 为这个原型创建一个 Flutter 移植版:

  1. 使用 Playwright 为原型 Web 应用编写端到端测试(end-to-end tests)。
  2. 将原型实现移植为 Flutter Web 应用,同时保持端到端测试不变,以确保功能对等(feature parity)。
  3. 为 Flutter 项目添加 Android 构建。

计划确实奏效了,但每一步都比我预想的更加笨拙:

  • 在为原型编写端到端测试之前,我必须先将它改为使用语义化 HTML 和 ARIA 属性,因为很多输入标签只是裸露的 <div>
  • 我无法保持 Playwright 测试不变,因为 Flutter 实际上不会为 Web 应用输出语义化 HTML。它会创建自己的 Flutter 专用 HTML 方言,并将所有内容绘制在 HTML canvas 上。不知为何,大多数 Playwright 元素定位器仍然可以工作,但我不得不对测试做大量 Flutter 特有的修改。
  • 即使有 LLM 的帮助,我也花了很长时间才弄清楚如何使用 Flutter 构建 Android 软件包
    • Android 的构建系统 Gradle 在 NixOS 上存在很多问题。我不断遇到一些神秘错误,最后发现原来是它缓存在我主目录中的过期数据导致的。
  • Flutter 让通过 Bluetooth 通信变得出人意料地困难。在 Web 上(至少在 Chrome 中),调用 navigator.bluetooth.requestDevice 基本上就能免费获得这一功能;但使用 Flutter 时,你必须使用一个专有的第三方库,并自行编写设备选择器界面。

我原以为这会是一个轻松的周末项目,几个小时就能拼出来。30 个小时和 200 美元的 LLM 使用额度之后,我终于让它运行起来了。

在真实 Android 设备上运行我的 MeshCore Flutter 应用

然而,就在我让自己的 Flutter 实现与原型达到功能对等的那天,我准备去 Reddit 分享它,却发现有人刚刚分享了 meshcore-open,一个使用 Flutter 实现的 MeshCore 客户端。它和我的想法完全一样,但实现得好得多。

有人抢先一步让我很失望,但同时我也松了一口气。根据我短暂使用 Flutter 的经历,我恨不得尽快摆脱 Flutter。我本来就只是想做一个概念验证,希望其他人接手,所以现在有了一个功能丰富的开源 MeshCore 客户端实现,我很高兴。

也许 MeshCore 需要的是一个跨语言库

在开发 MeshCore Flutter 应用时,我必须实现解析 MeshCore 设备到客户端消息的底层逻辑。MeshCore 有一份公开规范,定义了 MeshCore 的点对点协议,而且这份规范本身也相当宽松。但对于运行 MeshCore 固件的设备如何通过 Bluetooth 或 USB 与配套客户端(例如 Android 应用)通信,还有另一套未公开的协议。

事实上的参考实现是 MeshCore 固件,但它把点对点协议逻辑、设备到客户端的协议逻辑和 UI 逻辑混杂在一起,并且把实现分散在代码库中互不相干的不同位置。

例如,MeshCore 客户端可以通过 Bluetooth 从 MeshCore 设备获取联系人列表,但它必须将原始字节反序列化回联系人。由于没有用于解码消息的库,每个 MeshCore 客户端和库都在各自重复实现同样的功能:

我从这些实现中注意到:

  • 它们不得不使用 32 之类的魔法数字,而不是引用某个权威位置中定义的常量。
  • 它们都没有为解析器编写自动化测试。
  • 它们把不必要的底层工作带入了高级语言。例如,每个实现都在存储 outPathoutPathLen 变量。这是 C 实现遗留下来的产物,因为数组并不知道自己的大小。而在 JavaScript、Python 或 Dart 这类语言中,你不必手动跟踪数组大小。
  • 它们没有仔细检查数据,所以会毫不在意地传递垃圾数据,例如负数路径长度或超出地球范围的 GPS 坐标。
  • 它们全都忽略了 flags 字段,尽管 flags 本应表示哪些字段已填充。至少在点对点消息中应该如此。对于设备到客户端的消息,flags 似乎没有意义。

我最初想到的是使用 protobufCap’n Proto 这样的协议库重写这部分逻辑,但目前我看不到以向后兼容的方式集成第三方库的办法。

那么,如果我用 C 编写一个 MeshCore 设备到客户端协议的核心实现呢?我可以添加特定语言的绑定,这样我们就不必分别为 Dart、Python、JavaScript 以及其他任何你想使用的语言编写完整的独立实现。

于是,我开始编写自己的 MeshCore 客户端库:

这个库还没准备好作为概念验证进行演示,但已经很接近了。

MeshCore 维护者完全有可能不喜欢这个想法;如果得不到他们的支持,这个项目基本上就会胎死腹中。但我还是做了,因为我以前从未尝试过编写跨语言库,而这是一段有趣的经历。

我上一次尝试从 Python 调用 C 代码是在 20 年前,当时必须使用 SWIG。那时候,这感觉既痛苦又不太正规,而如今看来已经改进了 80%。

我非常希望核心实现使用 Zig 而不是 C,但我发现阻碍太多:

  • Zig 尚不能编译为 xtensa 架构,而大多数 MeshCore 设备都使用该架构。
  • 大多数 MeshCore 固件项目使用的 PlatformIO 不支持 Zig。
  • Dart 的 ffigen 也许可以与 Zig 配合使用,因为 Zig 支持 C 的 ABI,但即使是让它与 C 配合工作也很困难。
    • Python 的 cffi 也是如此。

总结

完成了什么?

经验教训

  • 减少进行中的项目
    • AI 让启动新项目变得前所未有地容易,但要把项目变成可以投入生产的成果,我仍然是瓶颈。结果就是,我手上有很多正在进行、等待我审查后才能发布的项目。频繁切换上下文和跟踪任务会带来很大的精神负担。

下个月的目标

  • 发布《重构英语》的三章内容。
  • 发布我的 2025 年年度回顾(第 8 年)。

请求帮助

在你居住的地方,面向开发者的书卖 30 美元贵吗?如果贵,请告诉我,在你的国家,你预计会用多少本地货币购买一本类似《设计数据密集型应用》的编程书。

原文由 Michael Lynch 发布

本文章由 openai/gpt-5.6-luna 进行翻译