背景
之前在阅读Badgerdb源码的时候看到过一些不错的代码实践,一些可以用到Nutsdb中,可以提升代码质量的技巧,我都默默记下来了。之前一部分心思都用在了别的东西上面,没有时间来实现我的一些想法。不过正好,最近招募到了一批小伙伴一起开发这个项目,这些小范围的重构正好可以给大家熟悉项目用。在这个过程中我感觉也可以记录一下一些代码为什么重构了,以及为什么感觉这样的代码比较好。在这篇文章做一个小小的分享。当然这个小分享只是这个系列的第一弹,敬请期待后面更多分享环节喔。
代码展示
废话不多说了,直接进入代码操作环节。
1. 一个比较好的测试用例封装代码
在对写代码相关的测试用例的时候,一般测试代码会是下面这样的流程:
- 创建db
- 使用db进行相关的操作。
- 回收资源
很显然,在这个测试过程中,我们真正需要关注的是第二步,而创建和回收资源又是不可避免的。为了避免重复代码的出现,将第一步和第三步封装起来,而第二步通过回调函数的形式传入封装的函数中。在我看来这是一个比较好的实践。这里使得我们只需要关注我们需要测试的逻辑。下面这个是Badger的写法。
func TestWrite(t *testing.T) {
runBadgerTest(t, nil, func(t *testing.T, db *DB) {
for i := 0; i < 100; i++ {
txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), []byte(fmt.Sprintf("val%d", i)), 0x00)
}
})
}
// Opens a badger db and runs a a test on it.
func runBadgerTest(t *testing.T, opts *Options, test func(t *testing.T, db *DB)) {
dir, err := ioutil.TempDir("", "badger-test")
require.NoError(t, err)
defer removeDir(dir)
if opts == nil {
opts = new(Options)
*opts = getTestOptions(dir)
} else {
opts.Dir = dir
opts.ValueDir = dir
}
if opts.InMemory {
opts.Dir = ""
opts.ValueDir = ""
}
db, err := Open(*opts)
require.NoError(t, err)
defer func() {
require.NoError(t, db.Close())
}()
test(t, db)
}
他利用了回调函数这个特性实现了这一点,直接屏蔽了资源申请和回收的细节。我立马想到了Nutsdb一开始测试用例,一个db指针所有测试方法共用。很难判断在跑这个测试用例的时候这个db对象是什么状态。给看代码的人造成了很大的负担。看到这段代码我就想到了可以用这种方式重构这个项目一些测试用例。在上上个版本中,一个小哥帮忙完成了这部分工作,现在代码看起来舒服了很多。
2. 链式调用初始化对象
在我们创建一个对象的时候一般都是大开大合式的打法,直接开干。比如:
stu := &Student{
Name: "Elliot",
Age: 18,
}
之前这种代码在Nutsdb中随处可见,在看Badgerdb的代码的过程中,我发现了他这样创建对象的方式,感觉还蛮舒服的。
opt := badger.DefaultOptions(sstDir).
WithValueDir(vlogDir).
WithReadOnly(ro.readOnly).
WithBlockCacheSize(ro.blockCacheSize << 20).
WithIndexCacheSize(ro.indexCacheSize << 20)
很显然,这是一种通过链式调用的方式来创建对象,每一个with相关的函数都返回这个对象本身,这样可以让这条调用链路一直持续下去。后面用这种方式重构了Nutsdb创建对象的方式。这里举个Nutsdb中的例子,方便读者更容易看清这个方式的庐山真面目。
// NewEntry new Entry Object
func NewEntry() *Entry {
return new(Entry)
}
// WithKey set key to Entry
func (e *Entry) WithKey(key []byte) *Entry {
e.Key = key
return e
}
// WithValue set value to Entry
func (e *Entry) WithValue(value []byte) *Entry {
e.Value = value
return e
}
// WithMeta set meta to Entry
func (e *Entry) WithMeta(meta *MetaData) *Entry {
e.Meta = meta
return e
}
// WithBucket set bucket to Entry
func (e *Entry) WithBucket(bucket []byte) *Entry {
e.Bucket = bucket
return e
}
后面创建Entry对象就可以这样子创建啦:
NewEntry().WithKey(key).WithValue(val)
清爽不少。
总结
谁都不能保证代码一直写的很好,永远不写出屎山,关键的地方是,你能不能认识到一些东西可能写的不是很好,当你遇到好的东西可以反哺原来的代码,可能在工作中这个情况会不一样,因为改动,意味着一定的风险。所以这个也是开源项目的意义所在,你可以利用一些机会不断的打磨你的一些技能。换一个角度,人也是一样的,没有人永远是对的,没有人可以知道全部事情,你能不能不断的接受新事物,不断的打磨自己,这是一件比较重要的事情。