何谓“元”(meta):
大哉乾元,万物资始,乃统天。 --《彖》
道生一,一生二,二生三,三生万物。 -- 《道德经》
此处的元编程(metaprogramming)并不是C++的元编程魔法,而是关于流程的(即构建系统、代码测试以及依赖管理)。必须要指出的是,“元编程” 也有用于操作程序的程序” 之含义。
Build systems - 构建系统
构建过程:您需要执行一系列操作。通常,这一过程包含了很多步骤,很多分支。执行一些命令来生成图表,然后执行另外的一些命令生成结果,然后再执行其他的命令来生成最终的论文。
构建系统:帮助完成构建过程操作的工具。需要定义依赖、目标 和 规则。我们必须告诉构建系统我们具体的构建目标,系统的任务则是找到构建这些目标所需要的依赖,并根据规则构建所需的中间产物,直到最终目标被构建出来。理想的情况下,如果目标的依赖没有发生改动,并且我们可以从之前的构建中复用这些依赖,那么与其相关的构建规则并不会被执行。
make
是最常用的构建系统之一,您会发现它通常被安装到了几乎所有基于UNIX的系统中。make
并不完美,但是对于中小型项目来说,它已经足够好了。当您执行 make
时,它会去参考当前目录下名为 Makefile
的文件。所有构建目标、相关依赖和规则都需要在该文件中定义,它看上去是这样的:
paper.pdf: paper.tex plot-data.png
pdflatex paper.tex
plot-%.png: %.dat plot.py
./plot.py -i $*.dat -o $@
冒号左侧的是构建目标,冒号右侧的是构建它所需的依赖。缩进的部分是从依赖构建目标时需要用到的一段程序。
在 make
中,第一条指令还指明了构建的目的,如果您使用不带参数的 make
,这便是我们最终的构建结果。或者,您可以使用这样的命令来构建其他目标:make plot-data.png
。
规则中的 %
是一种模式,它会匹配其左右两侧相同的字符串。例如,如果目标是 plot-foo.png
, make
会去寻找 foo.dat
和 plot.py
作为依赖。
Dependency management
依赖管理
就一个项目来说,它的依赖可能本身也是其他的项目。项目也许会依赖某些程序(例如 python
)、系统包 (例如 openssl
)或相关编程语言的库(例如 matplotlib
)。
现在,大多数的依赖可以通过某些软件仓库来获取,这些仓库会在一个地方托管大量的依赖,我们则可以通过一套非常简单的机制来安装依赖。
例子:
Ubuntu 系统下面有Ubuntu软件包仓库,可以通过apt
这个工具来访问
RubyGems 则包含了 Ruby 的相关库
PyPi 包含了 Python 库
版本控制
版本控制:大多数被其他项目所依赖的项目都会在每次发布新版本时创建一个版本号。通常看上去像 8.1.3 或 64.1.20192004。版本号有很多用途,其中最重要的作用是保证软件能够运行。
语义版本号,这种版本号具有不同的语义,它的格式是这样的:主版本号.次版本号.补丁号。相关规则有:
- 如果新的版本没有改变 API,请将补丁号递增;
- 如果添加了 API 并且该改动是向后兼容的,请将次版本号递增;
- 如果修改了 API 但是它并不向后兼容,请将主版本号递增。
软件开发行业的「前后」是按照英语习惯来的,不是按照汉语习惯来的。按照英语习惯,「向前进」(forward)指未来,「向后退」(backward)指过去。注意要把「前」「后」分别理解成「前进」和「后退」,不可以理解成「从前」和「以后」。
lock files
锁文件列出了您当前每个依赖所对应的具体版本号。通常,您需要执行升级程序才能更新依赖的版本。这么做的原因有很多,例如避免不必要的重新编译、创建可复现的软件版本或禁止自动升级到最新版本(可能会包含 bug)。
一般可见于python项目中的requirements.txt
vendoring
vendoring会把依赖中的所有代码直接拷贝到项目中,这样就能够完全掌控代码的任何修改,同时也可以将自己的修改添加进去,不过这也意味着如何该依赖的维护者更新了某些代码,也必须要自己去拉取这些更新。
常见于Go项目中的vendor
Continuous integration systems - 持续集成系统
持续集成,或者叫做 CI 是一种雨伞术语(umbrella term,涵盖了一组术语的术语),它指的是那些“当您的代码变动时,自动运行的东西”,市场上有很多提供各式各样 CI 工具的公司,这些工具大部分都是免费或开源的。比较大的有 Travis CI、Azure Pipelines 和 GitHub Actions。
工作原理:您需要在代码仓库中添加一个文件,描述当前仓库发生任何修改时,应该如何应对。目前为止,最常见的规则是:如果有人提交代码,执行测试套件。当这个事件被触发时,CI 提供方会启动一个(或多个)虚拟机,执行您制定的规则,并且通常会记录下相关的执行结果。您可以进行某些设置,这样当测试套件失败时您能够收到通知或者当测试全部通过时,您的仓库主页会显示一个徽标。
Test
- 测试套件:所有测试的统称。
- 单元测试:一种“微型测试”,用于对某个封装的特性进行测试。
- 集成测试:一种“宏观测试”,针对系统的某一大部分进行,测试其不同的特性或组件是否能协同工作。
- 回归测试:一种实现特定模式的测试,用于保证之前引起问题的 bug 不会再次出现。
- 模拟(Mocking): 使用一个假的实现来替换函数、模块或类型,屏蔽那些和测试不相关的内容。例如,“模拟网络连接” 或 “模拟硬盘”。