开发

上学期的「软件工程基础」,要求我们以小组为单位,完成给定的项目。由于是自由组队,我们几个认识的自然组成了一组,分到了「风行旅途」的开发任务。这是在线火车购票旅游系统,用于线上的火车购票、酒店预订、火车订餐,类似于12306(具体需求见此)。我们给其的英文名为「SwiftJourney」,因为「Swift」有「迅捷」之意。

项目初期,我们先确定了技术栈。我和saite负责后端,而前端由其他三位组员负责。他们自然选择了Vue,因为先前有过相关的开发经验。我们选择了Rust,因为saite对Rust比较熟悉,我在先前的寒假中也学习了一些。Rust的性能和安全性都很出色,比较适合我们的项目,也贴合了「Swift」(迅捷)的特点;不过它的学习曲线确实很陡峭[1][2]

先前的面向对象课程中,大作业是组队开发Java项目,我们选择复刻游戏《竞宝风云》(出自综艺节目《魔方新世界》)。不过,当时任务分配不甚均衡,且学期中还有其他事务,因此只能在最后几天连夜赶工,在最终展示时还出现了bug,不甚顺遂。汲取了先前的教训,这次我们一开始就制订了团队成员准则、分配了任务,还确定了每周进度目标。由于平时课程中就使用华为云的CodeArts,我们也就自然沿用了这个系统,管理项目并分配工作项。不过,我认为这个系统太过复杂了,每个用户有多种不同类别的ID,在不同操作中需要使用的ID类型都不同,且缺少明确指引,把其他人拉进项目也是难上加难,花了几十分钟才搞定。有次课下实践是使用CodeArts,不少同学也如此抱怨。

其实我对架构之类的宏观设计理解很差,只能依靠其他人,这可能是因为我过于专注细节的缘故。在saite的推荐之下,后端采用了领域驱动设计,以期更好地应对复杂业务逻辑,同时也是方便后续改造。不过其实我们都没接触过,只能根据网上的资料摸索。最开始,我根本不知道应该从何入手,后来在saite的指导之下,我才有了些许头绪。

saite后来如此总结道:

实际上,我在设计时根本不清楚软件工程的基本流程(当时「软件工程基础」课程才刚开始,还未完成对需求分析、概要设计、详细设计等内容的介绍),甚至并不清楚在着手写代码前总体设计的重要性。在看了「领域驱动设计」的大致概念与几篇教程后便直接开始创建数据库、编写代码(虽然同一学期还安排了「数据管理技术」课程,但当时的课程进度并未介绍到数据库设计理论相关内容,在设计项目数据库时,我甚至并不知道3NF等范式)。课程结束之后回顾,才发现我忽略了战略(Strategy)层面设计,例如划分限界上下文、统一语言(Ubiquitous Language)制定等,而只注意了战术(Tactical)上的设计模式[3],导致一些问题,例如Entity设计不合理(特别是关于火车订票的相关实体)、Domain Service间耦合程度太高、Application Service的拆分并未明确对应用例。虽然存在上述诸多问题,但由于「软件工程基础」课程只要求实现单体应用,服务、数据库表之间关联程度较大也不太影响结果。而在之后的「软件工程基础实践」(小学期)中,要求编写单元测试、拆分微服务(独立数据库),相关问题才完全暴露出来。

对我而言,另一个困难之处是对Rust并不十分熟悉。我只是把《Rust语言圣经》中的基础部分学习完了,而且也不敢说非常扎实。写项目时,我最头疼的就是类型转换。例如错误类型就有ServiceErrorRepositoryErrorGeneralError,使用到了anyhowtracing等库。虽然现在来看也没有什么,但当时我对特征对象也比较陌生,看代码就如同看天书一般,复制粘贴的其他文件代码也往往无法直接使用。此外,可能由于项目过于复杂,又或是Rust的相关资料比较少,AI也无法提供太多帮助。还有一处麻烦的地方是,如果需要更改某个服务所需的外部依赖,那就需要修改对应的泛型参数列表,散布多处,十分繁琐。尽管如此,我觉得Rust的报错信息相对清晰,能够帮助我定位问题。

在开发过程中,还遇到了烦心事。我负责收集、生成数据,包括车站、车次等。由于车次采用了真实数据,在生成火车餐时,产出的json文件大小接近1GB,然后使用Git LFS将其上传到仓库中。后来在saite的建议之下,还是采用了压缩包的方式。然而,原先的json文件始终占用着存储空间,即使我使用了强制推送,确保没有任何commit中有对该文件的引用,可仓库的体积仍然没有改变。而免费版的仓库只有1GB存储空间——过了几天,代码完全提交不上去了。当时查阅了华为云的文档,没有看到相关信息;联系客服并等待数小时后,也被告知无法删除。与之形成对比的是,GitHub的文档中就提到,可以联系支持人员删除Git LFS对象。当时担心工作项等丢失,于是选择充钱升级华为云仓库。此外,开发期间还遇到MinIO社区版的Console去除了多数管理、配置功能,因此我们一怒之下使用脚本自动化配置。

虽然最终并没有完全实现所有功能,但总体来说也还不错。后端在期末周之前就写完了,并且也没太多致命错误。中转、换乘查询的算法是由AI写的,速度很快,即便使用了中国大陆的真实数据,也能在一秒内给出结果。前端人员在学期中有些忙碌,不过紧赶慢赶,最终也是写完了。展示时,老师和助教都肯定了我们的项目。老师还惊异于我们的技术选型,尤其是为什么要用Rust,而不是常用的Java等。我当时直接引用「软件工程基础」课程PPT中的回答,「作为学生,应当积极尝试新兴技术」,老师对这个回答非常满意🐶。

查询车票界面

改造

软件学院在暑假末会开设「软件工程基础实践」课程(俗称「小学期」),要求在先前项目的基础上做改造。这应该也是学院的特色吧。这次主要的要求是将后端拆分为微服务,为后端写单元测试,并且要部署CI/CD流水线,执行集成测试、压力测试。其实我猜课程团队是希望前端成员负责流水线,但对我们来说,项目本身就使用了Docker,改造成k8s部署也并不复杂,使用AI可很快完成。所以对于后端的压力还是更大些。也因此,另一位刚学会Hello World的组员加入后端团队,负责写单元测试。他一直抱怨我们先前写的代码是「屎山」,还吐槽测试样例的mock很复杂。我前几天学习了如何写测试,但后来发现AI生成得非常好,根本就不需要改动代码。单从项目的角度来说,算是白费了力气。

然而,由于saite个人的身体原因,他在课程初期无法投入过多精力,导致后端进展受阻,尤其是拆分微服务。如前所述,我拙于宏观把控,只能等待他制订拆分方案。而前端虽相对轻松,但缺乏后端经验,也是心有余而力不足。至结束期限前几天,我们才制定出了明确的拆分方案,开始拆分。我们计划拆出7个微服务,如图所示:

从理论上来说,微服务的好处是可独立部署、降低耦合[4]。但由于已有的架构问题,按照我们的方法拆分微服务,基本就是将可在内存中完成的跨Service调用,改为使用HTTP在微服务之间完成。这不仅没什么好处,还降低了系统的整体性能,只是为了满足课程要求,是典型的「分布式单体」反模式。拆分过程可以说是苦力活,但AI并不能帮上忙:尽管有详尽的拆分方案文档,可它总是乱写一气。在展示的前一天,我们才勉强拆分完,随即发现系统有诸多bug。虽然一直修到晚上,但还是存在问题。直到展示结束后,才基本把问题处理完。

不过展示时,老师和助教都认为我们的项目挺不错。老师最后还问我们:「如果再让你们重新写一遍项目,你们还会选Rust吗?」我们都只是在笑,根本无法回答这个问题。虽然有队友开玩笑称,如果当初选择Python或是Java,就不会有这么多问题。不过saite觉得,架构设计的错误以及人员分工的不合理,才是问题的主要原因,而不是Rust。虽然从某种意义上讲,选择Rust也是导致人员分工不合理的原因之一。对我而言,如果选用其他简单的语言,确实能快速完成课程任务,但用Rust写这个项目,确实收获了许多。

saite还吐槽了此类团队作业。虽然明面上都有权重分配,但事实上也没什么作用,除非是一点都不做,否则权重基本上都是1。他还称,从之前的团队分工中的问题中得到的经验,在面临新的问题时总显得无用,最后回顾,还是每次都觉得犯了一堆错误(或许,这就是学习吧)。他还认为,在团队项目中,最好不要产生「有靠山」的感觉,这样会产生懈怠。据说这学期的「软件系统分析与设计」会有十余人的团队作业,真是令人头大。「人类在神圣的沉默当中学会历史」,项目结束后,我们很少再激烈地讨论当初的选择;而团队合作的种种教训,都已内化为我们无声的经验。


  1. Blanco-Cuaresma S, Bolmont E. What can the programming language Rust do for astrophysics [J/OL]. Proceedings of the International Astronomical Union, 2017, 12(S325): 341–344. DOI: 10.1017/S1743921316013168. ↩︎

  2. Perkel J M. Why scientists are turning to Rust [J/OL]. Nature, 2020, 588(7836): 185–186. DOI: 10.1038/d41586-020-03382-2. ↩︎

  3. Evans E. Domain-Driven Design: Tackling Complexity in the Heart of Software [M]. Upper Saddle River: Addison-Wesley, 2003. ↩︎

  4. Newman S. Building Microservices: Designing Fine-Grained Systems [M]. 2nd ed. Sebastopol: O’Reilly Media, 2021. ↩︎