您是否应该将您采用的第三方库包装到项目中?

时间:2009-12-16 16:53:06

标签: refactoring

我今天和同事讨论过。

他声称无论何时使用第三方库,都应该为它编写一个包装器。因此,您可以随时更改内容并适应特定用途。

我不同意始终这个词,关于log4j的讨论出现了,我声称log4j经过了充分测试和经过时间验证的API和实现,并且所有可想象的都可以后验配置而且没有什么是你的应该包装。即使你想要包装,也有经过验证的包装器,如commons-logging和log5j。

我们讨论的另一个例子是Hibernate。我声称它有一个非常大的API被包装。此外,它还有一个分层API,可让您根据需要调整其内部。我的朋友声称他仍然相信它应该被包装但是他没有这样做因为API的大小(这个同事在我们当前的项目中比我更老)。

我声称this,并且应该在特定情况下进行包装:

  • 您不确定图书馆如何满足您的需求
  • 您只会使用一小部分图书馆(在这种情况下,您可能只展示其部分API)。
  • 您不确定库的API或实现的质量。

我还认为,有时你可以包装代码而不是库。例如,将与数据库相关的代码放在DAO层中,而不是抢先包装所有的休眠。

嗯,最后这不是一个真正的问题,但您的见解,经验和意见都受到高度赞赏。

16 个答案:

答案 0 :(得分:61)

这是YAGNI的完美示例:

  • 这是更多的工作
  • 它会使您的项目膨胀
  • 可能会使您的设计复杂化
  • 它没有直接的好处
  • 你为它写的情节可能永远不会显现
  • 当它发生时,你的包装器很可能需要完全重写,因为它与你正在使用的具体库绑得太紧,新的API与你的API不匹配。

答案 1 :(得分:10)

嗯,明显的好处是切换技术。如果你有一个不推荐使用的库,并且你想要切换,你可能最终会重写大量的代码以适应这种变化,而如果它被包装,你可以更轻松地为新的lib编写一个新的包装器。 ,而不是改变你的所有代码。

另一方面,这意味着你必须为你所包含的每个普通库编写一个包装器,这可能是一个不可接受的开销量。

我的行业是关于速度的,所以我唯一能够证明编写包装器的理由是,它是围绕某个可能会定期发生巨大变化的关键库。或者,更常见的是,如果我需要使用一个新的库并将其塞进旧代码中,这是一个不幸的现实。

绝对不是“永远”的情况。这是可取的。但是时间并不总是存在,最后,如果编写一个包装器需要几个小时而长期代码库的更改将会很少,而且微不足道......为什么要这么麻烦?

答案 2 :(得分:6)

我同意几乎所说的一切。

包装第三方代码唯一有用的方法(违反YAGNI)是单元测试。

模拟静态等等要求你包装代码,这是为第三方代码编写包装器的正当理由。

在记录代码的情况下,不需要它。

答案 3 :(得分:5)

这里的问题部分是“包装”一词,部分是错误的二分法,部分是JDK与其他一切之间的错误区别。

“包装器”一词

如你所说,包装所有Hibernate是一个完全不切实际的企业。

另一方面,将Hibernate依赖项限制为已识别的,受控制的源文件集可能很实用,并且可以获得相同的结果。

错误的二分法

错误的二分法是未能认识到第三种选择:标准。如果您使用JPA注释,则可以将Hibernate替换为其他内容。如果您正在编写Web服务并使用JAX-WS注释和JAX-B,则可以在JDK,CXF,Glassfish或其他任何内容之间进行交换。

虚假区别

当然,JDK变化缓慢,不太可能死亡。但主要的开源软件包也变化缓慢,不太可能死亡。数以千计的开发人员和项目使用Hibernate。与Java本身相比,Hibernate没有更多的风险消失或进行根本不兼容的API更改。

答案 4 :(得分:5)

如果您计划包装的库在其来自同一域中的其他产品的“访问原则,隐喻和习语”中是唯一的,那么您的包装器几乎与该库类似,并且不会对您有所帮助如果你有一天切换到另一个库,因为你需要一个新的包装器。

如果以与其他库类似的方式访问库并且相同的包装器可以应用于这些库,那么它们可能是基于某些现有标准编写的,并且存在一些已经存在的公共层来访问它们。

如果我确定我必须在生产中支持多个不同的库,我只会选择包装器。

答案 5 :(得分:4)

决定是否包装库的主要因素是库更改对代码的影响。当只从1个类调用库时,更改库的影响将是最小的。如果在另一侧,在所有类中调用库,则更有可能使用包装器。

答案 6 :(得分:4)

  1. 在项目开始时,应使用原型来测试第三方库选择的任何不确定性,以测试第三方库的可扩展性/适用性/无论如何。

  2. 如果您决定继续并提供完全的分离/抽象支持,则应该花费成本并最终由项目发起人批准 - 最终这是一个商业决策,因为有人必须为此付费并且需要工作这样做(除非它绝对是微不足道的,在这种情况下,api可能是低风险)。

  3. 一般来说,经验丰富的建筑师会选择一种技术,他们可以有合理的信心,并且有经验,并且他们有信心会持续使用应用程序的生命周期,否则他们将消除决策中的任何风险在项目的早期,因此大部分时间都不需要这样做

答案 7 :(得分:4)

没有。 Java架构师/想要蜜蜂忙于设计以抵御虚构的变化。

使用现代IDE,当您需要更改时,这是一块蛋糕。在此之前,请保持简单。

答案 8 :(得分:3)

我倾向于同意你的大多数观点。使用绝对值通常会让你陷入困境,并且说你应该“总是”做一些限制你灵活性的事情。我会在你的列表中添加更多点数。

当你围绕一个非常常见的API使用包装代码时,比如Hibernate或log4j,你就会更难以引入新的开发人员。新的开发人员现在必须学习一个全新的API,如果你没有包装他们本来就非常熟悉的代码。

另一方面,您还将开发人员的视图限制在API中。使用API​​的高级功能需要花费更多时间,因为您必须确保以可以处理它的方式实现包装器。

我见过的许多包装层也非常特定于底层实现。所以,如果你在log4j周围写一个日志包装器,你会想到log4j术语。如果出现一些新的酷框架,它可能会改变整个范例,因此您的包装代码不会像您想象的那样迁移。

我绝对不是说包装代码总是不好,但正如你所说,你需要考虑很多因素。

答案 9 :(得分:3)

即使经过充分测试和久经考验的第三方库,包装的目的是您可能决定在将来某个时候切换库。包装它可以更轻松地切换核心应用程序中的任何代码。只需要更改包装器。

如果你绝对确定你永远不会(另一个绝对)在你的项目中使用不同的日志框架,那就继续跳过包装器。即使这样说,我也可能不会写包装,直到我知道我需要它,就像我第一次需要切换一样。

答案 10 :(得分:3)

这是一个有趣的问题。

我在系统中工作过,我们在我们使用的库中找到了showstopper bug,而且上游要么不再维护,要么对修复不感兴趣。在像Java这样的语言中,通常无法修复包装器中的内部错误。 (幸运的是,如果它们是开源的,你至少可以自己修复它们。)所以这里没有任何帮助。

但是我经常使用一种语言,你可以随时轻松地修改库,而不会看到甚至没有源代码 - 例如,我通常会向现有类添加新方法。因此,在这种情况下,包装没有意义:只需进行所需的更改。

另外,你的同事是否在称为“图书馆”的东西上划线? Java本身呢?他是否包装了内置类?他是否包装文件系统?线程调度程序?内核? (也就是说,使用他自己的包装器 - 从某种意义上说,所有都是CPU的包装器,但听起来他在谈论源回购中的包装器完全在你的控制之下。)我当新版本出现时,内置功能会发生变化或消失。 Java 不受此影响。

因此总是写一个包装器的想法归结为赌注。假设他只包装第三方图书馆,他似乎暗中打赌:

  • “第一方”功能(如Java本身,内核等)永远不会改变
  • 当“第三方”功能发生变化时,它将始终以可以在包装器中修复的方式完成

你的情况是真的吗?我不知道。在我做过的大中型Java项目中,对我来说很少。我不会花费精力包装所有第三方库,因为它似乎是一个糟糕的赌注,但你的情况肯定不同于我的。

答案 11 :(得分:3)

有一种情况,你有充分的理由可以包装。即,如果你需要测试东西,默认的第三方对象是重量级的。然后拥有一个界面可以真正发挥作用。

请注意,这不是要替换库,而是在可以管理的地方无关紧要。

答案 12 :(得分:2)

在大多数情况下,包装整个库是样板,无效和错误。它可以以一种非常聪明的方式完成。我想说包装库是适合的,主要是在UI组件库的情况下,同样,你必须在你需要的所有组件中添加一些你的核心功能。< / p>

  • 如果需要进行太多修改和添加,这很可能不是您正在寻找的库
  • 如果有适量的添加和修改 - 在这些情况下总会有设计模式。例如,Decorator pattern(允许动态地将新/附加行为添加到现有对象)非常适合大多数情况。
  • IDE搜索/替换和重构功能提供了一种简单的方法,可以在需要进行一些重要更改并显示包装对象时,在所有需要的位置更改代码。 (当然,单元测试在这里会有所帮助;))

答案 13 :(得分:2)

根据我的经验,如果您充分利用抽象,问题就变得没那么严重了。耦合到库就像耦合到任何其他接口。因此,如果需要更换实现,则需要减少意外耦合和必要的重写范围。不要将你的应用程序逻辑绑定到某个构造,但不要仅仅围绕某些东西形成一堆愚蠢的(字面上的)包装器并期望获得任何好处。

包装器通常不会获得任何东西,除非它回答特定目的(例如多态化非多态构造)。它们经常出现在重构中,但我不建议在它们上面构建一个架构。当然有一些例外,但有任何原则。

这不适用于适配器。当您想要实际更改库的接口及其用途与项目中的体系结构,代码或域概念一致时,适配器可能是非常重要的组件。

答案 14 :(得分:1)

你应该经常,有时,很少或从不这样做。甚至你的同事也不会这样做,但是指导性案例总是而且永远不会。假设有时是必要的。如果你从未包装过一个图书馆,那么最糟糕的结果就是有一天你发现你所使用的图书馆是必要的。这需要一些时间来包装该库并对客户端执行霰弹枪手术。问题在于,这种可能性是否需要花费更多的时间,而不是习惯性地提供很少需要的包装,而是从不执行霰弹枪手术。

我的直觉是呼吁YAGNI(你不需要它)原则并选择“很少”。

答案 15 :(得分:1)

我不会将它作为一对一的包装,但我会将应用程序分层,以便尽可能地替换每个部分。 ISO OSI模型适用于所有类型的软件: - )