a PR for kew

这大概是我第一次认真地向一个开源项目提交 PR。
在上篇博客里我提到,我装上了 Debian。
如果你注意过那张截图的右侧,会看到一个运行在终端里的音乐播放器 ——kew

album

这次的故事,就从它开始。

并不算宏大的开端

起初我对它很满意。运行很轻巧,界面也干净,几乎没有多余的东西。

但用着用着,我还是察觉到了一点不太舒服的地方。

在听歌的时候,有些文件名看起来有点奇怪 —— 数字被去掉了,而它们本来就是歌名的一部分。

比如,像 25時の情熱.mp3, 会被显示成 時の情熱.mp3

除此之外,还有歌词滚动偶尔不同步的问题。

当然,我也可以只提一个 Issue,然后等待。
但那天我多看了一眼,发现这是一个几乎纯 C / C++ 的项目。

于是我想,或许可以自己试一试。
说不定事情并不复杂呢。


本来以为会很简单

最开始我真的以为,这只是一个 if 的问题。
顶多加几层判断,把逻辑包起来,大概就好了。

但很快我意识到,事情并没有这么简单。

这个播放器有一个自动去除编号显示的功能。可以把一些命名格式统一的文件的编号去除,同时保留顺序。

但是我们其实并不知道,一个数字到底是不是 “曲目编号”。

1 test.flac1看起来像编号。 那1s intro.mp3 呢。
2002.mp3 呢?
1999_07_09Prince.mp3 呢?

歌名本身是没有罪的。


于是我开始测试

我试图搞清楚目前处理到什么情况。

我把那个负责处理文件名的函数单独拎出来,
写了一堆输入,一行一行地看它们的输出结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
===============================================================
stripTrackNumbers=1 | Original Song Filename
------------------------------ | ------------------------------
test.flac | 1 test.flac
Song.mp3 | 01-Song.mp3
Track.wav | 02. Track.wav
Under Score.ogg | 3_Under_Score.ogg
Review.mp3 | 5-Review.mp3
,Comma.mp3 | 6,Comma.mp3
Dot.mp3 | 7.Dot.mp3
Test.mp3 | 8 Test.mp3
Another.mp3 | 9.1 Another.mp3
1s intro.mp3 | 1s intro.mp3
mp3 | 2002.mp3
7 09Prince.mp3 | 1999_07_09Prince.mp3
.mp3 | 10-100.mp3
66-90=2.flac | 8.32.666-90=2.flac
=0.mp3 | 50-50=0.mp3
+2=5.gugugaga | .2+2=5.gugugaga
NoNumber.mp3 | NoNumber.mp3
Percent.mp3 | _90 Percent.mp3
is the universe.mp3 | 42 is the universe.mp3
===============================================================

所谓的 “自动修正”,本身其实是有风险的。
它会在你毫无察觉的时候,替你做出决定。

我停下来,看了很久。然后又想了一会儿,

我觉得这件事可能是一个过度设计,或许就不该由播放器来处理这个。
曲目编号是否需要隐藏,应该是用户自己的烦恼。

于是我加了一个开关,把这个 feature 收了起来。


顺手的事

另一边,我也在处理歌词不同步的问题。

我发现原来的解析逻辑是用一串正则完成的,只能识别 [mm:ss.xx] 这样的格式,
却无法正确处理 [mm:ss.xxx] 的情况。

这显然是问题的源头。

于是我用上了好久没碰过的指针,重新写了一段解析逻辑。
出乎意料的是,一次就跑通了。

而且效果异常地好。

格式化的诱惑

在折腾歌词同步功能的时候,还发生了一个小插曲。

当我改完代码,看着满屏风格各异的缩进和括号,手里的 clang-format 蠢蠢欲动。
我想着:“既然改都改了,不如顺手把源码都格式化一遍,让它变得清爽一点?”

这个念头诱人极了,我几乎没怎么犹豫,就带着一种 “我来帮你打扫干净” 的豪迈感,在项目根目录运行了全量格式化命令。
瞬间,成百上千个文件被修改,git status 的输出像失控的瀑布一样冲刷下来。成百上千个文件被标记为已修改。Diff 里的绿色行数爆炸式增长,滚动条瞬间变得细不可见。

看着那壮观的变更列表,我不得不意识到:
这简直就是灾难

协作不是个人秀。 保持最小的改动范围,是对维护者和其他协作者最大的尊重。

于是,我默默执行了 git checkout .,乖乖地撤销了所有修改。
重新只改了逻辑部分,然后小心翼翼地只格式化我修改的那些行。
世界清静了,Diff 也清静了。

不过由于我在一些代码块的外面套了一层 if 的壳,VS Code 的 Git Diff 显示因为缩进变化,就跟智障一样无法识别出原本一致的代码,我只能用 git diff -w 来查,评论区求一个更好的处理策略。

这算是留下的一点教训。当然,我学到了随便‘不要格式化整个仓库’。不多更重要的领悟是在写作过程中,每个人都应该‘记得格式化自己的代码’。一个项目的整洁,终究要靠每个 contributor 的自觉。


Review 来得很快

维护者很快测试了这个 PR。
歌词解析的改动没有问题,而且测试者似乎很满意现在的同步效果(因为他打了两个感叹号 w)

但是他同时指出,那个新增的开关似乎失效了。

这是一个很敏锐的反馈。

我也吓了一跳。明明我在本地测试时是好的?

于是我又花了一些时间,顺着 settings.c 里那些层层叠叠的初始化函数,终于抓到了 bug。
原来是读取偏好设置的逻辑,不仅覆盖了用户的设置,还因为初始化的顺序问题,把我的开关 “吃掉” 了。

bug 本身并不难修。
但在修好它之后,看着那些测试用例里被误伤的 2002.mp3
看着那些被切断的年份和乱掉的歌名。

即使开关修好了,这个功能本身可能也是一把双刃剑。

我没有试图把逻辑修完。那需要整改整个文件名处理的架构,对于目前的我来说,动静太大了。

我在代码里留了一个 TODO, 也写了一个 Issue,把那些奇怪的边界情况记录下来。

也许有些问题,不适合匆忙解决。


“This is truly joyous!”

最后的回复只有一句话:

This is truly joyous! I have no more issues with this PR!

我盯着这个算是 “LGTM” 的句子看了几秒钟。

这是一次很小的 PR。 没有新功能,也没有炫目的改动。

但它让我清楚地感觉到:
开源真的是一件很有趣的事情


重构的延续

PR 合并后,我并没有就此停下。那个被我暂时搁置的 "过度设计" 问题,一直在脑海里挥之不去。

我注意到代码中还存在一些耦合问题。比如 *format_filename* 函数,它直接访问全局的 appstate 来获取用户设置:

1
2
3
if (get_app_state()->uiSettings.stripTrackNumbers) {
// 剥离轨道号的逻辑
}

于是我做了一个小小的 follow-up 重构:
把状态判断移到调用层,通过参数传递。

首先修改 format_filename 函数:

1
2
3
4
5
// 修改前
void format_filename(char *str)

// 修改后
void format_filename(char *str, bool strip_track_numbers)

然后更新所有调用点。在 common_ui.c 中:

1
2
3
4
5
// process_name 函数内
format_filename(output, strip_track_numbers);

// process_name_scroll 函数内
format_filename(output, strip_track_numbers);

在 UI 层调用时,需要从 ui->stripTrackNumbers 获取设置:

1
2
3
// playlist_ui.c 中
process_name(buffer, filename, max_name_width, ui->stripTrackNumbers, true);
process_name_scroll(buffer, filename, max_name_width, is_same_name_as_last_time, ui->stripTrackNumbers);

这个把逻辑向 calling function 迁移的方法论,就是依赖注入 (Dependency Injection)

重构后函数变得更纯,也更清晰。

不过,维护者选择了一个更简单的实现方式,并亲自补上了代码。算是一个简单的后日谈


把心情记录下来

我不知道以后还会不会继续给这个项目提交代码。
但我还是想把这次经历记下来。

不是为了证明什么,
而是提醒自己:

我曾经认真地对待过这样一件事。而且,意外地做得不错?

今天放一首纯音乐,据说其对应了一封 4 月 10 日的信件。我很喜欢其中的这句。

我即將踏上人生最後的旅程,帶著最低限度的行李、相機、用來寫信的筆和墨水。

4/10