故事从一座“考古现场”开始
软件工程里有一种地貌,远看像项目,近看像遗址。drwateR 及其核心依赖包正处在这片地貌中:功能不少,历史很长,README 有时比代码更像传家宝,发布过程则依赖“记得先删掉上次的 tar.gz”这种口耳相传的知识。
这次没有选择给屎山刷一层油漆,而是把它当作一个真正需要维护的 R 生态来处理。目标很朴素:能检查、能测试、能跨平台、能生成文档、能打版本、能进入 DRAN,并且下一个维护者不必靠通灵完成发布。
先盘点,再动铲子
第一步是明确边界。当前优先维护 drwateR 相关的核心 10 个包:
- drwateR:生态入口与公共工具,负责包信息和 DRAN 更新检查。
- dfeR:数据格式与数据处理相关工具。
- dateR:日期和时间处理工具。
- langeR:语言与国际化辅助能力。
- uniteR:单位、量纲与统一表达工具。
- cctdb:数据库访问和相关数据接口。
- cctda:藻类与水环境数据分析能力。
- dwfun:DRWATER 的通用函数与分析辅助工具。
- rmdify:R Markdown、Quarto 与复杂标记处理工具。
- figeR:图形、图件和图形工作流辅助工具。
dateR1 和已经停用的 customsetup 不再混入维护范围。先把边界画清楚,审计才不会变成“看见什么修什么”的无限考古。
审计清单:让包从能跑变成可维护
1. 元数据不再靠猜
每个核心包都补齐了常规的包信息接口:<pkg>_version()、<pkg>_info() 和 <pkg>_lifecycle(),并在 DESCRIPTION 中声明生命周期。版本号、包名、维护状态从包自身读取,而不是散落在 README、脚本和发布者记忆里。
2. 安全边界先于“方便写”
数据库相关代码重点区分了 SQL 值和 SQL 标识符:值使用参数化查询,表名和字段名走显式校验,不把未经验证的字符串直接拼进查询。动态代码执行也按输入边界检查,避免把“灵活”误写成“把用户输入交给 eval(parse())”。
3. macOS、Linux、Windows 不再被当成同一台电脑
跨平台审计覆盖了路径拼接、可执行文件定位、临时目录、文件查看器和 system2() 调用。外部命令优先使用 Sys.which()、R.home("bin") 和 file.path(),不假设 /bin/sh、.exe、固定 HOME 或某一种路径分隔符存在。system2() 本身并不神秘,真正危险的是把某个平台的命令名、参数和 shell 行为当成了 R 的常量。
rmdify:最像压力测试的一站
这次最有戏剧性的部分是 rmdify 的 clab 解析。真实文档不会永远把一个标记乖乖塞进一行,它可能跨行、跨段落,里面还有嵌套引用、公式、内联 R,甚至夹着一个图形 chunk。于是解析器改为状态机处理括号深度和代码围栏,而不是用一条“看起来很聪明”的正则表达式押注文档永远听话。
现在可以处理跨段落的复杂标记,也支持旧式与 options clab="..." 属性形式。内联代码例如 2 会被正常处理;围栏中的 R/Quarto chunk 则被保留,不在解析阶段偷偷执行。图形引用应通过明确的 Quarto label 建立:
::: {.cell}
```{.r .cell-code}
ggsavep("../figures/demo.pdf", loadit = TRUE)
```
:::这条边界很重要:文档转换器可以理解代码结构,但不应该在用户没有要求时擅自运行任意代码。
发布流程终于像流程
核心包现在通过 Makefile 统一执行:更新版本,运行 roxygenise,生成 NEWS,总结主要变化,使用保守的检查门禁,提交到 main,推送提交,创建并推送版本 tag,最后精确构建 DRAN 索引。
这次还修掉了一个很有“屎山气质”的发布陷阱:根目录里如果残留旧版本 tarball,构建脚本可能复制错文件。现在构建前清理旧包,并按 DESCRIPTION 中的精确版本匹配 tarball,避免“终端说成功,仓库里却是上一个版本”的幽灵发布。
每个包同时补充了中英文 README、NEWS 和 hex logo。logo 不是治疗技术债的药,但它能提醒我们:一个包不仅要能跑,还要能被人找到、读懂和使用。
结果:不是魔法,是可重复
这一轮完成后,10 个核心包都已完成版本更新、检查、提交、推送和 tag,并重新构建到本地 DRAN。drwateR 还提供了客户端更新检查入口:
drwateR::check_drwateR_updates()它不会替用户自动升级,更不会在后台施法,只负责把可用更新明确告诉用户。升级仍然是一个可审计、可回滚的决定。
当然,审计并不等于宣布技术债已经灭绝。部分历史示例和旧文档仍值得继续清理,完整示例检查也应在后续逐步恢复。诚实的发布说明比一张“所有检查全绿”的海报更有用:我们知道哪里稳了,也知道哪里还要继续铲。
屎山没有被魔法消灭。它被拆成了可测试、可发布、可回滚的砖块。下次再有人问“这个包怎么发布”,答案不再是“找当年写它的人”,而是:看 Makefile,跑检查,读 NEWS,然后让版本号自己说话。