在一家全球化初创公司背后的 30+ SaaS 服务和成本这篇文章中有提到 Bytebase 在研发团队只有 10 人的规模下,做到了每两周一次发布,且每个版本有 100 - 150 左右的 PR 数量。这样的「壮举」也在 Hacker News 上引发了讨论。
随着时间的推移,现在 Bytebase 已经演化到 10 人,2 周,200+ PR。想要做到极致的研发效率是需要「因地制宜」考虑所有环节,琢磨每一个细节,并且离不开每一位同学的努力。正如软件工程中的「没有银弹」所言,不同的组织、不同的研发背景是需要不同流程和策略才能达到研发效率的山顶。不过,在追求研发效率的过程中,一些想法和思路是可以参考借鉴的。
接下来本文会通过一个具体的例子来探秘 Bytebase 追求研发效率的冰山一角。
背景介绍
众所周知,Bytebase 作为一款数据库 DevOps 工具,因此代码中少不了与 SQL 打交道。同时,为了保证 Bytebase 能够如预期中为用户提供强大的功能,我们在 Bytebase 的 SQL kernel 模块中添加了大量的单元测试。
此外,Bytebase 使用 Golang 作为后端研发的语言。对 Golang 熟悉的同学可能知道,Golang 中有两种方式可以定义字符串常量:使用双引号,或者使用反引号。这两者的主要区别是:
- 对于特殊的字符,在双引号的格式中需要使用转义字符,例如换行(\n), 双引号自身等。
- 反引号支持原生格式的字符串,不需要使用转义字符,但是无法使用反引号本身。
下面展示了使用不同格式来定义相同的字符串常量:
// The Double-quoted Style
s := "SELECT id, name \nFROM t \nWHERE name != \"Bytebase\" \nLIMIT 1"
// The Back-quoted Style
s := `SELECT id, name
FROM t
WHERE name != "Bytebase"
LIMIT 1`
可以看到,在可读性上,不使用转义字符的反引号格式略胜一筹。然后反引号也有它自己的问题:
- 在定义多行的反引号格式字符串常量时,字符串本身所使用的格式影响着最终的内容,因此通常和代码缩进格格不入。
- 反引号格式的字符串中不能出现反引号本身,但是,反引号是 MySQL 方言中用来表示标识符的符号,在 MySQL 方言的 SQL 文本中经常出现。由此导致很多时候不能使用反引号格式。
在 Bytebase 研发同学们的身心和眼睛被 SQL 与 Golang 字符串折磨了数周之后,我们引入了 YAML 文件来存放 SQL 相关的测试文件,并且借此机会实现了从手写测试用例和测试结果转变为手写测试输入,生成测试结果的方式。
选择 YAML
选择 YAML 背后的原因是 YAML 定义多行字符串可以使用所谓的 Block Scalar Style。简单来讲,我们可以使用下面的格式来定义之前提到的字符串:
example: |-
SELECT id, name
FROM t
WHERE name != "Bytebase"
LIMIT 1
如你所见,Block Scalar Style 下,不需要任何类型的引号来表示这是一个字符串,因此,我们也能够在其中使用任何类型的字符。除此之外,YAML 使用了一些标识符来定义字符串中的空行与换行的格式。在这个例子中,”|-” 标识符表示取出最后一行的换行符,但是保留其余行的换行符。
拥有了这个利器之后,我们重构了相关的测试框架,他们大概是这个样子:
func RunTest(..., record bool) {
ReadTestData()
result := CalculateResult()
If record {
WriteResult()
} else {
CompareResult()
}
}
此时,不仅解决了在 Golang 中定义测试用字符串常量可读性差的问题,还顺便推进了测试半自动化生成。
突发惨案
理想中的故事应该就此画上句号,但是现实总是残酷的。对于软件工程而言,引入任何新内容都是一把双刃剑,需要慎重权衡。
关于 YAML 的故事这样的。在上面的例子中可以看到,当我们想要添加新的测试时,我们会先设计好测试输入,然后将对应测试的 record 值设置为 true,最后通过 RunTest 方法来生成对应的测试结果,最后人工检查是否满足预期。在之前,最后生成的结果都是之前介绍的 Block Scalar Style,可读性极佳。但是在一个风和日丽的下午,生成的结果并不是 Block Scalar Style,而是 YAML 中的 Double-quoted Scalar Style。看起来大概是这样:
example: "SELECT id, name\nFROM t\nWHERE name != \"Bytebase\" \nLIMIT 1"
没错,这个看起来与 Golang 中的双引号格式差不多,可读性也被各种转义字符完完全全破坏掉了。在我不停尝试下,依然没有办法得到我想要的 Block Scalar Style。不管是 Golang 中 YAML 格式的序列化(Marshal)方法,还是 Google 中都没有找到我想要的信息。无奈之下,只能查看并调试 Golang YAML 库来看看问题出在哪里。
// 此处省略若干调试、读代码过程。
最终,在控制序列化 YAML 格式的过程中发现了一个细节,当存在行末空格时,即出现一个空格符号连着一个换行符时,字符串格式会被强制使用 Double-quoted Scalar Style!
发现这个逻辑之后,再反过来去看字符串中确实有一个行末空格。把行末空格去掉之后确实能够得到 Block Scalar Style 的字符串了。这里可以使用 VSCode 的 Render Whitespace trailing 功能。
后记
极致的研发效率是几乎所有的研发团队都在追求的,除了最容易被关注的流程框架之外,需要去注意、能够带来效率提升的细节还有非常多。在不断调整、优化的过程中,最后才能够逐渐达到极致的效率,这可能也就是大家常说的「团队磨合」的一部分吧。
ヾ(✿゚▽゚)ノ
你可以访问官网:https://www.bytebase.com/,免费注册云账号,立即体验 Bytebase。