小猴偷米:技术,青春,与看不见的未来

本期 BGM「3人でいる時間(三人共处的时光)」来自「我的青春恋爱物语果然有问题。」。

时隔五年,《我的青春恋爱物语果然有问题。完》从春天推迟到了夏天,但终究还是来了。周五下午工作摸鱼的时候,重新下载了久违的 Bilibili,大老师还定格在雪乃的犹豫与结衣的追问之间,没有做出什么回答,只是转身买了两罐咖啡递给二人。

而我在周末的闲暇时光里,重新找出了《春物》前两季来回忆之前的剧情。这一次回味,就在心中反复打转,成为一个永远无法逃开的结。也许是因为告别了大学时对现充同样的厌恶,也可能是工作后开始渐渐明白一些事理,看完后就一直在心里默念着:自己的逻辑真的正确吗?正确的逻辑就一定会被大家接受吗?说话有考虑到别人的感情吗?

而另一个让我难以忘怀的痛点,是我忽然发觉自己没有青春,又或者因为这青春已经过去,而有种不曾存在过的错觉。

其一的原因不难理解,毕竟动画和小说只是杜撰,自己又没有那样的天分,而国内这样的应试教育环境下,高考和学习是最好的出路,也没有人的青春能像理想中那样美好;其二则反复萦绕在心里,渐渐成为一个谜团:青春的意义就在于当下,那么一旦青春结束,是否就该和从未存在过一样?假如把自己逝去的青春作为谈资,是否又有让自己仿佛重新经历青春的功效?

# 先声:困境前夜

2015 年我入学的时候,九龙湖校区还是一个刚刚通地铁半年的偏僻村庄,快递站还在东门外遥远的马路对面,而对面街上的小吃街还没有被城管赶走。而我对大学生活的憧憬,从看到学校之前就早早存在了——毕竟在高中生眼里,大学是可以放飞自我的地方,是不再唯学习论的地方。

高考完还未离家的时间里,混迹于新生群和班群,显然是快速融入大学生活的重要的一环,而在新生群和班群里维护 QQ 机器人,是其中最能让人乐此不疲的事情了。那段时间里,做了不少争强好胜的事,也渐渐听说了在学校里,有一个类似的学生服务平台,它的名字叫小猴偷米。

顺着小猴偷米微信公众号一路找下去,知道了这个团队的名字,看到了团队的官网介绍,依稀觉得找到了自己想去的地方:东南大学先声网;当时的业务有先声网主页、军训征文平台、小猴偷米微信/易信公众号、最具影响力毕业生投票系统等等。

▲ 当时的先声网官网,团队成员并没有实时更新,很多已经是毕业的老司机了。

开学没多久,军训的间隙里,我收到了东南大学先声网的面试通知;面试在南门附近的行政楼五楼某个会议室里,对面坐着三五名学长学姐。我介绍了自己做过 Android 开发的经历,酷鱼小工具开发的经历,Symbian 和学习机魔改破解的经历,学长们表示很新奇,很快让我通过了面试。

后来才发现,那是先声网最后一次在行政楼办公。 开学后的一段时间里,做了一个简单的 Entry Task 之后,团队的管理和活动计划便开始杳无音讯;年底的第一次正式例会,选在了某个借来的教室,主题是先声网的改名方案和产品线讨论。

那一次例会由当时的站长界神主持,在教七的一间教室里,界神在黑板上写下大家提出的改名方案,最后决定沿用公众号名称的小猴偷米作为团队的新名字,英文名维持 Herald Studio 不变;而后续产品线的开发方案,由于当时的团队只有一套后端 Tornado API 和公众号,由东神牵头确定了小猴偷米 App 的大致方向。

至于为什么在教室开例会,为什么要改名,这一切的源头都指向一个决策:东南大学先声网,在那一年被党委宣传部扫地出门了。

个中原因我不清楚,但对比 2001 年时的先声网,也许是因为先声网从师生共建变成学生团体之后,自然的自由化、去党委化,让校方不再有脸上沾光的感觉,最终决定把我们抛弃吧。

▲ 当时的先声网行政楼办公室一角,遗憾的是,我第一次亲眼看见这些小物件,是在行政楼的地下车库里。

# 小猴偷米 Android:成为马猴烧酒,拯救废社危机?(X)

在那次例会上,某诚信肥宅学长星神的游说,也成了奠定之后团队基调的一个插曲。也许是因为他极力推崇把团队英文名改为 Monkee Studio,而大家都不为所动,所以他的其他提议如学习前端(星神称之为「与我签订契约,成为马猴前端」)、重建团队网站等等,也都被一并忽略了——众所周知,在 2015-2017 年间,Native 开发远比 Web 开发要火热许多。

不过签订契约成为马猴烧酒什么的,倒是给教室里带来了一点快要废社的危机感。

没过多久就到了寒假。刚入学半年的我,也拗不过家里的劝说,回到扬州郊区的家里过年;春晚从姑且能看变成了无聊,也是从那年开始的。由于远道而来的祖父母占用了我的小床,父母在门口用衣柜隔开一块小空间,我就在那块小空间里自己睡折叠床,忍受着每天三四点钟起来遛弯的老人们,同时用电脑写我的 Android App。

▲ 课表助手实验版,由于不会架构,暂时把这个模块当作独立 App 来做;第四张图是当时的小猴偷米公众号。

根据东神和我的初步想法,这个 App 应该看上去很模块化,但并不需要太多动态加载能力;首页应该跟多数其他同类的校园 App 一样以宫格的形式展示。当时的我还不会做架构,恳求了东神帮忙搭了项目架构,同时自己开始研究跑操助手和课表助手两个模块,先以独立 App 的形式开发。

跑操助手和课表助手开发完成后,在与东神合并的过程中,我也一步步从观察熟悉架构、指出问题、调整一些与 Android 原生特性相关的用法,到最终把整个项目 take over,轻松接下了小猴偷米 Android App 的开发工作,而东神则对一些页面的排版布局和交互有一些直男的执念。

初代小猴偷米 App 另一个纠结点是首页的展示内容问题。最开始我们只想到在首页放功能宫格,而只有这些图标又会十分单调,又不知道该加一些什么其他的东西;后来我想到在首页加上时间顺序的信息流,各个功能模块可以按时间顺序抛出一系列的消息,首页则负责将这些消息进行聚合排序,我当时称之为时间线模式

而在实现的过程中,我发现很多功能并不应该产生过去的消息,而是展示当前正在进行的状态和即将要到来的事件,因此后来逐渐改为每个模块可以占据一个卡片,显示当前的状态。卡片有四种优先级:置顶(PINNED)、有内容有红点(CONTENT_NOTIFY)、有内容无红点(CONTENT_NO_NOTIFY)、无内容(NO_CONTENT),首页则根据优先级而非时间,对功能卡片进行排序展示,我称之为卡片模式

卡片模式这个词限制了我们的思维,让大家都觉得做成卡片的样子会比较直观,因此初代小猴偷米 Android App 是这样的:

▲ 简陋的初代小猴偷米 Android App,还残留着时间线模式的影子;第三张图是后来的 iOS 版课表助手。

开学后的 2016 年 3 月,在初代 App 成型、内测组招募的同时,我们也从界神那里接到消息,小猴偷米工作室得到了学生处某研究生老师的支持,将会挂靠学生处名下,同时在大活 510 得到一个三分之一教室大小的新办公室。之后的某个周末,团队几个大老爷们一起跑到行政楼五楼和地下车库,把先声网时代的杂物一坨坨的往大活搬。从行政楼到大活的直线距离有差不多 1.5 公里,我们就推着借来的推车,在路上延续着淳朴的商业互吹。

# 小猴偷米 iOS:你用过四手 iPhone 6 吗?

在 Android 版发布后的 4 月,大家常常收到 iPhone 用户的催更,而 iOS 开发的门槛也是一如既往的高。与此同时,住在我宿舍隔壁楼的学长浩神联系到我们,向我们介绍了他开发的 iOS 版仿小猴偷米界面;从那时起,浩神就成了小猴偷米工作室里非正规方式加入的一名新成员,而我在看了基于 Swift 1 的 iOS 版代码之后,感叹于 Swift 的简洁,开始打起学习 iOS 开发的主意。

不过我直到那时,活了 19 年,还没有摸过 iPhone 呢。

最开始的一个月里,暂时先通过虚拟机解决了基本的开发问题,把课表助手独立 App 的开发实验在虚拟机 + 模拟器中率先做了出来;而没有真机、没有显卡驱动的卡顿体验,始终不是权宜之计。

当时我自己使用的手机是上文提到的 MX4 Pro,而电脑是高考后在扬州某电子市场买的阉割版 Dell Inspiron 13 7348 超极本,它便宜、轻薄,有着容易发黄掉皮的仿金属外壳,和从海外版 1080p 阉割下来的 768p 触摸屏。在我的求助下,浩神答应帮我制作 Hackintosh 安装 U 盘,在几次从我宿舍到隔壁楼宿舍的迂回奔波、上淘宝买了一张博通网卡之后,这台快要千疮百孔的电脑终于迎来了它的新生:成功安装了 Mac OS X Yosemite。

而手机的问题,我在当时的某朋友群里,花 1900 元的积蓄买回来一部四手日版 docomo iPhone 6。说它是「四手」是因为群友 A 买的二手,转手给了群友 B,群友 B 又转卖给我,因此到我这里是四手。

这部 iPhone 对我来说如获至宝——从 4 月 17 号到 26 号,十天的时间里,我对着 Android 版自己写的 Java 代码人肉翻译成 Swift,把 Adapter 改成 DataSource,把 Listener 改成 Delegate,把 Lambda 改成 Closure,把 Activity 改成 ViewController,思想和变量名完全照搬,完成了小猴偷米 iOS App 的第一个版本。

废寝忘食的十天里,每天白天在课上带着电脑偷偷写代码,老师经过就一个三指切屏,晚上在室友睡觉之后兀自 debug 好几个小时,都是想尽快把 iOS 版完成,让自己已经投入的努力不至于白费。

▲ 小猴偷米 iOS App v1.x 的最终效果,图为首页第一屏、课表助手和考试助手。

写完之后才发现,本来不知道怎么实现卡片式布局,歪打正着做成了 iOS Grouped TableView 的样子,结果却比卡片好看。之后的 Android 版本也开始向 iOS 的设计靠拢,布局更加紧凑,也许是当时全国校园 App 中颜值最高的一款了。

iOS App 找了某学长的开发者账号借壳发布后,曾经坚定处于 Android 阵营的我发现,iOS 真流畅,动画真爽,音质真好,系统真省心,还能跟同学每天交流使用心得,增进感情。 于是,这部 1900 元买来的四手日版 docomo iPhone 6,成为了我的主力机。从 2016 年的春季学期到暑假,iOS 版成为了更加频繁得到更新的版本,打开 Android Studio 的次数一天比一天少。

不过,潦草的学习上手路线也直接导致了问题,照搬 Java 的设计模式,导致大量十个以上的变量和闭包抱团形成循环引用,形成严重的内存泄漏;全 Storyboard 的做法也让 Xcode 变得越来越卡……

▲ 一直懒得移植到 Android 的学期切换、Widget 和一次性显示所有周课程的课表概览视图。当时用的壁纸真好看

2016 年的暑假,在维护小猴偷米 App、等待 App Store 审核制毒 的充实经历中度过。而后来的秋季学期里,在院学生会宣传部升任副部的经历,让我认识了我后来的女朋友,开启了一段新的故事。

# 小猴偷米小程序:本来没打算学前端……

2016 年末到 2017 年初,发生过一件在一念之间改变我人生轨迹的事。

不久前学校某商业创业团队的 CEO 曾经来到小猴偷米在大活的办公室,跟我们介绍了他们项目的情况,并且希望挖我过去做开发,当时我是拒绝的:忙于考试,没有时间,而且不喜欢创业团队的氛围。几周后,和女朋友出去看电影,却发现自己没钱了。这一没钱不要紧,我找到了当时来挖我的那位学长朋友,对方答应我去担任 iOS 开发,每月发放 1000 元薪酬;而对当时每月只有 1800 元生活费的我来说,这 1000 元可解燃眉之急。

而一开始是 iOS 开发的我,在新的团队里逐渐变成了杂耍,随着微信小程序的发布,团队也宣布 All in 小程序,我就又变成了小程序开发,跟当时另一位本行做客户端的学长一起。

当时的画风非常搞笑:两个客户端开发对着小程序文档,以为是原生,写了半个月的 <scroll-view>,变着花样获取屏幕宽度高度往上面怼,结果突然发现,小程序页面本来就是可以滚动的,不用加 <scroll-view>……

然后俩人恍然大悟:这不就是我们一直不想学的前端吗?

于是,我是从小程序入门的前端,跟今天我的其他同事比起来,还是太嫩了。小程序学完之后,顺理成章学会了 Vue,本来因为初中时折腾 PHP 的痛苦经验而让我厌恶的前端,就这样向我敞开了大门。回到小猴偷米之后,我也用比较基础的小程序能力,完成了小猴偷米的小程序版本。

▲ 小猴偷米最初的小程序版本。

最初的小程序版本完全按「小」的理念来实现,相比 App 而言,查询功能只占据可以横向滑动的一栏,重要数字外透,点击可以展开具体信息,无需打开新的页面;不过代码实现上就太 Hack 了。当时的代码参见改为套壳前的最后一个版本

值得一提的是,当时微信小程序还没有提供 web-view 组件,也不能展示富文本,这个版本的小猴偷米小程序则借助第三方小程序组件化引擎,实现了服务端将通知新闻转换为 Markdown,小程序端拉取后渲染成富文本展示的特性。

▲ 小猴偷米 App v1.9(on App Runtime for Chrome)、小程序、小程序 2018 新春版全家福。

# 小猴偷米 WebService 3:作为站长的责任,也许是重构

随着产品线的增多,一个一直埋藏在背后的问题逐渐暴露出来:后端。

当时小猴偷米的后端沿用着先声网三年前的 Python 代码,逻辑冗杂零封装,尤其是爬虫的部分,光是发一个请求,就要洋洋洒洒写二三十行代码。代码里各个模块维护着自己的当前学期常量,类似的还有开学日期、特殊的用户名密码等魔法常量数不胜数。曾经发生过一个惊掉我们大牙的事情:一个学期结束前的最后一周,我们接到用户反馈,发现微信公众号里的空教室查询系统还停留在上学期。

服务器的架构也同样不够直观:小猴偷米用的是先声网时期接管的体育系服务器,在其中开着一个 Ubuntu 虚拟机,对外提供服务,而体育系服务器虽然因为需要提供微信公众号服务而开放了外网,成为内外网之间数据沟通的桥梁,却很容易在特殊时期与外网失联,晚上 7 点一到,整个服务都挂了,只能靠外网的服务器下发一些无济于事的通知公告,十分鸡肋且浪费服务器性能。

这不禁让我思考:阻碍系统重构的阻力究竟是什么?——无论是什么,一定不应该是我。

2018 年的寒假,又一次待在某个亲戚家里,借着对 Koa 的一知半解(扫了一眼文档,只记住了 Logo 长什么样子),搞定了一套封装所有公共逻辑的中间件栈:第一层的跨域中间件,第二层的监控和日志中间件,第三层的请求体和返回包格式化中间件,第四层的旧版接口兼容性中间件,第五层的短路保护中间件、认证中间件、管理员权限中间件、redis 缓存中间件、学期信息中间件,以及最内层的路由中间件。

其中路由中间件使用了自己开发的全自动简易文件路由 kf-router,一个文件代表一个路径,路由无需统一注册,方便快捷,易于上手;数据库使用了自己开发的 sqlongo,在轻便免安装的 sqlite 中实现更简洁的操作。后者时至今日已经被真正的 MongoDB 取代,前者则因为简单方便的开发方式,一直保留至今。

大概是 4 月 17 日,经过一波数据迁移,新的 Koa 后端正式代替老的后端,成为一个新标准,而爬虫接口的开发则变得非常简单

至于服务端具体的功能开发、服务器架构的调整,借助于那一年招的新人们,也都顺利完成了。新的服务端直接放在外网,同时作为 WebSocket 服务器,允许内网中的爬虫集群连接,并能向爬虫集群分发内网抓取任务;而 Herald WebService 3 项目也成了(可能是)先声网有史以来短期内最多人参与共建的后端项目。

即便如此,直到今天,我依然没有学会整个 Node 后端的精髓——也许是因为我的做事方法更倾向于根据背后运作的原理,自己造一套远离世俗的普通轮子,而不是花时间去学最多人用的那个轮子吧。

以上这些,作为我在小猴偷米工作室做的全部内容的概括;其他诸如每张轮播图的制作过程、PWA 版、早期给 App 用于版本升级和统计等用途的 App Service 服务端、以及第三方合作的物理系平台、计软红包雨等项目,略去不表;上文中所提到的项目均为我参与开发的早期版本。

2017 年,陪伴我们两年的界神毕业,经过校招加入了腾讯 AlloyTeam,一开始做 QQ 群相关的 Web 业务,之后团队转向了 TIM 在线文档,也就是今天的腾讯文档;2018 年的寒假,经界神内推(放水),我也顺利加入了这个大家庭,并在实习一年后转正,毕业后,小猴偷米的事也就渐渐交给了学弟。

学弟则忙于如何教学弟的学弟零基础入门前后端。

由此开始的小猴偷米,也许再也没有之前那么强的技术氛围,再也招不到可以完全胜任某项任务的新人,而组织关系上,也逐渐背起了官方化的重担;我也不再执念于我的产品会被后人如何更新迭代,我只是希望这座我一手搭建起来的技术架构,有一天能遇到一个有能力推倒它的人。

# 结语:如果这是强者的游戏,请让我退出

▲ 也许是 2013 年的照片,在某个干净的会议室;而今天的我们已经失去了会议室,也许也即将失去办公室。

入职腾讯后,把工作交接给下一任学弟的间隙,当时接纳我们的学生处老师离开了学生处,选择了继续读研,不久后传来消息,小猴偷米的大活办公室要被收回。这次是学弟出面,拉来了学院、网络中心、校会等等好几个部门,算是顺利完成了第二次搬家;而从学弟口中我也得知,学生处老师对我本人颇有意见,说我「在走廊看到老师不问好」。

如果是这样,我恨自己没有主动站出来驳斥:我跟学生处每位老师都只正式见过一面,虽然认识对方姓名,但在公共场合下,且对方在打电话或与其他学生讨论问题的时候,并不适合与这样疏远的人打招呼。如果把站长(部长)不打招呼视为排挤一个学生团体的理由,则颇为可笑。

那两年,团委和学生处清理了一大波学生社团,大活也不再是大学生活动中心,成了大学老师活动中心,而且还是不教书的、不能称之为老师的老师。——至于其深层次原因,相信互联网是有记忆的。

我们曾经依存于整个学校最高地位的组织,却也因为「用不到了」而被驱逐;曾经靠脆弱的人情关系挂靠的地方,也借口眼神和礼节的不伦而不欢而散。这些惨痛的经历都无时无刻不提醒着我,一个愿意伸出手接纳你的强者,总有一天也会让你无家可归。

因为他们是强者,他们有一万种言辞为自己辩护,却没有一颗与你平等对话的心。

在这片土地上,今天的学生社团的境况,大抵如此。

# 参考资料

东南大学先声网 Web Archive

利用爬虫技术能做到哪些很酷很有趣很有用的事情?jayjliang 的回答

哪些大学有像华科大dian团队,冰岩作坊,联创团队那样的计算机团队?DreamPiggy 的回答

校园APP,不是鸡肋就是弃妇?

掌理之死