2.2.1 共享库的取舍与不足
一旦抽取新库,它就成为一个新的实体,有自己的编码风格、部署流程以及编码规范。在我们的语境里,“库”意味着对代码进行了打包操作(将其封装成.jar文件、.dll文件,或者Linux平台上的.so文件),它可以被多个项目使用。某个团队或者某个开发者若要负责新代码库的维护,则需要建立部署流程、验证项目代码的质量、开发新功能等。并且,这是一个持续不断的过程。
如果你决定采用共享库,就要定义一系列的规范与流程,包括编码规范、部署流程等。不过,只要创建过一次共享库,同样的规范与流程可以复用。添加第一个共享库的开销可能比较高,后续要低得多。
这种方式最显而易见的缺点之一是新创建的库需要使用与消费端一致的程序设计语言。举个例子,如果Payment和Person服务使用不同的程序设计语言开发,一个使用Python,另一个使用Java,那我们就不太可能采用共享库的方式解决代码重复问题。不过,实际项目中,这极少成为问题,因为服务通常都采用同一种程序设计语言或者同体系的程序设计语言(譬如基于JVM的语言)创建。当然,我们也可以用不同的技术创建服务生态。然而,这会极大地增加系统的复杂性。通常这意味着我们需要雇用熟稔各种技术栈的专家,他们能使用各式各样的工具,譬如基于不同技术栈的构建系统、包管理器等。你选择的程序设计语言决定了你要采用什么生态,语言与生态是紧密相关的。
开源贡献
JVM开源生态中有很多活跃的开源社区,它们开发、维护了各式各样的库。创建一个独立的库并将其开源之前,最好先调研开源社区中是否已经存在类似的库。当然,要适配自己的需求,你可能需要做一定的扩展。
如果开源社区中不存在类似的库,你也可以将自己的代码贡献到社区。通过向现有的开源项目贡献代码,更多的用户可以使用你的成果。而你将获得部署流程的支持以及免费的推广。这样一来,更多的人会知道你的库,并重用其中的代码。
很多时候,我们会用某种语言(譬如C语言)编写一个库,再将其封装到你选择的本地接口(譬如Java本地接口)语言中。然而,这种方式可能会带来问题,因为如此一来我们的代码需要经过另一层的间接调用。封装在原生接口内的代码在不同的操作系统之间可能是不兼容的,或者它的方法调用甚至比封装语言(譬如Java)的方法调用慢。基于这些考虑,接下来的讨论中,我们将专注于使用同一种技术栈的语言生态。
新创建的库需要在公司内部大力推广,只有这样,别的团队才能了解它,需要的时候才会使用它。否则就可能会出现混杂使用的情况,即有些团队用了新的库,另一些团队依旧还在使用冗余的代码。
利用存储库管理器是共享库的好办法,不过你需要为库文件维护一份文档。通常情况下,拥有良好测试的项目可以降低开发者为其贡献代码的难度。如果开发者可以使用你的测试套件方便地做一些实验,他们会更愿意使用你的库并为其贡献代码。另外,库的文档有时会由于欠维护而过期,这一点值得特别注意。因此定期更新文档非常重要。
同样,测试也需要及时维护,保持其与产品行为一致。这是帮助你在公司内部推广库的极好营销手段,可以让潜在用户对你的库的品质更有信心。当然,如果你选择了冗余代码的方式,那就需要在所有的地方测试那些重复的代码。这意味着你也需要有重复的测试代码。
不能因为测试覆盖率高就放弃维护库的文档。如果你希望靠查看测试代码了解如何使用一个新的库,可能困难重重,除非编写这些测试代码时就考虑了要将其作为文档提供给用户。测试需要覆盖各种使用库的方式,不仅局限在推荐的方式上。测试代码能回答某些问题,但是,和专用的帮助页面相比还有很大差距,帮助页面不仅提供了教学实例,还提供了帮助新手入门的内容。