我在开发 Puntgun 中学到的东西

#前言

Puntgun是我写的一个命令行工具,Python 语言,帮助推特账号管理自动化。 从2022年二月底开始写,到2022年十一月底碰到一个对我来说很难实现的编程问题, 加上当时马斯克成功收购推特,之后心思就不再它上面了。2023年2月9日,推特不再提供免费 API,算是帮我下手了结了这个项目。 (从十一月的新闻我就判断马斯克不倾向于开放生态,不利好我这项目,你看果然吧,但确实没想到能做这么绝。) 这是我第一个尝试运营社区的项目,然后事情还没开始就结束啦。😂 项目框架已经写好了,可以自定义规则,示范用的规则没写几条就碰到了那个难题,算是基本实现目标吧。

失败是成功之母,前提是记得总结经验,故有此文与大家分享,如果读文有感而发,请务必在评论区评论,让我学习一下你的经验。

#第三方开发风险大

虽然但是,推特一直是开放平台的标杆啊啊啊,oauth 和雪花算法都是它给业界贡献的,开发者 API 全,还有官方维护 API 库(没 Python 语言)。 我觉得任谁都想不到推特能在马斯克收购后转变这么大,或者我眼光浅没有从投资角度去分析推特居然经营不善到要被收购。 第三方开发风险大,嗯,这回栽个大的。

2022年初的选题思考:开发面向用户的应用型软件,比开发面向开发者的库更容易找到还没人做的空缺,也更容易吸引人气。 我是在手动 Block 各种奇怪 Follower 时发现这个选题的,像我这种小账号每次意外被大量 Fo,手动筛选 Follower 都要花半小时,肯定有很多用户苦于没法自动化管理。 Punt gun巨型猎鸟霰弹枪,用规则批量 Block,我看行。后来自然而然地扩展成多用途工具,mute、打印、调用 HTTP,想得很美。

以后慎重选择依赖平台做第三方开发,至少不能依赖商业平台,等于把项目的命拱手让给不可控风险。

#原型与便签

《程序员修炼之道》-第二章:注重实效的途径-原型与便签(翻了下书第一版也有这节)。 这节讲的是:如果使用原型实验可行性,记得贴个便签时刻提醒自己这是个需要无情丢掉的原型,不要投入太多,不要直接基于它做开发。 我读过这书,记得这节,但我犯了这个错误,错误导致了那个至少占50%责任的编程难题。

这个难题是这样形成的:

我这个工具会大量调用推特 API,确实是I/O bound 项目。Python 有现成又好用的 nio 包,asyncio。 我用的叫 tweepy 的第三方推特 API 库,提供两种调用,一种是blocking I/O,一种是asyncio 形式的。 2022年初,我不会 asyncio,然后我想先跑通请求验证一下可行性,之后再改成 nio,就选择了 blocking 形式的 tweepy API。 显然我没在显示器上贴便签,把这事给忘了,在开发过程中一直没替换成 nio。

这个工具的基本功能是“输入推文或用户信息,多个规则匹配,决定对其的操作”,是类似责任链模式的那么一种东西。 根据我的工作经验判断,这个模式很像应用网关的过滤器链,可以仿照着开发。 在我的 Java 开发工作中,reactive 形式的 WebFlux 库(或 RxJava 库)是合适又方便的框架。 Python 生态里有对标reactivex规范的RxPy框架,好,用它。

……直到我在开发将近完成时才发现 rxpy 和 asyncio 不能兼容。经过详尽搜索后(有兴趣请看我提的 issue),我面临着:

  1. 在 rxpy 的 asyncio 库中实现我需要但没有的 API | 在 rxpy 库中实现兼容 asyncio 的接口 =
    • 了解 Reactivex 规范的实现方式 +
    • 精通 asyncio +
    • 精通 rxpy 项目 =
    • 我还没这个本事,根据 issues,贡献这个库的人都有在其他语言的 rx 库中贡献或工作过的经历。
  2. 不考虑第一方兼容,两者独立运行,通过 threading 库或 multiprocessing 库协调 =
    • 首先得让它俩都正常跑起来(这很难) +
    • 精通这俩库中的一种 +
    • 实现一种数据传递方式,在两机制间传递信息(单用 Queue 只能通一边,怕是要上共享内存+锁那种,这两边至少把数据来回倒腾两次,没有上限)=
    • 俗话说的好,引入多线程就是引入至少一个 bug,还自己实现多线程方面的内容,我不会,也不想引入,日后维护纯添麻烦。
  3. 放弃 rxpy(因为不能放弃 nio),用 asyncio 重新写一遍关键代码(因为 asyncio 的特性,基本得全重写)。
    • 如果推特没宣布关 API,我打算用这种,但自己实现一种类似责任链机制也不简单。

幸好,推特没有给我这个赎罪(?)的机会。这个错实实在在是我亲手埋下的,请大家务必记得做原型就贴便签。

#认清需求后再推翻重写一遍

我在五月二号,开始开发两个整月后,把项目推翻重新写了一遍,我认为这个决定做对了。

大家可以翻一下各种著名项目的最开始的 commit,大概都差不多:模块划分频繁更改、各种 rename……说明开发刚开始,没人知道自己想做什么。 软件开发学说里有系统边界这个概念,但我们的项目需求是内生的,没人告诉我们要写什么。 怎么办,总之先写着,直到真正确定下想做什么之前全都是原型,是自己(dev)为自己(customer)写的 prototype,启发自己的需求。 等到你真正明白自己这个项目的系统边界了,再从头开始为这个目的而设计项目结构,别不舍得。 这里我再掉一下书袋,还是《程序员修炼之道》第二版,有个什么“(汽车)不要开出前灯范围”的章节,讲的是相关方面(推荐读这本书,很棒)。

#列个计划表

自己的项目啥都想要,正常。但是我推荐列一个计划表,比如用github 的 project。 自己的项目不会有排期,所以泳道图的堆栈式结构就足够了,用来给要做的事排优先级。 也可以加一个泳道存灵感,总之把长期开发中想到的东西都写在计划表里,给大脑工作区腾地方,也不怕遗忘。

根据我的经验,选择困难时,无论是不知道选哪个实现方案还是买哪件商品,消去法(列条件限制)比较管用。 用语言描述你的目标,列出各个方案,重点放在“这个方案会让项目增加什么复杂性|硬性限制”上,比如:

  • 这个方案能满足我哪些需要?
  • 这个方案我是要自己实现还是使用现成的库?
    • 自己实现,可行性?(时间等成本上)值得吗?
    • 引入现成的库会对我的项目增加什么新约束?(切记上文的教训)

可以先实现简单的,之后再做优化。简单的完成品比精致的半成品重要。 前提是它被封装地很好,不会对外部造成影响。 即使是自己在开发整个项目,封装依然可以帮忙节约大量的认知负担。 设计好外围的交互关系后,一次只专注于一个模块的内部设计。 拆分功能到足够小,小到能凭经验把它实现。

#注释和单元测试拯救记忆力

哈,这回可没人用覆盖率逼我写单测了,也没规定要求写那没人看的 word 接口文档。这回我的单测只能帮到一个同事——未来想重构的我。 我记性不好,很不好的那种不好,每天下班我要记一下自己在做什么,睡一觉如同内存掉电,没记录的话要坐工位上半小时才能想起来。 这项目,我就周末逮着点时间写写,要是不多写点单测和注释和 TODO,怕是每周看到代码都像刚接手的项目,不可能持续小一年。 至少对我来说,单测和注释(和计划表)是维持这个项目的一大重要支撑。

#技术栈不熟可以抄

隔语言如隔山,换门语言做开发并不仅需要学会语言本身,工程结构,编码规范,CI 中要用到的检查工具,开发、测试所依赖的框架……基本要全换。 自己一个个要素去搜,费时费力。有没有好办法?有,找一个已经成熟的和你的项目结构类似的项目,读+搜着学习它的工具配置和代码结构,抄到我们这边。 这个技巧我用过两次了,我的naming小工具是 Rust 写的命令行工具,抄的我记得应该是click, 这次的 puntgun 是 Python 写的命令行工具,抄的PDM。 通过抄成熟项目,避免了自己闭门造车搞出一个不成熟的开发环境(未知技术栈对我们来说是 unknown unknown 嘛,不看到就不会知道还有这么方便的工具),抄着学不丢人,半吊子才丢人。

#后记:之后干点啥?

经验总结到此为止,希望我的经历能对你开发个人项目有所帮助。

我买了块树莓派 pico-W,还买了块小显示屏, 试试 bare metal coding,大概会对各种计组知识有更深的理解吧,如果没放弃的话。

最近推特中文圈有个高手云集的讨论AI 编程理论可行性和对行业冲击的串, 我相信其中的观点比如“当 AI 创造的成本压得比维护现有代码低,维护就不复存在了”(就像家电维修成本高不如换新的), 然后我们短暂而自豪的 coding culture 大部分都是为了“更好地维护”这一目的设计的, 我对失去信仰的恐惧大概比对失去工作的恐惧还要更甚。 我想了两个转行方向,做 profiling 和做渗透测试,但经过咨询推友发现这两个方向都不能在“成熟 AI 编程”前提下站稳不受冲击。 大概唯一的慰藉就是看看现在的 AI 给出的错误百出的编程答案,心想这碗饭还能多吃几年。

整理心态积极拥抱变化吧,这也是我们这行的信条之一,与君共勉。

updatedupdated2023-08-082023-08-08