http://www.raychase.net/2380
最近在Stack Exchange上面看到一个帖子,是问程序库设计的指导原则的,“What guidelines should I follow while designing a library?”,有趣的是,很多人都在谈论面向设计,各路API设计,还有程序语言设计,唯独搜索“程序库设计”,无论中文还是英文,Google还是百度都找不到太多内容。但是我想,没有程序员会否认库设计的重要性吧,我想在这里结合这个帖子谈谈我的想法。
在这个帖子里面,votes最高的回答,提到了这样几类tips,我在下面简要叙述一下,其中基础的部分包括:
- Pin Map,明确你期望库主要用来做什么,但不要把它定得太死,用户要可以比较方便地做出改变。
- Working Library,一个工作的库,如果它连这点都达不到,一定要注明。没有人希望浪费时间在一个无法工作的程序库上面。
- Basic Readme,清晰地描述库是用来做什么的,测试的情况等等。
- Interfaces,接口必须清晰地定义,这可以帮助库的使用者。
- Special Functions,特殊的功能,一定要注明,包括在readme文档中注明,以及在注释中注明。
- Busy Waits,如果有一些场景需要使用busy wait(我不知道怎么翻译),其过程中可能会出现异常,使用interrupt或者其它妥善的方法来处理。
- Comments,你做的任何的改变都要注释清楚,明确描述接口和其每个参数,方法是做什么的,又返回什么;如果有某个中间方法被调用到,就要注明。
- Consistency,一致性,所有东西,包括注释。相关的方法要放在一个简单的代码文件里面,小但是逻辑一致。
其中的高级部分又包括Detailed Readme,Directory Structure,Licensing和Version Control。
这些都是需要注意的内容,并且大部分对于程序的库设计来说是基础要求,但是这些从重要性来说,并没有说到点上。《C++沉思录》里面有这样一句话:“库设计就是语言设计,语言设计就是库设计”,二者从先定义问题域到后解决问题的思路是类似的。我觉得比较重要的需要考虑的事情包括:
考虑库的目标用户。这听起来扯得有点泛,但实际上这是确切的问题,这是开源库还是你只是在小组内部使用的库,或者是公司内部使用的?用户的能力和需求是不一样的,要求当然不同。
要解决的核心问题。这是上文中Pin Map的一部分,不要重复发明轮子,那么每一个新库都有其存在的价值,这个问题既要通用又要具体,“通用”指的是库总有一个普适性,解决的实际问题对于不同的用户来说是不一样的;而“具体”是指库解决的问题对于程序员来说是非常清晰和直接的。例如设计一个库,根据某种规则把不同的数据类型(XML,BSON或者某种基于行的文本等等)都转换成JSON。
统一的编程风格。很多库都有自己精心设计的一套DSL,比如链式调用等等几种方式,当然,这也和使用的语言有关系。定义一种用起来舒服的编程风格对于程序库的推广是很有好处的。这也是一致性的一个体现。
内聚的调用入口。这和面向对象的“最少知识原则”有类似的地方,把那些不该暴露出去的库内部实现信息隐藏起来,在很多情况下,程序库不得不暴露和要求用户了解一些知识的时候,比如:
1
2
3
4
5
6
7
| MappingConfig config = new MappingConfig(); config.put(MappingConfigConstants.ENCODE, "UTF-8" ); FileBuilder fileBuilder = new StandardFileBuilder(mapping); InputStream stream = fileBuilder.build().getInputStream(); DataTransformer<XMLNode> transformer = new XMLDataTransformer(...); ... transformer.transform(stream); |
这里引入了太多的概念,MappingConfig、FileBuilder、DataTransformer等等,整个过程大致是构造了一个数据源,还有一个数据转换器,然后这个数据转换器接受这个数据源来转换出最后结果的过程。那么:
这些象征着概念的接口和类最好以某种易于理解的形式组织起来,比如放在同一层比较浅的包里面,便于寻找;
也可以建立一个facade类,提供几种常用的组合,避免这些繁琐的对象构建和概念理解,例如:
1
| XXFacade.buildCommonXMLTransformer(); |
向后兼容。当然,这一点也可以归纳到前文提到的一致性里面去。我曾经拿JDKHashTable举了一个例子,它的containsValue和contains方法其实是一样的,造成这种情况的一个原因就是为了保持向后兼容。
依赖管理。依赖管理很多情况下是一个脏活累活,但是却不得不考虑到。通常来说,任何一个库考虑自己的依赖库时都必须慎重,尤其是面对依赖的库需要升级的时候。如果依赖的库出了问题,自己设计的程序库也可能因此连累。
完善的测试用例。通常来说,程序库都配套有单元测试保证,无论是什么语言写的。
健全的文档组织。通常包括教程(tutorial)、开发者文档(developer guide)和接口API文档(API doc)。前者是帮助上手和建议使用的,中间的这个具备详尽的特性介绍,后者则是传统的API参考使用文档。
http://softwareengineering.stackexchange.com/questions/196151/what-guidelines-should-i-follow-while-designing-a-libraryInterfaces
The next thing is that you should have clearly defined interfaces. A working library with convoluted interfaces is frustrating. This will help you yourself use the library later on, and will make things easier for other users. This should be one of the most important aspects to keep in mind.
Whether you follow a top-down or a bottom-up approach, the interfaces should always be clear in your mind. In a bottom-up approach, this may and will be hard, but it should not be ignored. Otherwise, you will end up with a overly complex library which may not be usable.
Consistency
Make sure everything, even the comments, are consistent across the
.h
and .cpp
files.
Keep only related functions within a single file. Having a few small, but logically consistent, modules is better than one huge file with everything in it.