The Missing Semester of CS Education, MIT - Metaprogramming
Metaprogramming is the best collective term we could come up with for the set of things that are more about process than they are about writing code or working efficiently.
对于元编程这个概念,我们可以理解为作用在程序之上的程序 (programs that operate on programs)。这一节将主要介绍构建系统,依赖管理与持续集成。
This article is a self-administered course note.
It will NOT cover any exam or assignment related content.
Build System
在之前对 build system 已经有相当的认识了;Make
就是一个著名的 build system。在 Makefile
中,我们需要声明一系列的 directives,它包括:
- targets.
- dependencies.
- rules.
make
将会根据声明的 directives 按照 dependency hierarchy
逐步生成 targets。这一过程被称为 build process。当
make
缺省参数时,它默认将第一个 directive 中的 target 作为
final target 进行生成。
make
另一个优秀的特性是,它能保证每次生成 target
时所执行的命令数最少。这是因为它采取了这样的贪心策略:当某个
target 的所有 dependencies 都是最新的时 (通过比较 target 与 dependencies
的最后修改时间),无需执行 rules 中的命令。
make
只是众多 build system 中最普通,最 low-level
的一个。根据 project 的类型与规模,有许多 build system 可供采用,例如
CMake
, ninja
,
Bazel
。然而它们的内核都是相似的,离不开上述三种元素的声明。
Dependency Management
Build system 更多的是控制一个 project 内部的 build
process;但从更宏观的角度来看,某个 project 很有可能会以另外的 projects
作为依赖项。这些依赖项可能是某些 installed programs (如
python
),系统包 (如 openssl
) 或语言中的库 (如
ncurses
)。
常见的做法是通过一个 repository 来集中存储某个 project
大量的依赖项,并且提供便利的下载机制。比如 Ubuntu 的 package
repositories (我们通过 apt
进行访问),NodeJs
的 npm
,Python
的 PyPi
等等。
我们常常遇到版本不兼容的问题;大多数情况下这是软件和它的依赖项版本产生冲突导致的。类似
npm
,PyPi
这样被很多软件所依赖的 project
会发布版本号 (version number),这种机制称为
versioning。
最常见的版本语义 (semantic versioning),例如
8.1.3
或者 64.1.20192004
,由左到右分别是 major
version,minor version 与 patch version。
- If a new release does NOT change the API, increase the patch version.
- If you add to your API in a backwards-compatible way, increase the minor version.
- If you change the API in a non-backwards-compatible way, increase the major version.
若 project A 初始依赖 project B 的版本
v1
,版本语义保证 B 的任何与 v1
的
major version 相同且 > v1
的版本都能够与 A
兼容。例如,基于 Python 3.5 编写的代码能在 Python 3.7
环境下运行;但不一定能在 Python 3.4 环境下运行;更不一定能在 Python 4
环境下运行。
在依赖管理系统中,我们常常能见到一种特殊的文件:lock files。lock file 能够加速开发周期,使 project 始终基于其中指定的依赖项版本进行构建;并且对于用户来说,它也提供了一定的安全保证。
一个更极端的做法称为 vendoring, 即 copying all the code of your dependencies into the project。这给予了我们对依赖项的完全掌控权力;我们甚至能自主修改其中的代码。然而这样做的代价是失去了 versioning 带来的便利。
Continuous Integration System
Continuous integration (持续集成),或 CI,其一个不正式的定义为 "stuff that runs whenever your code changes"。例如,每当有人向 project 中 push 了代码,你希望对代码进行 stylecheck,或是对整个 project 跑一次 test suite 等等。这一需求在一些大 project 中很常见。
目前比较流行的 CI 系统有 Travis CI, Azure Pipelines 与 Github Actions。
我们十分熟悉的 dependabot 就是一个简单的 CI 服务:它定时检查 project 依赖项的更新情况,当依赖项存在更新的版本时,它向 project 提出一个 pr 作为提示。
本博客所基于的 GitHub pages 也是一个 CI 服务:当我们写好 markdown
格式的博文并且 push 到主分支时,GitHub pages
将通过指定的静态网页生成框架 (Hexo, Jekyll 等) 生成 HTML 文件并部署到
github.io
domain
上。这使得撰写并发布博文的操作变得非常简单:我们只需要执行 push
操作,剩下的交给 CI。
与 build system 相似,你需要在 repo 中存储一个 "recipe",指定 CI 在发生哪些变动时 (trigger) 对 project 执行哪些操作 (action)。当指定的事件发生时,CI 将启动一个虚拟环境,对 project 执行你在 recipe 中指定的操作,然后记录并存储结果。
Test Briefing
我们经常听到一个词 “测试套件” (test suite),它是一系列测试的总称。那么,对于一个 project,有哪几种常见的 test 类型呢?
- Test suite: 一系列测试的总称。
- Unit test: a "micro-test" that tests a specific feature in isolation.
- Integration test: a "macro-test" that runs a larger part of the system to check that different feature or components work together.
- Regression test: a test that implements a particular pattern that previously caused a bug to ensure that the bug does not resurface. (在修复完某个 bug 后,向测试套件中添加一个回归测试)
- Mocking: to replace a function, module, or type with a fake implementation to avoid testing unrelated functionality. (比如,通过 overwrite 的形式 mock network 或 mock the disk)
Reference
This article is a self-administered course note.
References in the article are from corresponding course materials if not specified.
Course info:
MIT Open Learning. The Missing Semester of Your CS Education.
Course resource: