19

我最近对我在项目代码库中看到的一个问题感到越来越沮丧。

我正在开发一个具有 >1M 行代码的大型 Java 项目。接口和类结构设计的很好,编写代码的工程师也很熟练。问题是,为了使代码更简洁,人们在需要重用某些功能时编写实用程序类,结果随着时间的推移和项目的发展,越来越多的实用程序方法出现了。但是,当下一位工程师遇到对相同功能的需求时,他无法知道有人已经在代码中的某处实现了实用程序类(或方法)并在不同的类中实现了该功能的另一个副本。结果是大量的代码重复和太多具有重叠功能的实用程序类。

我们作为一个团队可以实施任何工具或任何设计原则,以防止实用程序类的重复和低可见性?

示例:工程师 A 有 3 个地方需要将 XML 转换为 String,因此他编写了一个名为XMLUtil的实用程序类并在其中放置了一个静态toString(Document)方法。工程师 B 有几个地方可以将文档序列化为各种格式,包括字符串,因此他编写了一个名为SerializationUtilserialize(Document)的实用程序类,并有一个称为返回字符串的静态方法。

请注意,这不仅仅是代码重复,因为上述示例的 2 个实现很可能是不同的(例如一个使用转换器 API,另一个使用 Xerces2-J),因此这可以被视为“最佳实践” “还有问题……

更新:我想我最好描述一下我们开发的当前环境。我们使用 Hudson 进行 CI,Clover 进行代码覆盖,使用 Checkstyle 进行静态代码分析。我们使用敏捷开发,包括日常谈话和(可能不够)代码审查。我们在 .util 中定义了所有实用程序类,由于它的大小,现在在根 (.util) 类下有 13 个子包和大约 60 个类。我们还使用 3rd 方库,例如大多数 apache commons jars 和一些组成 Guava 的 jars。

我很肯定,如果我们让某人负责重构整个包的任务,我们可以将实用程序的数量减少一半,我想知道是否有任何工具可以降低该操作的成本,以及是否有任何方法可以可以尽可能地延缓问题的再次发生。

4

9 回答 9

9

这个问题的一个很好的解决方案是开始添加更多的面向对象。要使用您的示例:

示例:工程师 A 有 3 个地方需要将 XML 转换为 String,因此他编写了一个名为 XMLUtil 的实用程序类,并在其中放置了一个静态 toString(Document) 方法

解决方案是停止使用原始类型或 JVM 提供的类型(String、Integer、java.util.Date、java.w3c.Document),并将它们包装在您自己的项目特定类中。然后您的 XmlDocument 类可以提供方便的 toString 方法和其他实用方法。您自己的 ProjectFooDate 可以包含解析和格式化方法,否则这些方法最终会出现在各种 DateUtils 类等中。

这样,每当您尝试对对象执行操作时,IDE 都会提示您使用实用程序方法。

于 2013-04-14T06:54:19.703 回答
6

你的问题是一个很常见的问题。这也是一个真正的问题,因为没有好的解决方案。

我们在这里处于同样的境地,好吧,我会说更糟糕的是,有 1300 万行代码、营业额和 800 多名开发人员在编写代码。我们经常讨论您描述的同一个问题。

第一个想法 - 您的开发人员已经使用过 - 是重构某些实用程序类中的公共代码。我们对这个解决方案的问题是,即使是结对编程、指导和讨论,我们的人数太多了,无法有效。事实上,我们在子团队中成长,人们在他们的子团队中分享知识,但知识不会在子团队之间传递。也许我们错了,但我认为在这种情况下,即使是结对编程和谈话也无济于事。

我们还有一个架构团队。该团队负责处理设计和架构问题,并制作我们可能需要的通用实用程序。这个团队实际上产生了我们可以称之为公司框架的东西。是的,它是一个框架,有时它运行良好。该团队还负责推动最佳实践,并提高对应该做什么或不应该做什么、什么可用或不可用什么的认识。

良好的核心 Java API 设计是 Java 成功的原因之一。好的第三方开源库也很重要。即使是精心设计的小型 API 也可以提供真正有用的抽象,并有助于大大减少代码大小。但是你知道,制作框架和公共 API 与在 2 小时内编写一个实用程序类根本不是一回事。它的成本真的很高。一个实用程序类的初始编码需要 2 小时,调试和单元测试可能需要 2 天。当您开始在大型项目/团队中共享通用代码时,您实际上是在创建一个 API。你必须确保完美的文档,真正可读和可维护的代码。当您发布此代码的新版本时,您必须保持向后兼容。您必须在公司范围内(或至少在团队范围内)推广它。从您的小型实用程序课程的 2 天增加到 10 天,

而且你的 API 设计可能不是那么好。好吧,并不是你的工程师不聪明——他们确实是。但是您是否愿意让他们在一个小型实用程序类上工作 50 天,以帮助以一致的方式为 UI 解析数字?当您开始使用具有完全不同需求的移动 UI 时,您是否愿意让他们重新设计整个事物?您还注意到世界上最聪明的工程师是如何制作永远不会流行或会慢慢消失的 API 的吗?你看,我们制作的第一个 Web 项目只使用内部框架或根本不使用框架。然后我们添加了 PHP/JSP/ASP。然后在 Java 中我们添加了 Struts。现在 JSF 是标准。我们正在考虑使用 Spring Web Flow、Vaadin 或 Lift……

我想说的是,没有好的解决方案,开销随着代码大小和团队规模呈指数增长。共享大型代码库会限制您的敏捷性和响应能力。任何更改都必须谨慎进行,您必须考虑所有潜在的集成问题,并且每个人都必须接受新特性和新特性的培训。

但软件公司的主要生产力点不是在解析 XML 时获得 10 甚至 50 行代码。无论如何,执行此操作的通用代码将增长到一千行代码,并重新创建一个复杂的 API,该 API 将由实用程序类分层。当这个家伙制作一个用于解析 XML 的实用程序类时,它是一个很好的抽象。他给十几行甚至一百行专业代码命名。这段代码很有用,因为它是专门的。通用 API 允许处理流、URL、字符串等。它有一个工厂,所以你可以选择你的解析器实现。实用程序类很好,因为它只适用于这个解析器和字符串。因为你需要一行代码来调用它。但是,当然,此实用程序代码的用途有限。它适用于这个移动应用程序,或加载 XML 配置。然后'

总之,我会考虑随着团队的成长而分散代码责任,而不是试图整合整个代码库的代码:

  • 将从事一个大项目的大团队转变为从事多个子项目的小团队;
  • 确保接口良好以最大程度地减少集成问题,但让团队拥有自己的代码;
  • 在这些团队和相应的代码库中,确保您拥有最佳实践。没有重复的代码,良好的抽象。使用社区中现有的经过验证的 API。使用结对编程、强大的 API 文档、wiki……但你应该让不同的团队做出选择,构建自己的代码,即使这意味着跨团队重复代码或不同的设计决策。你知道,如果设计决策不同,这可能是因为需求不同。

您真正管理的是复杂性。最后,如果你制作一个单一的代码库,一个非常通用和先进的代码库,你会增加新人加速的时间,你会增加开发人员根本不会使用你的通用代码的风险,并且你会因为任何变化而减慢每个人的速度破坏现有功能的机会要大得多。

于 2011-04-18T09:36:12.747 回答
4

您可以使用几种敏捷/ XP 实践来解决这个问题,例如:

  • 互相交谈(例如在每日站立会议期间)
  • 结对编程/代码审查

然后创建、记录和测试一个或多个可以引用的实用程序库项目。我推荐使用 Maven 来管理依赖/版本。

于 2011-04-11T18:06:58.413 回答
3

您可能会考虑建议将所有实用程序类放在一个组织良好的包结构中,例如com.yourcompany.util.. 如果人们愿意很好地命名子包和类,那么至少如果他们需要找到一个实用程序,他们知道去哪里找。不过,我认为这里没有任何灵丹妙药的答案。沟通很重要。也许如果开发人员在编写新实用程序时向其他开发人员发送一封简单的电子邮件,这足以引起人们的注意。或者一个共享的 wiki 页面,人们可以在其中列出/记录它们。

于 2011-04-11T18:18:49.633 回答
1
  1. 团队沟通(大喊“嘿,有人有 Document toString 吗?”)
  2. 将实用程序类保持在绝对最低限度,并将它们限制在单个命名空间中
  3. 总是想:我怎么能用一个对象做到这一点。在您的示例中,我将扩展 Document 类并将这些toStringserialize方法添加到其中。
于 2011-04-11T18:28:49.173 回答
0

将 IDE“代码完成”功能与支持类型扩展的语言(例如 C# 和 F#)相结合时,这个问题会有所帮助。因此,想象 Java 有这样一个特性,程序员可以在 IDE 中轻松探索类的所有扩展方法,例如:

Document doc = ...
doc.to //list pops up with toXmlString, toJsonString, all the "to" series extension methods

当然,Java 没有类型扩展。但是您可以使用 grep 在您的项目中搜索“所有以 SomeClass 作为第一个参数的静态公共方法”,以获得类似的洞察力,以了解已经为给定类编写了哪些实用程序方法。

于 2011-04-11T18:43:39.883 回答
0

构建一个识别“相同功能”的工具非常困难。(理论上这实际上是不可能的,在实践中你可能需要一个定理证明器)。

但经常发生的是人们克隆接近他们想要的代码,然后对其进行自定义。 您可以使用克隆检测器找到这种代码

我们的CloneDR是一种工具,用于基于使用参数化语法树来检测准确和几乎未命中的克隆代码。它匹配代码的解析版本,因此它不会被布局、更改的注释、修改的变量名或在许多情况下插入或删除的语句所混淆。有许多语言的版本(C++、COBOL、C#、Java、JavaScript、PHP 等),您可以在提供的链接中查看克隆检测运行的示例。它通常会发现 10-20% 的重复代码,如果您将这些代码抽象到基于宗教的库方法中,您的代码库实际上会缩小(这发生在使用 CloneDR 的一个组织中)。

于 2011-04-12T03:10:22.363 回答
0

您正在寻找一种可以帮助您解决这个不可避免的问题的解决方案,那么我可以推荐一个工具:

  • TeamCity:一个令人惊叹的易于使用的产品,可以管理从存储库构建的所有自动化代码并运行单元测试等
    。对于大多数人来说,它甚至是免费产品。
    更好的部分:它在所有代码中内置了代码重复检测

更多要阅读的内容:

于 2011-04-13T20:57:17.410 回答
0
  1. 一个标准的应用程序实用项目。根据功能构建具有受限扩展范围和包的 jar。
  2. 使用 apache-commons 或 google 集合等常用实用程序并提供抽象
  3. 维护知识库和文档以及 JIRA 跟踪以查找错误和增强功能
  4. 进化重构
  5. findbugs 和 pmd 用于查找代码重复或错误
  6. 审查和测试实用工具的性能
  7. 利用业力!要求团队成员在现有丛林代码中找到或需要新代码时为代码库做出贡献。
于 2011-04-14T04:27:38.813 回答