最近尝试写了一个SkipList的持久化实现, 目标是在磁盘上操作SkipList数据结构和数据,这些数据结构和数据保存在一些自定义的存储格式中。

和平时写公司业务功能代码一样,我使用了测试驱动开发(TDD,或者叫测试驱动设计,desgin)的过程。 这意味着我首先针对功能写测试代码,然后实现功能,接着在自动测试用例的保护下,演进和重构代码, 如此反复循环。

但是在做SkipList的持久化实现时,我发现这套流程在本次实践里的作用有限。

主要的问题是以上流程无法让我得到一个完美的存储格式和相关实现,当出现之前没有考虑到的情况时, 重构的代价非常大,几乎需要重新考虑整个设计。

比如,当我实现SkipList的插入操作时,一些用例会让我考虑到单个文件大小限制的问题, 从而让我设计出可以使用多个文件的结构。

同时,在实现查询功能时,个人开发经验和用例能够让我考虑到磁盘的读写效率,在设计中采用一些磁盘友好的实现, 以便快速的进行文件寻址,和对缓存友好。

但是,当最后实现删除操作时,我发现之前的设计在实现文件收缩时非常麻烦,几乎无法实现。勉强做出一个实现后, 又发现在并发操作时,删除操作的代码带来了bug,定位修改起来非常麻烦。

这些问题让我反思,对于存储结构、网络协议这类设计,其实TDD是无法启发良好的设计的, 因为TDD关注的是代码本身的自洽和设计,而不能完美驱动协议、或者需要领域知识积累(如存储格式设计)的设计。

对于编程语言、网络协议、数据库存储引擎这类设计,还可能需要发布多个版本,版本要做到向前兼容, 需要很大的智慧和领域知识功底,这些都不是TDD能带来的。

如果不得不做这类设计,需要前期就尽可能把所有功能、需求都考虑到实现中,而不是先简单实现一个可用的版本, 并且实现需要有灵活性,在之后可以方便兼容和演进,这和代码的灵活性还不一样, 考验的是设计者对领域各种特点和限制的理解。

所以,和其他技术、软件方法一样,TDD不是银弹,不可能适合所有的场景,但这不应该成为我们拒绝它的理由,而应该思考为什么没有满足我们的预期。