最近每天早上醒来的第一件事就是看邮件,做开源这么久,好像突然变忙起来了,之前从来没有过的分身乏术的感觉也涌现了出来。

有段时间不写博客,就会浑身难受,实在没写的就更新下近况。― laixintao

那就水篇文章来谈谈我最近做开源的体会吧。

一个性能非常拉垮的 Markdown 解析器

我在 2018 年的时候造了一个 Markdown 的轮子用来解析、渲染 markdown 文档,名字叫Marko。其实我不爱造轮子,如果有能用的修修补补也就用了,但有个需求,我实在是没找到合适的可用的。我希望能方便地给 markdown 解析器增加自定义的元素和解析逻辑,但现有的流行的库都有不同程度的不足:

  • Python-Markdown, 文档极其简陋,我不知道有谁成功地给它写了扩展。
  • mistune, 大佬 lepture 的作品,速度相当快。但它的基于树状正则匹配的解析逻辑1限制了扩展的可能性,你只能加一些小打小闹的扩展,文档里给的扩展例子,就是恰好能用的之一,想改动解析逻辑,几乎是不可能的事。

更重要的原因是,这两个都是基于最原始的 John Gruber 提出的 Markdown spec——没有 spec,这就造成了在很多 corner case,有可能渲染不符合预期,甚至 crash。当然,正是因为选择了一个相对自由的框架,才能在性能方面一骑绝尘。

再加上当时我正则表达式出了师,膨胀的一比2,我先是写了个玩具的 TOML 解析器试手,然后就想自己写一个 Markdown 解析器。现在想来,我一个编译原理都没学过的野路子,连 FSM 和 BNF 都不知道,简直是不自量力。因为我的目标是:

  • 遵守CommonMark spec3
  • 解析和渲染过程独立,方便自定义两者任一阶段,以及观察 AST 结果
  • 统一所有元素的接口,方便 subclass 扩展

直到动手做了之后我才知道我错了,CommonMark 的 spec 之变态导致我(菜得)只能(想出)线性解析4,而且我大胆猜测 BNF 也不好使。每做一步解析之前,要先试探着往前,不行再回退回来,正则用得又到处都是,结果就是性能不能指望了,我一跑 benchmark 崩溃了,比最慢的还慢。但这样得到的好处就是 90% 的元素解析5和 100% 的元素渲染接口我都统一了,这已经足够应对大部分扩展的需求。

本以为就做为练手项目算了,不打算宣传,却发现无意间也帮助到了很多人,他们可以用 Marko 来很快扩展出自己的解析器,比如这个以及这个,这是做开源最欣慰的时刻吧。

渐渐庞大的 PDM

与之相反,PDM 却是一个在同类产品中有性能优势的项目。最近几乎所有业余时间以及部分摸鱼时间都在搞这个。曾经我经常在发版之后进入贤者时间,像观赏一个艺术品一样审视把玩,以为没有什么大的改进可做了。这样的日子一去不复返了,用户量上升以后需求和 bug 还是渐渐出现,关键是我还觉得它们提得好有道理啊,得做啊。欣慰的是,我最近可能收获了若干铁杆用户,疯狂在 Repo 里互动,而我正在做一个大 feature,目前来看反馈非常正面,我突然有种「我为用户提供了价值」的感觉。

还有两位老哥,热衷于给 PDM 加 type hint,提了个这么大的 PR,改了 61 个文件,我哭笑不得,这可咋 review 啊,自动生成的 type hint 得人工过一遍才行吧。

这样功能一直加下去,PDM 的代码终究会变得越来越坏,我自认为现在的代码库己不算容易维护的代码了。而且,PDM 的 bug 都出得非常匪夷所思,就是一眼看不出来线索的那种,主要是路径也挺深,还各种不科学:只有 Linux 挂, 只有 Mac 挂, 只有 Python 3.7 挂,批跑挂单个跑不挂,本地跑不挂上 CI 挂,时挂时不挂……天天被教育,我真的够够的了,时常要深呼吸一下,眺望远方,才能重振旗鼓。探其原因,都是我要对 Python 环境动手脚而自己作的死。

做开源,就是这样欣慰与闹心共存着的吧。


  1. 这个词是我发明的,意思是先把文档划分成块级元素,再在块级元素中解析子元素及行间元素,是一个分治的思想。

  2. 一则轶事:腾讯的第三面,就问了一题正则,这不撞了枪口。

  3. CommonMark 并非唯一的 Markdown spec,其它的还有vfmd

  4. 先解析完前面的,才能解析后面的。

  5. 还有 10% 天杀的元素,它们的解析相互依赖,无法分隔出来,堪称CommonMark 最恶心的部分