在系统规模越大越大、功能越来越复杂的当下,整个系统通常被拆分成多个功能模块,个人或团队负责其中的一个或多个模块。如果说约定好的接口是实现的标准的话,那么最后生成的库就是常见的最终的交付形式之一。那么,该如何开发出优秀的库代码呢?
首先,需要明确基本的功能需求。任何软件系统都是为了服务于具体的业务,业务的具体流程和要求就是对软件的需求。为了明确模块的具体需求,就需要对模块在整个系统中的位置有比较全面的了解,这样才能对它所在的上游接口和底层模块对它的限制和要求有深入的理解。
其次,使用通用的模型和数据结构实现。需求明确之后,剩下就是整个模块如何搭建设计的事情了。这个阶段最重要的能力就是抽象和建模能力。以一个边统计收集底层动态边向上提供查询报警信息的模块为例子,根据其中主要的数据流程,把统计搜集信息的子模块视作生产者,而查询和报警的模块就是消费者,因而整个模型本质就是一个生产者和消费者模型。这个分析的过程就是抽象建模的过程。既然模型已经清晰,那么就不难应用我们在操作系统中学习到关于生产者和消费者模型的基本知识了。当然,回归到具体实现,还需要考虑到下面的问题:数据的格式是否结构化的;数据的规模是几十个Byte,几个KB,还是数MB;是持续不断的增长还是偶尔才增长;需要访问最新的数据还是需要优先访问最早的数据;允许为缓存数据而分配的内存是否有限制;这些数据是否需要储存到日志当中去;是否需要优先从缓存中访问数据等等。上述的这些问题都需要紧密结合具体的业务需求给出答案,并且一旦上述问题清晰之后,我们就不难确定选择什么样的数据结构来组织这些数据。
这里特别要强调的是一旦确定了数据组织的结构,那么务必实现基于该数据结构常用的标准的操作方法或者函数,保证使用该数据结构的程序都有相应的函数或者方法去访问它,而不用再在业务层考虑数据结构内部的细节,这样就能把业务层和具体的数据结构层分离开来,更多的时间和精力能够集中到业务的实现上。比如,如果使用的是环形缓冲区,在具体的业务流程代码里面不应该出现引用环形缓冲区头、尾索引的代码,也不应该在业务层出现判断缓冲区空、满状态的具体代码,这些都必须引用针对环形缓冲区的基本插入、删除、判断空、判断满、计算元素长度的标准函数去实现。有的工程师会设计出带有自己特色的数据结构、甚至实现有些特别的函数,这个本无可厚非,但如果可能还是应该尽量实现标准的数据结构和常用的方法,这样的好处一是会大大降低出错的概率,二是会极大地提高程序的可读性和可维护性能。
再者,需要考虑到所有调用库函数和访问数据的场景,这些可能的场景包括:1. 存在多个调用者同时调用库中函数的可能,并且库代码里面使用了全局变量或者静态变量,那么不要忘记用锁等并发互斥机制保证数据的一致性;2. 在所有的业务逻辑流程当中,是否存在生产者和消费者,或者多读者和多写者的模型,如果存在,需要使用相应的同步互斥机制保证数据一致性;同时,也要兼顾整个系统对代码在空间(内存、存储)和时间(性能、响应时间、吞吐率、IOPS)纬度的具体的要求;而在业务逻辑上千万不要只考虑到主要的、正常执行的操作流程,务必考虑到各种现实中可能出现的错误操作流程和依赖模块出现问题的情况。此外,还需要考虑今后是否需要升级、前后版本如何保证兼容等问题。
最后,进行可测试性设计和开发。实现可测性开发的手段包括但不限于:在模块开发过程中就考虑支持断言、日志、debug Mode等模式来提高程序的可调试程度;每开发一个小的新功能和模块之后,就写出一个测试程序去及时测试;把所有的小测试模块集中起来,整理成一个可以全部运行和检查的自动化测试程序或脚本。
总而言之,一个好的库代码的实现除了需要考虑功能完整性、稳定性、健壮性、鲁莽性之外,还务必需要重视它的通用性、可维护性、可调试性、可读性、数据一致性和时空要求和限制。