第16章
十一月的第一周,数据结构课布置了一个大作业。王老师在投影仪上放出题目的时候,教室里响起了一阵低沉的抱怨声。题目要求实现一个简易的学生信息管理系统,功能包括增删改查、文件读写、按成绩排序,难度不算高,但代码量不小,一个人做完大概需要一周。
“组队完成,两人一组。”王老师推了推眼镜,“下周三交。”
教室里立刻热闹起来,大家开始转头找人组队。关系好的自然凑到一起,落单的四处张望,用眼神互相试探。殷洛燃坐在倒数第二排,没有转头,没有张望。他在等课程系统分配组队名单——王老师说了,没找到队友的人系统会自动分配。
两天后,系统分配结果出来了。殷洛燃打开课程序的时候,看到自己的队友那一栏写着一个名字。
傅承霄。
他把那个名字看了两遍,退出页面,又点进去,确认自己没有看错。傅承霄。系统自动分配。他在心里把这个过程拆解了一遍——选修这门课的大一学生有四十七人,单数,必然会有一组是两人。自动分配的算法大概率是按学号顺序匹配,傅承霄的学号在他前面几位。这不是他安排的,不是韩膺安排的,是教务处那套老旧的课程系统在某个深夜自动运行的脚本决定的。
但结果是一样的。他们被分到了一起。
殷洛燃拿起手机,打开和傅承霄的聊天框。上次对话还停留在约讨论室的那几句。他打了一行字,想了想,没发。他退出聊天框,关掉屏幕,把手机扣在桌上。他不需要发消息确认什么,他知道傅承霄已经看到了分配结果,也知道傅承霄不会主动来找他讨论分工。不是不在意,是他的沟通习惯就是这样——你来找我,我会回应,但我不一定会先开口。
果然,晚上七点,傅承霄的消息来了。“作业。你负责哪部分?”
殷洛燃看着这条消息,发现傅承霄的提问方式和他预想的一模一样——不是“你想负责哪部分”,不是“你觉得我们怎么分工”,而是直接给出一个选择题,选项在对方的回答中生成。这是一种高效的协作方式,不给犹豫留空间,不给推诿留余地。
“我对文件读写那块还不太熟,你写那个,我做界面和排序算法,中间的数据结构部分我们一起。”殷洛燃回复。
“行。明天下午四点,图书馆402。”
和上次一样的句式。时间,地点,句号。
殷洛燃盯着那个句号看了一秒,回了一个字。“好。”
第二天下午四点,殷洛燃推开讨论室的门时,傅承霄已经在白板上画好了程序的结构图。模块划分、数据流向、接口定义,用不同颜色的白板笔标注了优先级。第一阶段的开发任务用黑色笔写,第二阶段用蓝色,预留的扩展接口用红色圈了出来。殷洛燃站在门口看了两秒,然后走进去,把书包放在桌上,拉开椅子坐下来。
“你这画了多久?”他问。
“半个小时。”傅承霄没有抬头,正在白板的右下角写函数命名规范。“先把接口定下来,后面不用来回改。”
殷洛燃点了点头,打开笔记本,把白板上的结构图抄了下来。他的字迹比平时工整,因为这张图他之后一周每天都要看。
两个人开始写代码。傅承霄负责文件读写模块和数据结构的核心实现,殷洛燃负责用户界面和排序算法。讨论室不大,两个人各自对着自己的屏幕,键盘声此起彼伏,偶尔停下来讨论接口的传参方式或某个边界条件的处理。
殷洛燃很快就发现了自己和傅承霄的工作方式之间的差异,以及它们如何像齿轮一样咬合在一起。傅承霄写代码之前会在脑子里把整个逻辑推演一遍,然后一气呵成地写出来,很少回头修改。他的代码注释很少,因为变量命名已经说明了大部分意图,不需要额外解释。殷洛燃写代码的方式更直接,他会先写出一个能跑的版本,然后再优化。他喜欢在代码里加注释,不是为了给别人看,是为了让自己记得当时为什么这么写。
这两种方式单独拿出来都不是最优解,但放在一起,刚好互补。傅承霄负责搭骨架,保证程序的核心逻辑正确、高效、可扩展。殷洛燃负责填血肉,保证用户界面友好、交互流畅、错误提示清晰。写到第三天的时候,他们的模块已经可以联跑了。傅承霄把文件读写的接口暴露给殷洛燃的界面模块,数据从硬盘到屏幕的整个链路跑通的一瞬间,两个人的屏幕上都显示了正确的输出。
“通了。”殷洛燃说。
“嗯。”傅承霄靠进椅背里,目光从自己的屏幕移到殷洛燃的屏幕上,在输出结果上停了一下,然后移开。他的表情没有太大的变化,但殷洛燃注意到他把键盘往旁边推了几厘米,这个动作意味着他暂时不需要打字了,意味着当前阶段的目标已经达成。
第四天,他们在白板上讨论排序算法的实现。题目要求按学生的总成绩排序,如果总分相同则按学号排序。殷洛燃想的方案是用快速排序,时间复杂度最坏情况下也不是问题,因为数据量只有一百条。傅承霄听完他的方案,沉默了几秒,然后在白板上写了一个多关键字排序的复合比较函数,用了一个条件运算符就把逻辑写完了,比殷洛燃预想的简洁了不止一个量级。
“你这个写法比我那个净多了。”殷洛燃看着白板说。
“你的方案也没问题。”傅承霄把白板笔放回笔托,“只是多了几行。”
多了几行。他说的是代码,但殷洛燃觉得这句话在说别的东西。傅承霄对“简洁”的追求不只体现在代码里,他的思维方式本身就是经过压缩的——把复杂的逻辑拆解到最简,去掉冗余,只保留必要的部分。和他的时候,殷洛燃发现自己也在不自觉地被这种思维方式影响。写代码之前会多想一步:这个变量有没有必要?这个判断能不能合并?这个循环能不能提前终止?
第五天,所有的功能都实现了。他们提前两天完成了作业,比班上大部分组早了一个周末。殷洛燃把程序跑了三遍,测试了所有的边界条件——空文件、重复数据、非法输入——全部通过。傅承霄把他的模块做了最后一次代码审查,在文件读写那里加了一行错误处理的代码,然后合上了笔记本。
“成了。”傅承霄说。殷洛燃靠在椅背里,看着白板上那些被写了又擦、擦了又写的痕迹。五天前白板还是净的,现在上面全是字和箭头,有些地方被擦得不彻底,还残留着上一版的笔迹。他看着那些痕迹,意识到这五天里他几乎没有想起过韩膺。
不是故意不想,是没空。每天下午四点到六点半,他和傅承霄在这间讨论室里写代码、画图、讨论、调试。两个半小时里他的注意力全部集中在眼前的问题上——这个函数的返回值类型应该是什么、那个循环的边界条件写对了没有、文件读写的缓冲区应该设多大。这些问题一个接一个地出现,一个接一个地被解决,节奏紧凑到没有空隙想别的事。
直到作业完成的那一刻,那些被屏蔽的东西才重新浮上来。
殷洛燃从书包侧袋里掏出手机,按亮屏幕。未读消息。韩膺,三条。第一条,“进度。”第二条,“最近怎么样。”第三条,没有文字,只发了两个问号。三条消息的时间跨度是三天,第一条是周三发的,第二条是周四,第三条是周五下午。今天是周六,他的手机安静了一整天。韩膺大概已经习惯了这种安静,不再发了。
殷洛燃看着那三条消息,发现自己是第一次忘了汇报。之前每一次接触——图书馆、食堂、选修课、讨论室——他都会在当天或第二天整理成文字发给韩膺。不是因为他享受汇报,是因为他知道如果不主动发,韩膺会来问。与其被动回答,不如主动汇报。这是他给自己定的规则。
但这周他忘了。不是故意拖延,不是选择性遗忘,是真的忘了。周三他在写文件读写的接口,周四他在调试排序算法的边界条件,周五他在做集成测试和代码审查。每天下午两个半小时的讨论加上晚上各自完成自己模块的时间,他的一天被切割成以小时为单位的小块,每块都有具体的任务和截止时间。韩膺和他的消息被挤到了队列末尾,然后被新的任务覆盖,沉到了底。
殷洛燃把三条消息都标为已读,没有回复。他把手机扣在桌上,屏幕朝下。傅承霄正在收拾东西,笔记本电脑合上,充电线绕好塞进书包侧袋,白板上的内容他拍了照,笔托上的笔按照颜色排列整齐。这些动作他已经看过很多次了,但今天看的感觉不一样。不是因为动作本身变了,是因为他意识到自己记住了这些动作的顺序——合电脑、绕线、拍照、排笔。这个顺序在过去五天里重复了五次,每次都一样,精确到连绕线的圈数都没有变化。
傅承霄把书包拉链拉上,站起来。“周一我把文档补一下,你这边界面再加个退出确认。”
“好。”
傅承霄走到门口的时候停了一下,没有回头。“这周辛苦了。”
他推门走了。走廊里的声控灯亮了一下又灭了,脚步声越来越远。殷洛燃坐在空荡荡的讨论室里,白板上还残留着他们的笔迹。黑色、蓝色、红色,三种颜色交错在一起,像某种只有他们两个人能读懂的地图。他在那张白板前站了一会儿,把傅承霄写的函数命名规范从头到尾看了一遍,然后拿起板擦,从左上角开始,一格一格地擦。白色板擦在板上移动,发出细微的吱呀声,那些箭头、方框、公式、函数名被一点一点地抹去。他擦到右下角的时候,看到傅承霄用很小的字写了一行备注——“注意文件打开失败的情况”。那行字在笔托的上方,位置很偏,如果不特意去看本不会注意到,但傅承霄写了。他在别人看不到的地方也写了注释。
殷洛燃把那行字也擦掉了。
白板恢复了净,反着光灯的白光,像一块空白的屏幕。殷洛燃把板擦放回笔托,背上书包,走出讨论室。走廊里的声控灯又灭了,他在黑暗里走了两步,灯重新亮起来,白光照着灰色的地面,把他的影子投在墙上,长长的。
回宿舍的路上他掏出手机,打开韩膺的聊天框。那三条消息已经被标为已读,他盯着屏幕看了几秒,打了几个字。“数据结构大作业,和他一组,做完了。”
发送。他把手机塞回口袋,没有等回复。梧桐树的叶子落了大半,剩下的在风里沙沙作响。他走到宿舍楼下的时候手机震了一下,他拿出来看,韩膺的回复只有一个字。“好。”
殷洛燃把手机锁屏,推开宿舍楼的门。楼道里的声控灯又亮了,他一级一级往上走,脑子里反复播放刚才那几秒的画面——他站在讨论室的白板前,把傅承霄写的那行备注擦掉。他不知道自己为什么要擦掉它。那不是他的字迹,不是他的备注,不是他的责任范围。他完全可以留给下一个使用这间讨论室的人。但他擦了。擦得很净,不留痕迹,像它从来没有存在过。