6

我对这个问题的回答是“不”。但我的同事不同意。

我们正在重建我们的产品,并在短期内做出许多关键决定。

在做我自己的一些工作时,我注意到我们有一些内部 C++ 类来抽象一些 POSIX API(线程、互斥体、信号量和 rw 锁)和其他实用程序类。请注意,这些类是基本类,尚未从 Linux 移植(可移植性是重建的一个因素。)我们也在使用 POCO C++ 库。

我提请我的同事注意这一点,并建议我们放弃我们的内部课程,转而使用他们的 POCO 同等课程。我想充分利用我们已经在使用的库。他们建议我们应该使用 POCO 实现我们的内部类,并根据需要进一步抽象额外的 POCO 类,以免依赖于任何特定的 C++ 库(引用未来的未知数——如果我们想使用不同的库/框架,比如QT 或 boost,如果我们选择的那个结果不好或开发变得不活跃,等等)

他们也不想重构遗留代码,通过使用我们自己的类抽象 POCO 的部分,我们可以实现额外的功能(经典 OOP)。这两个论点我都可以理解。但是,我认为如果我们要重新编码,我们应该做大,或者回家。现在是重构的时候了,它真的不应该那么糟糕,特别是考虑到我们的类和 POCO 中的类(线程等)之间的相似性。关于第二点我不知道该说什么 - 我们应该只使用需要功能的扩展类?

我的同事也不想到处乱扔 POCO 命名空间。我认为我们应该选择一个库/框架/工具包,并坚持下去。充分利用其功能。这不是典型的做法吗?我见过的唯一一个抽象整个框架的项目是 Freeswitch(它提供了自己的 APR 接口。)

一个建议是,我们向彼此和潜在客户公开的 API 应该没有 POCO,但它会出现在实现中(这是有道理的。)

我们没有人真正有过此类设计决策的经验,这在当前产品中有所体现。从小就在这方面,我有一些直觉把我带到这里,但也没有实践经验。我真的想避免对已经解决的问题的糟糕解决方案。

我想我的问题归结为:在构建产品时,我们是否应该 a) 选择一个占主导地位的框架作为我们大部分代码的基础,并且 b) 期望该框架与产品紧密耦合?这不是框架的重点吗?(框架或库更适合 POCO 吗?)

4

2 回答 2

4

首先,您公开的 API 绝对应该不含 POCO、boost、qt 或任何其他不属于标准 C++ 库的类型。这是因为基础库有自己的发布周期,与您的库的发布周期不同。如果您的库的用户也使用 boost,但使用的是不同的、不兼容的版本,他们将需要花时间来解决不兼容问题。这条规则的唯一例外是当您设计一个库作为更广泛框架的一部分发布时 - 例如,添加到 POCO 工具包。在这种情况下,您的库的发布与整个工具包的发布相关联。

但是,在内部,您应该避免使用自己的包装器,除非您抽象出来的库是真正的“商品库” 1. 这样做的原因是当你在你的类后面隐藏一个外部库时,大多数时候你会模仿你隐藏的库的抽象级别。使用您的包装器的代码将按照外部库规定的抽象级别进行编程。当您将包装器背后的实现换成不同的框架时,您很可能会(1)调整新框架以适应旧框架的抽象级别,或者(2)需要改变方式你使用你的包装器。这两种情况都值得怀疑:如果你这样做(1),也许你不应该首先切换,如果你这样做(2),那么你的包装器被证明是无用的。


1 “商品库”是指提供某种抽象级别的库,该抽象级别在其他类似用途的库中很常见。

于 2012-09-11T15:33:05.713 回答
4

在两种情况下,我认为值得拥有自己的包装器:

1)您已经查看了不同系统/库上的几种不同互斥锁实现,您已经建立了一组共同​​的要求,它们都可以满足并且对于您的软件来说已经足够了。然后您定义该抽象并实施一次或多次,因为您知道您已经提前计划了灵活性。您编写的其余代码依赖于您的抽象,而不依赖于当前实现的任何附带属性。我过去曾这样做过,尽管我无法向您展示代码。

这种“最不常用接口”的一个典型例子是改变rename文件系统抽象,基于 Windows 无法实现原子重命名现有文件。因此,如果您将来可能将当前的 *nix 实现换成无法做到这一点的实现,那么您的代码不能依赖原子重命名替换。您必须从一开始就限制接口。

如果做得好,这种接口可以大大简化任何类型的未来移植,无论是到新系统还是因为您想更改第三方库依赖项。然而,一个完整的框架可能太大而无法成功地做到这一点——本质上你将发明和编写自己的框架,这不是一项微不足道的任务,而且可以想象是比编写实际软件更大的任务。

2)您希望能够模拟/存根/假冒/欺骗/剽窃/无论下一个聪明的技术是什么,测试中的互斥体,并决定如果你有自己的包装器,你会发现这比你更容易'试图弄乱来自第三方库或内置的符号。

请注意,定义您自己的函数,称为wrap_pthread_mutex_initwrap_pthread_mutex_lock,精确模仿pthread_*函数并采用完全相同的参数,可能满足(2)但不满足(1)。无论如何,正确地执行 (2) 可能需要的不仅仅是包装器,您通常还希望将依赖项注入到您的代码中。

在灵活性的标题下做额外的工作,而实际上没有提供灵活性,这几乎是浪费时间。根据另一个线程环境实现一个线程环境可能非常困难,甚至被证明是不可能的。如果您决定将来从 pthreads 切换到std::threadC++ 中,那么使用看起来与不同名称的 pthreads API 完全相同的抽象(大约)没有任何帮助。

对于您可能进行的另一个可能的更改,在 Windows 上实现完整的 pthreads API 是可能的,但可能比仅实现您实际需要的更困难。因此,如果您移植到 Windows,您所有的抽象节省就是搜索和替换软件其余部分中的所有调用的时间。您仍然需要 (a) 为 Windows 插入一个完整的 Posix 实现,或者 (b) 做一些工作来弄清楚您真正需要什么,然后只实现它。您的包装器对实际工作没有帮助。

于 2012-09-11T16:44:18.633 回答