
接手一个项目的第一天,我打开目录树,从 src 开始一个个点进去,两小时后关掉编辑器,说不出这个服务启动的时候先做了什么。
这种情况后来出现过好几次,读开源库也一样。目录结构看着整齐,控制器、服务、工具分得清清楚楚,可它是按职责分的,不是按运行顺序分的。你想知道一次请求从进来到写库经过谁,目录树不回答这个问题。
我现在的第一步是找那个能跑起来的最小入口。一个测试用例,一个示例脚本,一个 main 函数,什么都行,条件只有两个:能跑,链路短。
最短的入口常常是测试,因为它自带输入和期望,不用猜怎么调用。
def test_create_order(client):
resp = client.post("/orders", json={"sku": "A1", "qty": 2})
assert resp.status_code == 201
order_id = resp.json()["id"]
got = client.get(f"/orders/{order_id}")
assert got.json()["qty"] == 2
把它跑起来,然后在几个关键位置打日志,一次就够。位置我一般只挑三个:请求进来的入口、真正改动数据的那一行、往外发消息之前。三个点足够把一次请求切成几段,哪一段慢了、哪一段根本没走到,对着日志一眼就看明白。
我接过一个订单服务,第一次请求打下去,日志里依次出现路由、参数校验、库存检查、价格计算、写库、发消息六个位置,跟目录里的十几个模块对不上。那十几个模块里有三个是重构之前留下的,两个只在某个早就下线的活动里用过。
跑一次能立刻分辨哪些模块是活跃的、哪些是死代码。目录树分不出来,因为死代码的文件往往比活代码收拾得更整齐,命名规范,注释齐全,看着像核心。
跑起来之后再看目录,感觉完全变了。原来我是一个文件一个文件读,读到哪算哪;现在是带着一次真实调用去对号,每个模块在链路里的位置都能挂上去。理解代码的顺序应该是从一次真实调用出发,不是从目录树出发。
跑起来还有一个好处,它比读全更容易建立地图,而且这张地图天然是准确的。读全不等于读对,很多人读完一个项目,记住的是命名习惯,不是调用关系。
所以我给自己定了个规矩:陌生项目到手前两天,不改代码,不接需求,只做一件事,找到最小入口,让它跑起来,跟着一次请求走完全程,再动手写第一行。如果连入口都找不到,说明这个项目缺一个可验证的起点,那要先补一个测试,再谈别的。