距离毕业已经不远了。
那时我已经做了三年软件,作品累计下载量超过千万,每个月有数十万人在使用。这个数字足以让我在自我介绍时显得体面,却不足以回答一个最现实的问题:
毕业以后,我该去找一份工作,还是继续做自己的软件?
大学快结束时,我很容易把人生看成一道必须立刻作答的选择题。考研、就业、考公、创业,每个选项旁边似乎都站着一群已经抵达终点的人,向我证明那条路更加正确。没有人告诉我哪一步得分,哪一步失误,也没有一份标准答案能保证我从此不再后悔。
那天晚上,我没有想明白自己的未来。
第二天早上,我只是从床上爬起来,打开电脑,新建了一个项目。
项目里什么都没有,只有一个空白窗口。我在上面写下:
新建文件夹,预计几年后
Salt Player for Windows™
我截了一张图,配上一句“又来画饼了”,发到了频道里。

现在回头看,那个窗口真正重要的地方,不是它后来变成了一款软件,而是从那天开始,我第一次认真地把一段尚未确定的未来,交给了自己。
一、我没有选择更勇敢的路
大家好,我是不要糖醋放椒盐。

我是一名 00 后独立开发者,软件工程专业,也是椒盐音乐 Salt Player、椒盐笔记、青盐云听、Hearusy Spectrum、心事卷轴等软件的开发者。

有人会把独立开发想象成一种很浪漫的生活:没有领导,不用打卡,可以自由决定做什么产品、使用什么技术。
真正身处其中以后,我很少觉得自己是在追求浪漫。
我也考虑过求职,只是心思始终不在那上面。我不愿意准备面试,不愿意把接下来的几年投入一个自己并不期待的方向。市场上当然存在更容易变现的软件生意,但我知道,如果只是为了尽快赚到钱,去做一些自己根本不认同的东西,几年后的我大概会非常讨厌当时的自己。
这并不意味着独立开发比就业更加勇敢,也不意味着坚持热爱就一定能够得到回报。
我只是恰好更愿意承担这一种代价。
二、一年计划的开始
Windows 桌面端有很多开发方案。我早年接触过 VB,后来也尝试过 WPF、UWP 和 WinUI 3,最终选择了 Compose for Desktop。

这并不是一个足够稳妥的选择。
我从 2021 年开始关注 Compose,也清楚它并不成熟。对于 Windows 软件来说,选择 JVM 技术栈,在一些人眼里几乎等于主动走进桌面开发的鄙视链:
Java 写的软件,吃内存、启动慢、运行卡,内存占用差不多可以和 Electron 坐一桌。

但独立开发者做技术选型,不能只看一项技术在论坛上是否体面。大公司可以让不同团队分别维护不同平台,我只有有限的时间、资金和精力。跨平台复用对我而言不是漂亮的技术愿景,而是非常具体的成本问题。
也许我很容易迷恋“正确答案”。
我比较框架、语言和架构,希望从中选出一条最先进、最优雅、最不会后悔的路线。但真正的软件工程很少提供这种答案。每一项技术都在用某种优势交换另一种缺陷:性能换效率,灵活换复杂,统一换妥协。技术选型不是找到没有缺点的方案,而是判断哪一种缺点,自己有能力长期承担。
我后来认真了解了 JVM,也重新认识了 HotSpot。它当然存在局限,但它本身仍然是一项软件工程上的奇迹。
我的态度逐渐变成了:
先相信,再质疑。
先真正用它做出一些东西,再判断它究竟行不行。因为开发者最容易高估的,是自己对一项从未真正使用过的技术的理解。
三、一个人做软件的进度失控
项目刚开始时,我给自己安排了一条看起来很完整的路线。
先完成基础 UI,再设计数据库和音频引擎,之后处理播放队列、歌词和设置,最后集中测试。我计划用一年时间把它做完。
当时我已经做了很多年移动端本地播放器,自认为足够熟悉播放器的功能和结构。我以为 Windows 版本只是工作量更大,不会困难到失控。
但拥有移动端经验,并不意味着能够跨过桌面端的坑;熟悉播放器,也不意味着熟悉 Windows。
最先拖慢项目进度的,甚至不是什么复杂的音频问题,而是 UI。

我对界面细节非常在意。一个间距不统一、一个按钮状态不自然、一个动画不够连贯,我都可能花很长时间处理。我希望软件符合 Windows 11 的视觉语言,也不希望它只是一个被放大到电脑上的移动端应用。
可 Windows 是一个背负着漫长历史的系统。不同年代的组件同时存在,系统软件自身的设计并不完全统一,文档中的规则与实际表现也可能存在差异。有时我只是想实现一个看起来理所当然的效果,却要在框架、系统版本和自定义实现之间反复试验。

独立开发者也不只是一名程序员。
还要同时处理产品、设计、测试、排期、运营、宣发和用户反馈。写出一个功能或许只需要几天,但决定它是否值得被加入,需要考虑它会影响多少用户、增加多少复杂度,以及未来由谁维护。
大一上软件工程课时,老师问我们:“什么是软件工程?应该怎样应对用户需求?”
我当时回答,软件工程就是尽可能完成用户提出的所有需求。
老师笑着说,随着课程继续学下去,我可能不会再这样认为。
后来我确实不再这样认为了。
每增加一个开关,我都要考虑它与其他设置怎样组合;每加入一种特殊行为,都要在之后的版本里继续兼容;今天为了一个场景随手写下的分支,两年后可能会变成谁也不敢碰的逻辑。
功能写完,并不代表这项工作结束了。
很多时候,维护才刚刚开始。
一年计划就是这样一点点失去边界。不是因为我遇到了某个完全无法解决的问题,而是每个问题都能够解决,只是所有问题加在一起,远远超过了一个人能够控制的速度。
四、我曾把完美,误认成认真
项目进行到一段时间后,我申请了 Steam 商店页面。

审核通过的消息,是我下宿舍楼丢垃圾时收到的。我站在垃圾站门口看着手机,高兴得差点滑了一跤。

那些真正改变一件事情的时刻,未必总是庄重的。它可能只是宿舍里一个空白窗口,也可能是一条在垃圾站门口收到的通知。可从那一刻起,原本只存在于电脑和聊天频道里的想法,开始拥有了一个真实的商店页面。
与此同时,项目进度仍然在不断延后。
当时我认为,自己只是对品质要求比较高。后来我才发现,精益求精和迟迟不愿交付,有时只有一线之隔。
我往往会在自己能够控制的地方表现得格外勤奋。调整间距、修改动画、重构代码,这些事情都有清晰的反馈:投入时间,很快就能看到结果。但让用户真正开始使用、接受反馈、承认自己的判断可能错误,意味着作品会离开你的控制。


于是,“还不够完美”就成了一个非常合理的理由。
它让你看起来始终在认真工作,也让你暂时不必面对真实世界。
到了 2025 年上半年,我又花了一个多月,把音频引擎推倒重来。
我希望统一 Windows 和安卓端的音频能力,用一套更漂亮的架构减少重复维护。
真正开始以后,我发现两个平台在音频设备、输出链路、应用生命周期和异常恢复上,都有各自的规则。
接口当然可以起成相同的名字,运行时的行为却不会因此变得一致。
为了维持表面上的统一,我开始在公共层中加入越来越多的平台判断。复杂度没有消失,只是从两个相对独立的模块,被搬进了一个更难理解的抽象层。
一个多月过去,代码看起来更整齐了,发布日期却更远了。
现在回头看,这次重构不能算成功。
我高估了抽象能够解决的问题,也低估了两个平台之间真实存在的边界。有些重复来自设计不足,但另一些差异本来就无法消除。
不过,这段时间也没有完全浪费。
PC 和安卓端的歌词组件、播放队列以及部分基础 UI,在这个过程中逐渐完成了统一。我没有得到最初设想的结果,却更清楚地知道了哪些东西可以抽象,哪些边界必须尊重。
真正让我不舒服的,不是这次重构没有达到预期。
而是我开始承认,我有一部分所谓的认真,其实是在逃避交付。
五、把软件交给真实世界
PC 版本最初,我不愿意太早把一个不完整的软件交给用户。因为我觉得我做 Android 版本 3 年多了。
我以为自己可以先在内部解决大部分问题,然后直接发布一个足够成熟的版本。
很快,我发现我错了。
我的电脑运行正常,并不代表软件真的正常。不同显卡、驱动、系统版本、缩放比例、输入法和第三方软件,都可能改变程序的行为。用户看到的也许只是一个按钮没有反应,开发者却可能要从 UI 框架一路追查到系统接口,最后发现问题来自某个显卡驱动。
用户只看见水面上的一个气泡,开发者必须一路潜下去,找到究竟是哪一层正在漏气。
后来,我建立了测试群,开始定期发布测试版本。四千多位测试用户使用不同的设备和环境,让我见到了大量靠自己永远无法发现的问题。
测试不是发布前寻找几个 Bug。
真实世界永远比开发者脑海里的模型复杂。一个人当然可以写出程序,但只有当程序能够在无数个不由你控制的环境里继续工作时,它才真正开始接近软件工程。
我也逐渐理解了宣发与期待之间的关系。
独立开发者没有大型发布会,也很难在产品上线当天突然获得巨大的关注。持续分享开发进度,能够让用户提前认识产品,看着它从空白窗口逐渐拥有播放界面、歌词、数据库和音频能力。
但每分享一次,期待也会跟着高一点。
用户看了一年的精美截图,自然会认为,正式版本已经足够成熟。
有时我发出一张完成度很高的播放页,别人看见的是一款快要做完的软件;而我知道,截图以外还有很多页面没有适配,很多设备没有测试,还有一些问题,甚至只是暂时没有发生。
后来我不再只想展示最好看的部分。
什么已经能用,什么仍然不稳定,什么暂时做不到,都应该尽量说清楚。
不然项目越受关注,真正发布时的落差就越大。
六、软件上线以后
后来,因为审核机制上的一些阴差阳错,软件在一个没有预料到的时间点上线了。随后,双平台也陆续通过审核。

我曾经想象过发布当天的样子。
也许我会很激动,也许会反复刷新后台,觉得长时间的投入终于有了结果。
真正发生时,却没有什么狂欢。
我只是坐在电脑前,看着后台的数据开始变化。
有人下载,有人打开软件,也有人第一次把自己的音乐文件拖进去。之前所有只发生在测试群里的问题,从这一天开始,可能出现在更多我完全不认识的人面前。
发布以前,我总能告诉自己,软件仍在开发,界面还可以修改,架构也还有机会重来。
发布以后,这些解释突然变得没有那么重要。
用户点击播放,音乐有没有响起来,才是他真正遇见的软件。
后来我看到有用户说:
“这个软件界面很干净,用起来很舒服,没有那些乱七八糟的打扰。”
我不知道他说这句话时正在听什么。
也许电脑放在大学宿舍的书桌上,也许是在出租屋里,也许只是某个很普通的晚上。
上线以后,我花在维护上的时间越来越多。
新功能可以截图,也容易写进更新日志。稳定却很难展示。播放没有中断,程序没有崩溃,系统更新以后仍然可以正常工作,这些事情发生时,通常不会有人特意提起。
因为它们本来就应该发生。
可要让这些“本来就应该发生”的事情持续发生,背后需要处理大量不漂亮的工作。稳定不是保守,它是开发者对用户作出的一种长期回答。
重复测试,修改兼容逻辑,分析偶发错误,为很少见的设备增加判断,然后在下一个版本里重新检查一遍。
以前我更容易被“做出一个新东西”吸引。
后来我发现,能够把一个已经做出来的东西长期维护下去,也不比创造它更容易。
虽然我肯定也会遇到些问题:
从一个窗口,到另一个窗口
后来,我受邀来到上海,参加华为鸿蒙系统开发培训。
住在酒店时,我站在落地窗前,看着城市里的车流和灯光,突然想起了 2024 年 4 月 23 日的那个空白窗口。
一个是电脑屏幕里刚刚创建的窗口,一个是真正望向城市的窗。
它们之间隔着数据库、音频引擎、失败的重构、四千多名测试用户、平台审核、好评与差评,也隔着很多个我怀疑自己究竟能不能把这件事做完的晚上。
我也没有因为做出了这款软件,就突然找到了毕业以后所有问题的答案。
我仍然会怀疑自己的判断,也会担心收入和未来。看到别人走上更加稳定的道路时,有时也会想,另一种生活会不会轻松一些。
只是那个写着“预计几年后”的空白窗口,最终没有一直停留在一张截图里。
它已经出现在了很多人的电脑上,在某个普通的晚上,为一个我不认识的人播放一首歌。
这件事没有替我回答未来。
夜雨聆风