在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 > toIndex</tt> * @throws ArrayIndexOutOfBoundsException if <tt>fromIndex < 0</tt> or * <tt>toIndex > 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
vsOrPredicate
(每class出现5次) -
"And("
vsOr("
(在各自的toString()
方法中) -
#and
和vs#or
(在@见Javadoc评论) -
true
与false
(inapply
;!可以重写出expression式) -
-1 /* all bits on */
vs0 /* all bits off */
inhashCode()
-
&=
vs|=
inhashCode()
对于那些绝对需要表演的人来说,拳击,拆箱,聚集的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:
- 使用reflection和失去performance
- 使用模板Haskell或Caml4p等同于您的语言的预处理,并与糟糕生活
- 或者如果你的语言支持,我个人最喜欢使用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有一个。