POCOC++ 库(POCO 是"便携式组件"——Portable Components——的缩写)是一组开源 C++ 类库的集合,可以简化并加速以网络为中心的便携式 C++ 应用的开发。这些库提供了丰富的功能,从 HTTP 和 HTTPS 客户端与服务器,到 XML 解析、数据加密、线程支持等等。
15 年多来,我们一直依赖 POCO 库来验证 CppDepend 是否能准确评估实现良好的项目。因此,这份评估并非来自与该库的短暂接触,而是来自对过去 15 年间其众多版本的深入分析。
让我们来看一段 POCO 源代码中的代码片段:
该方法的实现具有以下特点:
- 参数很少。
- 使用断言来检查输入是否有效。
- 变量名易于理解。
- 方法很短。
- 函数体中没有不必要的注释;代码本身就能说明问题。
- 函数体缩进良好。
- 在适当的地方使用了 STL。
深入研究 POCO 源代码时,人们很容易观察到其实现的一致性,因为同样的最佳实践规则被应用于每个函数。在探究源代码时会发现,即使是 C++ 初学者也能轻松理解 POCO。具体来说,POCO 具有以下特点:
- 它没有过度工程化,理解其实现也不需要具备高深的 C++ 技能。
- 提供给最终用户的公共类组织良好、使用简单直接。
- 设计是模块化的,库被划分为若干项目,每个项目解决一个特定需求。
- 对于寻求高级用法的用户来说,扩展、定制或修改某些类的行为非常简单。
在本文中,我们将聚焦其设计的一些关键方面:
抽象性与不稳定性
Robert C. Martin 写过一篇有趣的文章,介绍了一组可用于衡量面向对象设计质量的度量,衡量的角度是该设计各子系统之间的相互依赖关系。
以下是他在文章中关于模块间相互依赖的论述:
是什么让一个设计变得僵化、脆弱且难以复用?是设计内部各子系统之间的相互依赖。如果一个设计无法被轻易修改,它就是僵化的。这种僵化源于这样一个事实:对高度相互依赖的软件进行单一修改,就会在其依赖模块中引发一连串的变更。当设计人员或维护人员无法预测这串连锁变更的范围时,变更的影响就无法估计。这使得变更的成本无法估计。面对这种不可预测性,管理者变得不愿批准变更。于是设计就变得僵化了。
而为了对抗僵化,他引入了传入耦合(Afferent coupling)、传出耦合(Efferent coupling)、抽象性(Abstractness)、不稳定性(Instability)、"距主序列的距离"以及"抽象性-不稳定性"图等度量。
"抽象性-不稳定性"图可用于识别难以维护和演进的项目。下面是 POCO 库的"抽象性-不稳定性"图:

这张图背后的理念是:一个代码元素被使用得越广泛,它就应该越抽象。换句话说,避免过度依赖实现;转而依赖抽象。所谓被广泛使用的代码元素,我指的是被程序中其他项目大量使用的项目(但这个理念同样适用于包和类型)。
让整个代码库广泛使用具体类型并不是个好主意。这会在程序中形成"痛苦地带"(Zone of Pain),在那里修改实现可能会影响程序的大部分。而且众所周知,实现比抽象更经常发生变化。
上图中的主序列线(虚线)展示了抽象性与不稳定性应如何平衡。稳定的组件会位于左侧。查看主序列线可以发现,这样的组件应该非常抽象,才能接近理想的线——另一方面,如果它的抽象程度很低,就会位于被称为"痛苦地带"的区域。
只有 Foundation 项目处于痛苦地带内,这是正常的,因为它被其他项目广泛使用,且主要包含非抽象的工具类。
类型内聚性
单一职责原则指出,一个类不应该有多于一个的变更原因。这样的类被称为内聚的。高 LCOM 值通常表明类内聚性差。LCOM 度量有几种变体。LCOM 的取值范围是 [0-1]。LCOMHS(HS 代表 Henderson-Sellers)的取值范围是 [0-2]。注意,LCOMHS 度量通常被认为在检测非内聚类型方面更有效。LCOMHS 值高于 1 应被视为警示。


只有 1% 的类型被认为内聚性不佳。
在本文中,我们对 POCO 的设计进行了简要的介绍。在接下来的文章中,我们将更详细地探讨其设计与实现,以理解为什么这个库能成为实现良好的开源 C++ 库中的佼佼者。
