3. 重构第一步
说实话,我见过太多Makefile项目,一上来就是几千行,各种变量满天飞,目标依赖乱成一锅粥。你问他们为什么要这么写?答曰:「历史原因,不敢动。」
嗯,我特别理解这种心情。我自己也踩过这个坑——有一次重构一个嵌入式项目的构建系统,光是梳理文件依赖就花了两天。但后来我发现,只要第一步走对了,后面全是顺水推舟的事。
这一章,我们就来聊聊重构Makefile的第一步:项目结构梳理与模块划分。说白了,就是先把你的项目「拆明白」,再谈怎么「建漂亮」。
1.1 为什么非要先梳理结构?
你想想看,一个大型项目,少说几十个源文件,多则上千。如果连文件放哪儿、谁依赖谁都不清楚,Makefile写得再花哨也是白搭。
我个人习惯是:先画图,再写规则。画什么图?项目目录结构图、模块依赖关系图。别嫌麻烦,这一步省了,后面全是坑。
核心原则: 项目结构决定了Makefile的复杂度。结构清晰,Makefile自然简洁;结构混乱,Makefile就是一团乱麻。
我曾经接手过一个项目,所有源文件都堆在src/目录下,连子目录都没有。Makefile里光是找文件路径就写了200多行。你说这能重构吗?能,但得先拆结构。
1.2 模块划分的三大原则
模块怎么划?我总结了三个原则,你记一下:
- 高内聚、低耦合——每个模块只做一件事,模块之间尽量少依赖。
- 按功能划分,不按文件类型——别把.c和.h分开,而是把「网络模块」「日志模块」「配置模块」各自放一起。
- 目录深度不超过3层——太深了,Makefile写起来累,人也看不懂。
举个例子,一个典型的C++服务端项目,我通常会这样组织:
project/
├── modules/
│ ├── network/ # 网络模块
│ │ ├── src/
│ │ ├── include/
│ │ └── Makefile.mk
│ ├── logger/ # 日志模块
│ │ ├── src/
│ │ ├── include/
│ │ └── Makefile.mk
│ └── config/ # 配置模块
│ ├── src/
│ ├── include/
│ └── Makefile.mk
├── common/ # 公共工具
│ ├── utils/
│ └── types/
├── build/ # 构建输出
├── Makefile # 顶层Makefile
└── Makefile.include # 公共变量和规则
你看,每个模块都有自己的Makefile.mk,顶层Makefile只需要include它们就行。这样改一个模块,不会影响其他模块。
小技巧: 每个模块的Makefile.mk只定义本模块的源文件、头文件路径和编译选项。顶层Makefile负责链接和全局设置。这叫「职责分离」。
1.3 模块依赖关系怎么理?
模块划分好了,接下来就是理依赖。这一步很多人会忽略,但恰恰是Makefile重构的关键。
我建议你画一张模块依赖图。别怕麻烦,用纸笔或者draw.io都行。核心思路是:从底层往上画。
比如上面那个项目,依赖关系可能是这样:
- config模块:不依赖任何模块(最底层)
- logger模块:依赖config模块(因为日志需要配置)
- network模块:依赖config和logger模块
有了这张图,你在Makefile里写依赖顺序就一目了然了。不会出现「A依赖B,但B还没编译」的尴尬情况。
注意: 千万别出现循环依赖!比如A依赖B,B又依赖A。这种结构在Makefile里会直接报错,或者导致无限递归。我曾经在一个项目里排查了整整一天,最后发现是两个模块互相include了对方的头文件……
1.4 用SVG画一张模块依赖图
光说不够直观,我画了一张图,你看看:
这张图看着简单,但实际项目里,模块一多,依赖关系就复杂了。我建议你每加一个模块,都先更新这张图。别偷懒,后面写Makefile的时候,你会感谢自己的。
1.5 避坑指南:我踩过的三个坑
聊了这么多原则,我再分享几个实际踩过的坑,你引以为戒:
- 坑一:把所有头文件放一个目录。我曾经有个项目,所有.h文件都丢在include/下,结果不同模块的同名头文件冲突了。后来我改成每个模块自己管自己的头文件,世界清净了。
- 坑二:模块划分太细。有个同事把每个.c文件都当成一个模块,结果Makefile里include了30多个.mk文件,编译慢得要死。记住,模块粒度要适中,一般一个模块5-10个源文件比较合理。
- 坑三:忽略测试模块。很多项目把测试代码和源码混在一起,结果Makefile里要写一堆条件判断来区分。我建议把测试代码单独放一个目录,比如tests/,和src/平级。这样顶层Makefile可以统一处理。
总结一下: 项目结构梳理是Makefile重构的基石。模块划分要遵循高内聚低耦合、按功能划分、目录深度不超过3层。理清依赖关系后,用图记录下来。别嫌麻烦,这一步做好了,后面的重构就是水到渠成的事。