2010-02-25 17 views
69

高度重複的代碼通常是一件壞事,並且有一些設計模式可以幫助最大限度地減少這種情況。但是,由於語言本身的限制,有時它是不可避免的。從java.util.Arrays採取下面的例子:在Java中管理高度重複的代碼和文檔

/** 
* 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倍,對每個根的在文檔/方法簽名非常小的變化,但完全相同的方法體,一個陣列類型int[],short[], char[],byte[], boolean[],double[],float[]Object[]

我相信,除非有人反思(這本身就是一個完全不同的主題),這種重複是不可避免的。我知道作爲一個實用程序類,這種高度重複的Java代碼非常不典型,但即使採用最佳做法,重複也會發生!重構不總是可行的,因爲它不總是可能的(明顯的情況是當重複在文檔中時)。

顯然維護這個源代碼是一場噩夢。文檔中的輕微錯字或執行過程中的小錯誤會增加很多次,但重複次數很多。其實,最好的例子發生在涉及這個確切類:

Google Research Blog - Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken (by Joshua Bloch, Software Engineer)

bug是一個令人驚訝的微妙的,在什麼許多人認爲只是一個簡單而直接的算法發生。

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

上面一行出現11次在源代碼中

所以我的問題是:

  • 如何這些類型的重複Java代碼/文件在實踐中如何處理?他們如何開發,維護和測試?
    • 您是否以「原創」開始,儘可能成熟,然後根據需要複製粘貼,並希望您沒有犯錯?
    • 如果您確實在原始文件中犯了錯誤,那麼您只需在任何地方修復它,除非您願意刪除副本並重復整個複製過程?
    • 並且您也將相同的過程應用於測試代碼?
  • Java會受益於某種有限用途的源代碼預處理這種事情嗎?
    • 也許Sun有自己的預處理器來幫助編寫,維護,記錄和測試這類重複的庫代碼?

註釋請求另一個例子,所以我把這個來自谷歌類別:com.google.common.base.Predicates線276-310(AndPredicate)與線312-346(OrPredicate)。

源這兩個類是相同的,除了:

  • AndPredicate VS OrPredicate(每個出現在其類5次)
  • "And(" VS Or("(在各自的toString()方法)
  • #and VS #or(在@see Javadoc註釋)
  • true VS false(在apply; !可以在hashCode()
  • &= VS |=hashCode()
+11

聽起來好像你特別擔心由於處理原始數組的代碼而導致的重複。就個人而言,我只是通過使用泛型集合和自動裝箱來避免這種重複(並鼓勵其他人做同樣的事情),避免數組和基元,除非絕對必要。你有沒有這種重複的例子,*不涉及原始數組? – 2010-02-25 20:08:38

+2

爲好的帖子+1和http://netlib.bell-labs.com/cm/cs/pearls/ – stacker 2010-02-25 20:15:28

+0

由於提供完整的重載,重複性僅僅是一個例子。我在非重載和非原始數組處理場景中也看到了這種重複。 – polygenelubricants 2010-02-25 21:54:08

回答

31

對於那些絕對需要表演,拳擊和拆箱以及聚合收藏的人以及那些絕對不需要的人來說。

同樣的問題發生在哪裏,你需要同樣複雜的工作都爲float和double高性能計算(比如一些Goldberd的What every computer scientist should know about floating-point numbers文所示的方法)。

當處理類似數量的數據時,TroveTIntIntHashMap圍繞Java的HashMap<Integer,Integer>運行圓圈是有原因的。

現在如何編寫Trove集合的源代碼?

通過使用當然:)的源代碼儀表

有更高的性能使用代碼生成器創建重複的源代碼(而不是默認的Java的人要高得多)幾個Java庫。我們都知道「源代碼工具」是邪惡的,並且代碼生成是廢話,但這仍然是人們真正知道自己在做什麼的方式(即像Trove這樣寫東西的人)做到這一點: )

對於什麼是值得我們生成一個包含像大警告的源代碼:

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

你能提供他們使用什麼代碼生成器的更多細節嗎?我不熟悉Trove。 – polygenelubricants 2010-02-26 04:10:38

+1

在Trove常見問題解答中,基本上他們有一個Ant目標,它調用一個修改腳本(如果我沒有記錯的話): http://trove4j.sourceforge.net/html/faq.html (I進入Java高性能計算,我已經看過這種技術使用了好幾次了......我們在這裏使用它,我們有我們自己的Java專有代碼來生成更多的Java代碼:) – SyntaxT3rr0r 2010-02-26 04:14:57

+1

@polygenelubricants:btw Trove是一個美妙的替代品默認的Java API,如果你需要處理基元。對於常規的集合,那麼你會想看看Javolution或Google集合等。從很多角度來看,默認的Java集合真的很糟糕。它適用於簡單的項目,但一旦開始處理重要數據量時,它們就會顯示出極限。 – SyntaxT3rr0r 2010-02-26 04:17:54

1

被重寫出表達的)

  • -1 /* all bits on */ VS 0 /* all bits off */很多這種重複的,現在可以避免由於泛型。在只有類型改變的地方編寫相同的代碼時,它們是天賜之物。

    不幸的是,我認爲通用數組仍然沒有得到很好的支持。至少現在,使用允許您利用泛型的容器。多態性也是減少這種代碼重複的有用工具。

    要回答您關於如何處理絕對必須重複的代碼的問題...用易於搜索的註釋標記每個實例。這裏有一些java預處理器,它們添加了C風格的宏。我想我記得netbeans有一個。

  • +3

    「可悲的是,我認爲通用陣列還沒有得到很好的支持。」 - 我不確定如何通過類型擦除來支持Java中的泛型數組。我認爲這是不可能的。 – polygenelubricants 2010-02-25 20:18:54

    +1

    我見過一個解決方法。投射一個Object數組或使用反射。兩人都不漂亮,但他們顯然工作。 – patros 2010-02-25 20:26:31

    +0

    通常,最好避免在Java中使用數組。與數組相比,ArrayLists提供更多的功能並且通常具有可忽略的性能成本。 – 2010-04-28 03:33:23

    16

    如果您絕對必須複製代碼,請按照您提供的優秀示例進行操作,並將所有代碼分組到一個地方,以便在需要進行更改時輕鬆找到並修復。記錄重複內容,更重要的是,記錄重複的原因,以便每個跟隨你的人都知道這兩者。

    +1

    +1通過記錄重複來增加重複文檔的長度似乎起初可能是一個糟糕的主意,但實際上重複的東西需要修改並且沒有關於重複的文檔要糟糕得多。 – Tanzelax 2010-02-25 20:32:22

    6

    Wikipedia不要重複自己(DRY)或複製是邪惡(DIE)

    在某些情況下,強制執行DRY理念所需要的努力可能比保持的單獨副本的努力更大數據。在其他一些情況下,重複的信息是不可變的,或者保持在足夠的控制下以使DRY不是必需的。

    有可能沒有答案或技術來防止這樣的問題。

    2

    我知道Sun必須爲Java SE庫代碼編寫這樣的文檔,也許其他第三方庫編寫者也要這樣做。

    但是,我認爲在僅用於內部的代碼中將文檔複製並粘貼到像這樣的文件中是非常浪費的。我知道很多人會不同意,因爲這會讓他們內部的JavaDocs看起來不那麼幹淨。然而,權衡是讓他們的代碼更加清潔,在我看來,這更爲重要。

    +0

    +1你可以在Javadoc類中編寫每個重複方法的文檔,並使用一個簡短的方法Javadoc,其中寫着「see class Javadoc」 – 2015-05-27 07:21:44

    2

    Java原始類型會使你陷入困境,特別是當涉及到數組時。如果您具體詢問涉及基本類型的代碼,那麼我會說只是試圖避免它們。如果使用盒裝類型,Object []方法就足夠了。

    一般來說,你需要大量的單元測試,除了採用反射之外,實際上沒有其他的工作要做。就像你說的那樣,這完全是另一個主題,但不要太害怕反思。先編寫DRYest代碼,然後對其進行分析並確定反射性能是否足夠糟糕,以確保寫出並維護額外的代碼。

    2

    您可以使用代碼生成器來使用模板構建代碼的變體。在這種情況下,java源代碼是生成器的產物,實際代碼就是模板。

    +0

    是的,當我說Sun或許有自己的預處理器時,這就是我所指的等。 – polygenelubricants 2010-02-25 21:13:36

    +1

    官方認可的方法是使用註釋和註釋處理器,這樣當你編譯代碼時,javac會調用你的註釋處理器,這會反過來產生源代碼以便由編譯器。這樣做的非官方方式是讓您的註釋處理器在調用時修改內部編譯器數據結構。 我找到的唯一免費的Java源代碼庫是CodeModel。 – msalib 2010-02-26 21:09:52

    +0

    對於較大的片段,這似乎是合理的,但有一點重複可能是兩個邪惡中較小的一個,它們爲構建過程增加了又一層複雜性。 – dsimcha 2010-03-06 19:14:55

    2

    既然聲稱是相似的兩個代碼片段,大多數語言限制了用於構建統一代碼的抽象設施碎片進入巨石。要抽象出當你的語言不能做到這一點時,你必須走出語言: - 最普通的「抽象」機制是一個完整的宏處理器,可以在實例化時將任意計算應用於「宏體」它(認爲Post or string-rewriting系統,這是圖靈功能)。 M4GPM是典型的例子。 C預處理器不是其中之一。

    如果你有這樣一個宏處理器,可以構建一個「抽象」爲宏,並以「抽象」的源文本,以產生實際的源代碼編譯並運行運行宏處理器。

    您也可以使用更多受限版本的創意,通常稱爲「代碼生成器」。這些通常不具有圖靈功能,但在很多情況下,它們工作得不錯。這取決於你的「宏實例化」應該多麼複雜。 (人們迷戀C++模板機制的原因是儘管它很醜陋,但靈活的圖形,所以人們可以用它來做真正難看但令人驚訝的代碼生成任務)。 這裏的另一個答案提到了Trove,它在更有限但仍然非常有用的類別中顯然。

    真正的一般宏處理器(如M4)只處理文本;這使得它們功能強大,但是它們不能很好地處理編程語言的結構,而且在這樣的mcaro處理器中編寫一個發電機真的很尷尬,它不僅可以生成代碼,而且可以優化生成的結果。我遇到的大多數代碼生成器都是「將此字符串插入此字符串模板」,因此無法對生成的結果進行任何優化。 如果你想要生成任意代碼和高性能的引導,你需要一些Turing功能,但理解生成的代碼的結構,以便它可以輕鬆地操縱(例如,優化)它。

    這樣的工具被稱爲Program Transformation System。這樣的工具就像編譯器一樣解析源文本,然後對其進行分析/轉換以達到期望的效果。如果您可以將標記放在程序的源文本中(例如,結構化註釋或帶有它們的語言註釋),則可以指導程序轉換工具執行什麼操作,然後可以使用它來執行此抽象實例化,代碼生成以及/或代碼優化。 (一個海報的掛鉤到Java編譯器的建議是這個想法的變體)。 。使用一般puprose轉化系統(如DMS Software Reengineering Tookit意味着你可以爲幾乎任何語言做到這一點

    4

    即使是衣着光鮮的語言比如Haskell有重複的代碼(see my post on haskell and serialization

    似乎有三種選擇,這一問題:

    1. 使用反射和失去效能
    2. 使用預處理,如模板哈斯克爾或Caml4p等同於您的語言,並與污穢生活
    3. 或者我個人最喜歡使用宏,如果你的語言支持它(方案,並口齒不清)

    我認爲比預處理不同的宏,因爲宏通常是相同的語言,目標就是作爲預處理是不同的語言。

    我認爲Lisp/Scheme宏可以解決許多這些問題。

    +2

    +1使用短語「花式褲子」。 – 2012-01-20 16:13:49