文/一个在大型项目里寻找过起点的人
前阵子我接手了一个大项目,代码仓库有几千个文件,光模块目录就有三十多个。我第一次把代码拉下来,在IDE里打开的时候,光是索引就花了快两分钟。
我想熟悉一下这个项目,于是从头开始看。从根目录的README开始,然后看pom.xml,看配置文件,看启动类,看核心模块的代码。看了大半天,我脑子的地图还是乱的——这个项目太大了,而且文件之间的依赖关系非常复杂,你从任何一个文件点进去,都能跳转到一个你之前没见过的目录。
后来我突然想到一个笨办法:这个项目的”主入口”是哪个?就是那个最初运行起来,输出第一行日志的文件?找到它,说不定能帮我理清这个项目的”起点”。
于是我用全局搜索,搜”Hello”。搜出了八百多个结果。大部分是单元测试里的测试数据、错误日志里的占位符、注释里的示例代码。找了很久,才在一个叫Application.java的文件里找到了一行注释,写着:”// 从这里开始,世界你好”。
下面是一个空的main方法。那个”输出Hello World”的代码已经被删掉了,只剩一行注释,孤零零地留在那里。
我盯着那行注释看了好一会儿。那个项目现在有几万行代码,支撑着每天上百万的请求。它的第一行代码,就是被这行注释所替代的那个Hello World。它已经不在了,但写它的人留了一个标记,告诉后面的人:”这里曾经是一个开始。”
项目越大,越容易忘记”从哪里开始的”
我发现一个规律:任何一个项目刚开始的时候,你都很容易找到那个”起点”。目录结构是新的,文件数量个位数,所有东西都摊开在表面,一目了然。那个输出”Hello, World!”的文件,通常就在根目录下面,名字就叫main.xx或者app.xx。
但随着项目长大,这个”起点”会被层层包裹起来。新的功能加进来,新的模块建起来,配置被拆分到不同的文件里,启动逻辑被封装到框架里。当初那个”一打开就能看到”的Hello World,被埋到了深处。你翻半天翻不到它,甚至可能它早就被删了,被”优化”掉了,因为”这个示例代码没有用,正式环境不跑这个”。
我自己的项目也一样。去年翻一个五年前写的项目,想找最初那个”hello.go”文件。翻了半天,最后在一个叫archive/old_stuff/的目录里找到了。它被移到了一个”古董文件夹”里,旁边还有一堆已经不再使用的旧代码。
那个文件的内容还在,三行,什么都没变。但它已经不在主代码树里了,需要特意去翻才能翻到。它就像一个被搬离了故居的老人,住在一个偏远的房间里,偶尔有人路过看一眼,大多数时候没人记得它还在。
那个”保留Hello World”的项目,后来怎么样了
但我也见过做法完全不同的项目。
有一个开源项目我关注了很多年。它的代码也很大了,几万次提交、几百个贡献者。但你任何时候去它的主分支,都还能找到一个叫examples/hello_world的目录。里面放着一个完整的、可以独立运行的最小示例——就是那个最初的Hello World,每次新版本发布的时候,这个示例都会跟着更新,确保它在新版本里依然能跑。
我后来跟那个项目的一个维护者聊过,他说这是他们从第一天就定下的规矩——不管项目怎么变大、怎么重构,必须保留一个”最小的、可独立运行的示例”,永远放在代码仓库的固定位置。新贡献者加进来,先跑这个示例,确认环境没问题,然后再开始开发。
他说这个规矩救过他们好多次。有一次重构改了底层API,所有的集成测试都通过了,但那个Hello World示例跑不起来了——因为新API在某个边缘场景下的行为变了,而集成测试没有覆盖到这个场景。如果当时没有那个”最小的示例”做基准验证,那个bug可能会在某个用户的生产环境里才被发现。
他说:”Hello World就像我们项目的压舱石。它单独放在一个角落,不做任何花哨的事,就保持最简单的形态。但只要它能跑,我们就知道项目的”底座”是好的。如果连它都跑不起来,那一定是出了根本性的问题,跟业务逻辑无关。我们可以直接停下手头所有的事去修它。”
那个被”优化”掉的Hello World,后来被找了回来
另一个故事来自一个朋友的公司。他们有一个内部工具,用了很多年,功能越来越多,代码越来越复杂。后来有个新同事入职,想跑一遍整个流程,按照文档去做,发现文档里写的”第一步,运行示例程序”的那个示例程序已经不存在了。
他去找老同事问,老同事说”那个啊,好几年前就删了,因为’正式环境不需要示例代码'”。新同事说那我想验证环境怎么办?老同事说”你直接跑我们的核心流程就行,那个更全面”。
新同事去跑核心流程,配置了各种环境变量、数据库、缓存、消息队列,折腾了大半天,终于跑起来了,但他完全不知道”是不是通了”——因为核心流程太复杂了,输出了几百行日志,里面有各种初始化信息、连接状态、注册成功、加载完成。他不知道哪一行是”我成功了的标志”,哪一行只是正常的启动信息。
后来那个朋友知道了这个事,觉得挺遗憾的。他花了一个周末的时间,硬是从版本控制系统里把那个被删除的Hello World示例找了回来,是四年前的一次提交里删掉的。他把那个示例恢复到项目里,更新了一下依赖版本,确认能跑通,然后提交了一行新的注释:”欢迎回来。你被删了四年,但项目里一直有人记得你。”
他告诉我这件事的时候,说了一句话我到现在都记得:”有些东西删了,功能上没影响。但人需要看到一个’最小可确认的标志’来告诉自己’我准备好了’。那个标志在不在,直接影响了新人对这个项目的第一感受。”
下次写项目的时候,留一个”根目录下的问候”
我现在自己写项目,不管大小,都会在根目录保留一个叫hello或者example或者playground的目录,里面放一个最简的Hello World。
这个目录不参与项目的主流程,不会被构建工具打包进去,不会出现在正式服务里。它就安静地待在那里,等某一天有人需要”从最简状态开始验证”的时候,可以直接找到它。
可能是新同事入职,想确认环境。可能是换了电脑,想验证配置。可能是项目出了大问题,需要确认是不是底层链路断了。也可能是某天深夜,你写代码写到累的时候,打开那个文件,跑一遍,看到输出,然后告诉自己:”底盘还在,我可以继续。”
这行代码放在那里,不占用什么资源,但给人留了一个”随时可以回去的起点”。在越来越复杂的软件世界里,这个起点的存在本身,就是一种安慰。
我后来在那个大项目的Application.java的注释下面,加了一个文件,就叫HelloWorld.java,里面三行,main方法打印那行字。
然后我在那行注释下面补了一行:”你走了很久了。我把你找回来了。以后你就住这里,不走。”