- 数据库
- 版本控制
- 后端
【免费下载链接】noms
The versioned, forkable, syncable database
Noms 是一个版本化、可派生(forkable)、可同步(syncable)的数据库。本文以仓库根目录的 CONTRIBUTING.md 为骨架,结合仓库源码与测试,完整讲解参与 Noms 开发的全流程:从 Go 环境准备、Go Modules 构建、代码风格与错误处理约定,到 PR 提交流程、常规测试与性能(perf)测试的实践方法,帮助读者掌握一套可复现、可验证的贡献工作流。
环境准备:安装并验证 Go 工具链
参与 Noms 开发的第一步是搭建 Go 环境。按 Go 官方安装文档完成安装后,要求Go 版本至少为 1.11(仓库的 go.mod 中声明的模块版本为go 1.12,因此 1.12 及以上同样适用)。安装完成后,在终端验证版本:
# 必须至少是 1.11 go version确认输出形如go version go1.12.x linux/amd64且版本号不低于 1.11 即可继续后续步骤。
获取并构建 Noms:Go Modules 时代的检出与编译
Noms 使用 Go 语言官方的Go Modules特性来管理依赖。这一点对既有 Go 用户尤为关键:
- 不要把仓库检出到
$GOPATH目录内; - 如果你确实只能在
$GOPATH下操作,可以设置环境变量GO111MODULES=on强制启用模块模式。
说明:
GO111MODULES这一名称在文档中沿用了 Go Modules 早期版本的习惯拼写,其标准形式为GO111MODULE。在 Go 1.16 及更高版本中,模块模式已是默认行为,因此通常情况下只需确保检出目录在$GOPATH之外即可。
构建与验证命令如下:
cd <任意非 $GOPATH 目录> git clone https://github.com/attic-labs/noms cd noms go install ./cmd/noms go test ./...其中:
go install ./cmd/noms编译并安装 Noms 的命令行主程序。cmd/noms目录下集中了noms.go、noms_blob.go、noms_map.go、noms_ds.go、noms_serve.go、noms_sync.go等一系列子命令的实现,安装后即可获得noms可执行文件。go test ./...递归运行仓库内全部包的单元测试,是验证环境与代码正确性的最快手段。
许可证与贡献者协议
Noms 是开源软件,采用 Apache License, Version 2.0 许可(仓库根目录的 LICENSE 文件即为该许可证全文,共 201 行)。出于法律原因,所有贡献者在提交 PR 被接受之前,必须签署贡献者协议(Contributor Agreement)——个人贡献者签署个人版协议,公司/机构贡献者签署企业版协议。这是所有外部代码贡献进入仓库的前提条件。
贡献代码:语言、代码风格与提交规范
允许的语言
贡献代码时仅允许使用三种语言:
- Go:核心后端实现语言;
- JS:主要用于
cmd/noms/splore(Noms 的可视化探索工具)等前端部分,其源码位于cmd/noms/splore/src下(如main.js、layout.js、node.js等),并通过webpack.config.js打包; - Python:主要用于仓库工具脚本,例如
tools/noms下的copy.py、staging.py、symlink.py以及根目录的tools/licensify.py。
Shell 脚本不被允许,涉及自动化需求时应优先考虑用 Python 或 Go 实现。
编码风格要求
- Go:必须使用
gofmt格式化,建议在编辑器中接入 gofmt 的自动格式化钩子,保证提交的 Go 代码风格统一; - JS:遵循 Airbnb JavaScript Style Guide;
- PR 标签:提交 PR 时,用
toward: #<bug>或fixes: #<bug>标注该变更所关联的 issue 编号,帮助维护者理解变更的上下文与动机; - Commit Message:遵循 Chris Beams 的 commit message 风格指南,核心要点包括:主题行与正文用空行分隔、主题行不超过 50 个字符、以动词开头、正文解释“为什么”而非“是什么”。
Go 错误处理约定:d 包的异常式错误
Noms 的错误处理采用两套并行的风格:
- 对外公开 API(Public API):默认使用 Go 惯例的返回值式错误,即函数返回
error由调用方处理; - 非对外代码:提供并鼓励使用基于
panic/recover的异常式(Exception-style)错误包装工具。使用这种风格必须给出充分理由——典型场景是:当前代码不知道该怎样继续执行而需要 panic,但你希望向上层调用栈传递“可被 recover 并继续运行”的信号。
为此,仓库提供了go/d包(见 go/d/try.go),包含如下“抛出一个可捕获错误”的函数族:
| 函数 | 语义 |
|---|---|
d.PanicIfError(err) | 当err != nil时,将错误包装后 panic(见 go/d/try.go) |
d.PanicIfTrue(b) | 当b == true时 panic,默认错误信息为"Expected true"(见 go/d/try.go) |
d.PanicIfFalse(b) | 当b == false时 panic,默认错误信息为"Expected false"(见 go/d/try.go) |
这三者在 Noms 内部被广泛使用,例如 go/datas/database_common.go 中就通过d.PanicIfError(err)处理数据库操作错误。
其底层机制是:这些函数调用d.Wrap(err),把普通error包装为带调用栈信息的wrappedError(同时实现Error()与Cause()方法,见 go/d/try.go);随后可由d.Try()或d.TryCatch()在栈上游捕获:
Try(f, types...):执行f;若捕获到WrappedError,当types为空时直接返回该包装错误,当types非空且原始错误类型匹配时返回cause,否则重新 panic(见 go/d/try.go);TryCatch(f, catch):执行f;捕获到包装错误后交给catch回调决定如何处理(见 go/d/try.go);d.Unwrap(err):若err是WrappedError则返回其Cause(),否则原样返回(见 go/d/try.go)。
配套的单测位于 go/d/try_test.go,覆盖了Try在未匹配类型时重新 panic、TryCatch对类型过滤、Unwrap语义、PanicIfTrue/PanicIfFalse的边界行为以及Wrap(nil)返回 nil 等关键路径。
注意:仓库中还存在以
d.Chk开头的旧式断言函数(d.Chk定义于 go/d/try.go,本质是绑定到panicker的 testify assert 实例)。维护者计划移除这些用法(对应 issue #3258),新代码不要使用d.Chk,统一改用上述d.PanicIfError/d.PanicIfTrue/d.PanicIfFalse族。
提交 PR:基于 Chromium 风格的分支评审流程
Noms 的代码评审协议源自 Chromium 团队的实践,提交 PR 的完整步骤如下:
创建 fork:将待修改的仓库 fork 到自己的账号下(例如从
https://github.com/attic-labs/nomsfork 出https://github.com/<username>/noms);添加 remote:将 fork 添加为本地仓库的 remote:
git remote add <username> https://github.com/<username>/noms推送分支:将改动提交到 fork 的某个分支并推送:
git push <username> <branch>发起 PR:用刚推送的分支创建 PR——通常只需在浏览器中打开上游仓库主页,GitHub 会识别新分支并自动提示创建 PR;
请求评审:当你认为 PR 已准备好接受评审时,在对应的 issue 中评论并请求 review。有时评审人不会主动 review,因为他们不确定你是否认为 PR 已经就绪;
迭代修改:通过 GitHub 常规 review 流程与评审人反复沟通修改;
合入:评审人满意后,由评审人负责合入(submit)变更。
运行测试:go test 与 Jenkins 集成
常规单元测试
go test是最直接的测试入口,例如:
go test $(go list ./... | grep -v /vendor/)该命令会运行除 vendor 包之外的所有测试。仓库中的测试覆盖十分全面,例如cmd/noms下每个子命令都有对应测试文件(noms_blob_get_test.go、noms_commit_test.go、noms_diff_test.go、noms_ds_test.go、noms_log_test.go、noms_merge_test.go、noms_root_test.go、noms_show_test.go、noms_sync_test.go、noms_version_test.go等),它们均基于 testify 的suite.Run组织测试套件。
Jenkins 集成:如果你(或仓库)具有提交权限(commit rights),Jenkins 会在每次 PR 及随后的每个补丁(patch)上自动运行 Go 测试。如需立即触发一次测试,任何具备提交权限的人都可以在 PR 下回复(不含引号):
Jenkins: test thisPerf 性能测试
默认情况下,go test和 Jenkins 都不会运行性能测试,因为它们耗时较长。性能测试由 go/perf/suite 包驱动,需要显式使用-perf与-v标志:
go test -v ./samples/go/csv/... -perf mem该命令以mem(内存存储)为后端数据库,运行samples/go/csv目录下所有包的性能测试(详细文档见 go/perf/suite 包注释)。
如何编写一个 perf 测试
以仓库自带的 samples/go/csv/csv-import/perf_test.go 为例,编写 perf 测试的基本模式为:
- 定义一个继承
suite.PerfSuite的测试套件结构体:type perfSuite struct { suite.PerfSuite csvImportExe string } - 在结构体上定义以
Test开头(可含前导数字,用于手动排序)的方法,例如Test01ImportSfCrimeBlobFromTestdata、TestParseSfCrime; - 调用
suite.Run启动套件,Run的第一个参数是结果数据集(dataset)ID:func TestPerf(t *testing.T) { suite.Run("csv-import", t, &perfSuite{}) }
suite.Run的行为细节见 go/perf/suite/suite.go:它会按-perf标志决定是否跳过测试;测试名会去掉Test前缀与前导数字(如Test01Import...记录为Import...);每个测试的耗时被拆分为elapsed(净执行时间)、paused(暂停时间,可用PerfSuite.Pause(fn)排除长耗时的准备代码,见 go/perf/suite/suite.go)与total(总时间);测试结果连同环境信息(CPU、内存、磁盘、主机信息,见getEnvironment,go/perf/suite/suite.go)以及 noms 与 testdata 的 git revision 一起写入 Noms 数据库数据集。
Perf 测试的完整参数
go/perf/suite包注册了以下命令行标志(定义于 go/perf/suite/suite.go):
| 标志 | 类型 | 默认值 | 作用 |
|---|---|---|---|
-perf | string | "" | 指定写入性能测试结果的数据库。为空则跳过 perf 测试;传mem可进行“干跑”(dry run,不落盘) |
-perf.mem | bool | false | 用内存存储(chunks.MemoryStorage)而非 NBS 磁盘存储作为底层 store,会改变测试计时,但在磁盘空间紧张时可用 |
-perf.prefix | string | "" | 结果数据集 ID 的前缀,例如foo/会让结果写入foo/csv-import而非csv-import |
-perf.repeat | int | 1 | 每个 perf 测试重复执行的次数 |
-perf.run | string | "" | 仅运行与正则表达式匹配(大小写不敏感)的 perf 测试 |
-perf.testdata | string | "" | testdata 目录路径;默认是$GOPATH/src/github.com/attic-labs/testdata |
PerfSuite还支持 testify 风格的 Setup/TearDown 生命周期钩子(go/perf/suite/suite.go):
SetupSuite/TearDownSuite:整个套件执行一次;SetupRep/TearDownRep:每次重复(受-perf.repeat控制)执行一次;SetupTest/TearDownTest:每个测试执行一次。
运行结果示例
go/perf/suite包注释给出了真实运行形态(go/perf/suite/suite.go):
noms serve & go test -v ./samples/go/csv/... -perf http://localhost:8000 -perf.repeat 3输出形如:
(perf) RUN(1/3) Test01Qux (recorded as "Qux") (perf) PASS: Test01Qux (5s, paused 15s, total 20s) (perf) RUN(1/3) Test02Bar (recorded as "Bar") (perf) PASS: Test02Bar (15s, paused 2s, total 17s)随后用noms show查看写入数据库的结果结构:
noms show http://localhost:8000::csv-import结果中包含environment(运行环境快照)、tests(每次重复的测试耗时映射,含elapsed/paused/total三个纳秒级时长字段)以及nomsRevision、testdataRevision等字段。
在 CI 上跑 perf 测试
如需让 Jenkins 代为运行 perf 测试,在 PR 下回复(不含引号):
Jenkins: perf this结果可在 Noms 的公开 perf 查看站点按pr_$your-pull-request-number/csv-import数据集查看。注意,只有具有提交权限(committer)的人才能触发该操作。
小结:贡献 Noms 的完整检查清单
- Go 版本 ≥ 1.11(仓库基于
go 1.12模块),且在非$GOPATH目录检出; go install ./cmd/noms构建成功,go test ./...通过;- 已签署个人或企业贡献者协议(Apache 2.0 许可下);
- 新代码仅使用 Go / JS / Python,不使用 Shell 脚本;
- Go 代码经
gofmt格式化;JS 遵循 Airbnb 风格;PR 打上toward:/fixes:标签;commit message 遵循 Chris Beams 指南; - 公开 API 返回错误;非公开代码如需异常式错误,使用
d.PanicIfError/d.PanicIfTrue/d.PanicIfFalse(不要用将被废弃的d.Chk); - 按 Chromium 风格流程提交 PR 并主动请求评审;
- 常规测试用
go test,性能测试用go test -v ./samples/go/csv/... -perf <db>,并可借助 Jenkins 的Jenkins: test this/Jenkins: perf this触发远程测试。
- 数据库
- 版本控制
- 后端
【免费下载链接】noms
The versioned, forkable, syncable database
相关推荐
Redux Toolkit 仓库贡献指南:从环境搭建、构建测试到提交 Pull Request 的完整工作流
Redux Toolkit 仓库贡献指南:从环境搭建、构建测试到提交 Pull Request 的完整工作流 本篇指南以仓库根目录的 CONTRIBUTING.
前端状态管理BiliNote代码贡献指南:从环境搭建到PR提交的完整流程
BiliNote代码贡献指南:从环境搭建到PR提交的完整流程 BiliNote是一个开源的AI视频笔记助手,支持通过哔哩哔哩、YouTube、抖音等视频链接,自
AI 应用大模型RAG语音后端前端桌面应用为 nuqs 仓库贡献代码:从 Worktree 开发环境搭建到测试、Lint 与合并的完整指南
为 nuqs 仓库贡献代码:从 Worktree 开发环境搭建到测试、Lint 与合并的完整指南 next usequerystate (包名为 nuqs )是
前端状态管理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考