在Java中pipe理高度重复的代码和文档

高度重复的代码通常是一件坏事,并有devise模式,可以帮助最大限度地减less这一点。 但是,由于语言本身的限制,有时候这是不可避免的。 从java.util.Arrays获取以下示例:

 /** * Assigns the specified long value to each element of the specified * range of the specified array of longs. The range to be filled * extends from index <tt>fromIndex</tt>, inclusive, to index * <tt>toIndex</tt>, exclusive. (If <tt>fromIndex==toIndex</tt>, the * range to be filled is empty.) * * @param a the array to be filled * @param fromIndex the index of the first element (inclusive) to be * filled with the specified value * @param toIndex the index of the last element (exclusive) to be * filled with the specified value * @param val the value to be stored in all elements of the array * @throws IllegalArgumentException if <tt>fromIndex &gt; toIndex</tt> * @throws ArrayIndexOutOfBoundsException if <tt>fromIndex &lt; 0</tt> or * <tt>toIndex &gt; a.length</tt> */ public static void fill(long[] a, int fromIndex, int toIndex, long val) { rangeCheck(a.length, fromIndex, toIndex); for (int i=fromIndex; i<toIndex; i++) a[i] = val; } 

上面的代码在源代码中出现了8次,文档/方法签名的变化很小,但是对于每个根数组typesint[]short[]char[]byte[]boolean[]double[]float[]Object[]

我相信,除非有人反思(这本身就是一个完全不同的主题),否则这种重复是不可避免的。 我知道作为一个实用程序类,这种高度重复的Java代码是非常不典型的,但即使是最好的实践, 重复也会发生 ! 重构并不总是可行的,因为它并不总是可能的(明显的情况是重复是在文档中)。

显然维护这个源代码是一场噩梦。 文档中的一个轻微的错字,或执行中的一个小错误,乘以无数次重复。 事实上,最好的例子恰好涉及这个确切的类:

谷歌研究博客 – 额外的,额外的 – 阅读所有关于它:几乎所有的二进制search和Mergesorts被破坏(由Joshua Bloch,软件工程师)

该错误是一个惊人的微妙的,发生在许多人认为只是一个简单而直接的algorithm。

  // int mid =(low + high) / 2; // the bug int mid = (low + high) >>> 1; // the fix 

上面的代码在源代码中出现了11次

所以我的问题是:

  • 在实践中如何处理这些重复的Java代码/文档? 他们如何开发,维护和testing?
    • 你是否从“原创”开始,尽可能成熟,然后根据需要复制粘贴,希望你没有犯错?
    • 如果您在原始文件中犯了错误,那么您只需要在任何地方修复它,除非您愿意删除这些副本并重复整个复制过程。
    • 你也应用这个testing代码相同的过程?
  • Java会受益于某种有限使用的源代码预处理这种事情吗?
    • 也许Sun有自己的预处理器来帮助编写,维护,logging和testing这类重复的库代码?

一个评论需要另一个例子,所以我从Google Collections: com.google.common.base.Predicates行276-310( AndPredicate )vs 312-346行( OrPredicate )中OrPredicate

这两个类的来源是相同的,除了:

  • AndPredicate vs OrPredicate (每class出现5次)
  • "And(" vs Or(" (在各自的toString()方法中)
  • #and和vs #or (在@见Javadoc评论)
  • truefalse (in apply ;!可以重写出expression式)
  • -1 /* all bits on */ vs 0 /* all bits off */ in hashCode()
  • &= vs |= in hashCode()

对于那些绝对需要表演的人来说,拳击,拆箱,聚集的collections品,什么都不是大不了的。

同样的问题发生在性能计算上,你需要同样的复合体来处理float和double(比如Goldberd的“ 每个计算机科学家应该知道的有关浮点数的文章中所述的一些方法)。

有一个原因,为什么Trove的TIntIntHashMap在处理类似数量的数据时围绕着Java的HashMap<Integer,Integer>运行。

那么如何编写Trove集合的源代码?

当然通过使用源代码工具:)

有几个Java库可以获得更高的性能(远高于默认的Java库),它们使用代码生成器来创build重复的源代码。

我们都知道,“源代码工具”是邪恶的,代码生成是废话,但仍然是那些真正知道自己在做什么的人(即像Trove这样写东西的人)做到这一点:)

对于什么是值得的,我们会生成包含如下大警告的源代码:

 /* * This .java source file has been auto-generated from the template xxxxx * * DO NOT MODIFY THIS FILE FOR IT SHALL GET OVERWRITTEN * */ 

如果您绝对必须复制代码,请按照您提供的优秀示例进行操作,并将所有代码分组到一个易于查找和修复的位置,以便在进行更改时进行修改。 logging重复,更重要的是,重复原因,使每个人后,你都知道这两个。

维基百科不要重复自己(DRY)或复制是邪恶的(DIE)

在某些情况下,执行DRY哲学所需的努力可能比维护数据的单独副本的努力要大。 在其他一些情况下,重复的信息是不可变的,或者保持在足够的控制下,使DRY不是必需的。

可能没有答案或技术来防止这样的问题。

即使像Haskell这样的花式裤子语言也有重复的代码( 参见我在haskell和serialization上的文章 )

看来这个问题有三种select:

  1. 使用reflection和失去performance
  2. 使用模板Haskell或Caml4p等同于您的语言的预处理,并与糟糕生活
  3. 或者如果你的语言支持,我个人最喜欢使用macros(scheme,lisp)

我认为macros与预处理不同,因为macros的语言通常与目标是相同的语言,因为预处理是不同的语言。

我认为Lisp / Schememacros可以解决许多这些问题。

我知道Sun必须为Java SE库代码编写这样的文档,也许其他第三方库编写者也要这样做。

不过,我认为将这些文档复制并粘贴到文件中是非常浪费的,只能在内部使用。 我知道很多人会不同意,因为这会让他们内部的JavaDocs看起来不那么干净。 然而,权衡是让代码更加干净,在我看来,更重要。

Java原始types会使你受到影响,特别是当涉及到数组时。 如果你具体询问涉及原始types的代码,那么我只是想尽量避免它们。 如果使用装箱types,Object []方法就足够了。

一般来说,你需要进行大量的unit testing,除了采取反思之外,没有其他的工作要做。 就像你说的那样,这完全是另一个话题,但不要太反思。 先写出DRYest代码,然后对其进行分析,确定reflection性能是否足够糟糕,以确保写出并维护额外的代码。

您可以使用代码生成器来使用模板构build代码的变体。 在这种情况下,Java源代码是生成器的产物,实际代码是模板。

给定两个声称相似的代码片段,大多数语言的构build抽象的工具有限,这些抽象将代码片段统一为一个整体。 要抽象当你的语言不能做到这一点,你必须走出语言: – {

最通用的“抽象”机制是一个完整的macros处理器,可以在实例化它时将任意计算应用到“macros体”(思考后缀或string重写系统,这是图灵function)。 M4和GPM是典型的例子。 C预处理器不是其中之一。

如果你有这样一个macros处理器,你可以构build一个“抽象”作为一个macros,并运行你的“抽象”源文本上的macros处理器来产生你编译和运行的实际源代码。

您也可以使用更多限制版本的创意,通常称为“代码生成器”。 这些通常不是图灵function,但在很多情况下,它们工作得不错。 这取决于你的“macros实例化”如何复杂。 (人们迷恋C ++模板机制的原因是尽pipe它很丑陋,但它具有图灵的function,所以人们可以用它来做真正的丑陋但令人惊讶的代码生成任务)。 另一个答案在这里提到了Trove,这在更有限但仍然非常有用的类别中是显而易见的。

真正的一般macros处理器(如M4)只处理文本; 这使得它们function强大,但是它们不能很好地处理编程语言的结构,在这样一个mcaro处理器中编写一个发电机真的很尴尬,它不仅可以产生代码,而且可以优化产生的结果。 我遇到的大多数代码生成器都是“将此string插入此string模板”,因此无法对生成的结果进行任何优化。 如果你想生成任意代码和高性能的引导,你需要一些Turing的能力,但是理解生成的代码的结构,因此它可以很容易地操作(例如,优化)。

这样的工具被称为程序转换系统 。 这样的工具就像编译器一样parsing源文本,然后对其进行分析/转换,以达到预期的效果。 如果您可以将标记放在程序的源文本中(例如,结构化注释或带有它们的语言注释),则可以指导程序转换工具执行什么操作,然后可以使用它来执行这种抽象实例化,代码生成以及/或代码优化。 (一个海报的挂钩到Java编译器的build议是这个想法的一个变种)。 使用通用的puprose转换系统(如DMS Software Reengineering Tookit意味着您可以对任何语言进行此操作。

由于generics,现在可以避免很多这种重复。 在只有types改变的情况下编写相同的代码才是天赐之物。

可悲的是,我认为通用数组还没有得到很好的支持。 至less现在,使用允许您利用generics的容器。 多态性也是减less这种代码重复的有用工具。

要回答你如何处理绝对必须重复的代码的问题…用易于search的注释标记每个实例。 有一些java预处理器,那里添加C风格的macros。 我想我记得netbeans有一个。