独立开发中·📚18 篇日记·📝最近更新:2026年07月12日
🏚️

847行的函数和三个月不敢重构的我

一个函数从50行膨胀到847行。重构不是技术问题,是心理战。

水哥
2026-06-279 分钟阅读

我三个月不敢碰的那段代码

src/services/restaurant.service.ts 里,有一个 847 行的函数。名字叫 getRestaurantsWithEverything

它做的事情从名字就看出来了:"把所有的东西都查出来"。

async function getRestaurantsWithEverything(
  params: GetRestaurantsParams
): Promise<RestaurantWithEverything> {
  // 筛选条件:距离、评分、人均、菜系、标签、是否营业……
  // 关联查询:评论、点赞、收藏、优惠券、套餐、预约……
  // 排序逻辑:距离优先/评分优先/综合排序/人气排序……
  // 缓存逻辑:Redis 缓存 5 分钟,带版本号……
  // 总计:847 行
}

每次新加一个平台功能,比如"筛选支持宠物友好的餐厅",我就往这个函数里加 20 行。

三个月后,它变成了一个我完全不敢动的怪兽。

为什么不敢重构

1. 没有测试

这 847 行的函数没有一行测试。因为它做的事情太多了——它不光查数据库,还调 Redis,还调搜索引擎,还打埋点。写单元测试要 mock 一整个宇宙。

2. 不知全貌

三个月里我至少改了它 30 次。每次都加一点、修一点。现在的我,已经不记得这个函数的所有分支了。重构它可能引入的 bug,比我修过的 bug 加起来还多。

3. 牵一发而动全身

六个平台都在用这个函数。我在觅食猫上改筛选逻辑,可能会莫名其妙地影响搬家通的排序。

源码依赖关系:
  restaurant.service.ts  ← 觅食猫
                        ← 本地生活
                        ← 同城推荐
                        ← 商家入驻
                        ← 优惠券平台
                        ← 搬家通(???为什么会依赖这个)

搬家通是一个搬家平台,它为什么会依赖餐厅服务?因为三个月前偷懒,搬家通的首页用了餐厅服务里的广告位接口。

这个依赖关系没有任何文档,它活在我一个人的脑子里。

下决心重构

一天早上,觅食猫出了个 bug:筛选"人均 100 以下"的餐厅,结果里出现了人均 300 的米其林。原因是 30 天前加的新标签功能影响了价格筛选的 where 条件。

我终于受不了了。要么重构,要么以后每次修 bug 都像拆弹。

重构策略:不是重写,是重构

第一步:添加测试——先造安全网

重构之前必须先有测试。不是"重构后再测试",是先测试,再重构。

// src/services/__tests__/restaurant.service.test.ts
describe('RestaurantService', () => {
  describe('filterByPrice', () => {
    it('should filter out restaurants above max price', async () => {
      const result = await filterByPrice(allRestaurants, { maxPrice: 100 })
      expect(result.every(r => r.avgPrice <= 100)).toBe(true)
    })

    it('should not affect other filters', async () => {
      const result = await filterByPrice(ratedRestaurants, { maxPrice: 100 })
      // 评分过滤仍然有效
      expect(result.every(r => r.avgPrice <= 100 && r.rating >= 4.0)).toBe(true)
    })
  })

  // 每条查询逻辑写一条测试
  // 31 个测试函数,覆盖所有已知分支
})

花了一整天写测试,写完了 31 个测试函数。其中 4 个测试直接失败了——它们暴露了我之前修过的 bug 但测试条件不对。先修正测试,再开始重构。

第二步:拆分——一个函数只做一件事

// ❌ 重构前:一个 847 行的怪物
getRestaurantsWithEverything()
  // 什么都做,什么都做不好

// ✅ 重构后:每个函数职责单一
getRestaurants(filters: FilterParams): Promise<Restaurant[]>
enrichWithRatings(restaurants: Restaurant[]): Promise<EnrichedRestaurant[]>
enrichWithPromotions(restaurants: EnrichedRestaurant[]): Promise<FullRestaurant[]>
applySorting(restaurants: FullRestaurant[], sortBy: SortType): FullRestaurant[]
buildResponse(restaurants: FullRestaurant[]): RestaurantResponse

拆分后的函数最长不超过 80 行。每个函数都能独立测试。

第三步:提取策略模式——消除 if-else 地狱

排序逻辑最初是一大坨 if-else:

// ❌ 重构前
if (sortBy === 'distance') {
  result.sort((a, b) => a.distance - b.distance)
} else if (sortBy === 'rating') {
  result.sort((a, b) => b.rating - a.rating)
} else if (sortBy === 'popularity') {
  result.sort((a, b) => b.views - a.views)
} 
// ... 还有 5 种排序方式

// ✅ 重构后:策略模式
const sortStrategies: Record<SortType, (a: FullRestaurant, b: FullRestaurant) => number> = {
  distance:   (a, b) => a.distance - b.distance,
  rating:     (a, b) => b.rating - a.rating,
  popularity: (a, b) => b.views - a.views,
  avgPrice:   (a, b) => a.avgPrice - b.avgPrice,
  newest:     (a, b) => b.createdAt.getTime() - a.createdAt.getTime(),
  relevance:  (a, b) => calculateRelevance(b) - calculateRelevance(a),
}

function applySorting(restaurants: FullRestaurant[], sortBy: SortType) {
  return [...restaurants].sort(sortStrategies[sortBy])
}

新增排序方式只需要在 sortStrategies 里加一行。

第四步:清理依赖——断开不该有的连线

// 搬家通不应该依赖餐厅服务
// 提取共享逻辑到独立模块
// src/lib/common/ad-slots.ts  ← 新文件

export function getAdSlots(platform: PlatformType, context: AdContext) {
  // 通用的广告位逻辑,各个平台独立调用
}

重构后:搬家通只依赖 ad-slots.ts,不再依赖整个餐厅服务。依赖关系清晰了。

重构结果

指标重构前重构后变化
最大函数行数84778-91%
文件数量1 个文件8 个文件解耦
测试覆盖率0%78%从无到有
新增筛选条件~30 分钟~5 分钟-83%
跨平台影响不可控可控依赖清晰
bug 修复时间1-4 小时10-30 分钟-70%

最重要的是:我现在敢改这段代码了。 改完后跑一遍测试,31 个绿灯全部亮起,心里就有底。

写在最后

重构这件事,道理大家都懂:"Don't repeat yourself""单一职责""高内聚低耦合"。但真正下决心去做,需要一次刻骨铭心的痛。

对我来说,那次"人均 100 以下出现米其林"的 bug,就是那最后一根稻草。847 行的函数不会明天就爆炸,但它会持续消耗你的开发效率、你的改 bug 速度、你的睡眠质量。

你今天为了"快点上线"写的每一行代码,都在为三个月后的自己埋炸弹。

现在我的开发流程里多了一条硬性规定:任何函数超过 150 行必须拆分,超过 300 行视为技术债务,计入 Sprint 工时。

三个月前那个不敢改代码的自己,如果看到现在的代码,应该会松一口气吧。

相关文章