javadoc提取工具_使JavaDoc保持最新状态的工具
javadoc提取工具
在許多項目中,文檔不是最新的。 更改代碼后,很容易忘記更改文檔。 原因是可以理解的。 在代碼中進行更改,然后進行調試,然后希望在測試中進行更改(或者,如果您使用的是更多TDD,則以相反的順序進行更改),然后是新功能版本的喜悅和新版本的喜悅您忘記執行更新文檔的繁瑣任務。
在本文中,我將顯示一個示例,說明如何簡化流程并確保文檔至少是最新的。
工具
我在本文中使用的工具是Java :: Geci,它是一個代碼生成框架。 Java :: Geci的最初設計目標是提供一個框架,在該框架中,編寫代碼生成器將代碼注入到現有的Java源代碼中或生成新的Java源文件非常容易。 因此,名稱為:GEnerate Code Inline或GEnerate Code,Inject。
當我們談論文檔時,代碼生成支持工具會做什么?
在框架的最高級別上,源代碼只是一個文本文件。 像JavaDoc一樣,文檔也是文本。 源目錄結構中的文檔(例如markdown文件)是文本。 復制文本的一部分并將其轉換到其他位置是代碼生成的一種特殊形式。 這正是我們將要做的。
文檔的兩種用途
Java :: Geci有幾種支持文檔的方式。 我將在本文中描述其中之一。
方法是在單元測試中找到一些行,并在可能的轉換后將內容復制到JavaDoc中。 我將使用3.9版之后的apache.commons.lang項目當前主版本的示例進行演示。 盡管有改進的余地,但該項目的文獻記錄非常豐富。 必須以盡可能少的人力來執行此改進。 (不是因為我們懶惰,而是因為人類的工作容易出錯。)
重要的是要了解Java :: Geci不是預處理工具。 該代碼進入了實際的源代碼,并且得到了更新。 Java :: Geci不能消除復制粘貼代碼和文本的冗余。 它對其進行管理,并確保每當導致結果發生更改時,就一遍又一遍地復制和創建代碼。
Java :: Geci的一般工作方式
如果您已經聽說過Java :: Geci,則可以跳過本章。 對于其他人,這里是框架的簡要結構。
Java :: Geci在單元測試運行時生成代碼。 Java :: Geci實際上是作為一個或多個單元測試運行的。 有一個流暢的API可以配置框架。 從本質Geci ,這意味著運行生成器的單元測試是一個斷言語句,該語句創建一個新的Geci對象,調用配置方法,然后調用generate() 。 此方法generate()生成某些內容后返回true。 如果生成的所有代碼與源文件中的代碼完全相同,則返回false 。 如果源代碼中有任何更改,則在其周圍使用Assertion.assertFalse將使測試失敗。 只需再次運行編譯和測試。
框架收集所有配置為要收集的文件,并調用已配置和注冊的代碼生成器。 代碼生成器與代表源文件的抽象Source和Segment對象一起使用,并且源文件中的行可能會被生成的代碼覆蓋。 當所有生成器完成工作后,框架將收集所有段,將其插入Source對象中,如果其中任何一個發生了重大變化,則它將更新文件。
最后,框架返回到啟動它的單元測試代碼。 如果更新了任何源代碼文件,則返回值為true否則為false 。
JavaDoc中的示例
JavaDoc示例將示例自動包含在Apache Commons Lang3庫中的方法org.apache.commons.lang3.ClassUtils.getAbbreviatedName()的文檔中。 當前在master分支中的文檔是:
/** * Gets the abbreviated class name from a {@code String}. * * The string passed in is assumed to be a class name - it is not checked. * * The abbreviation algorithm will shorten the class name, usually without * significant loss of meaning. * The abbreviated class name will always include the complete package hierarchy. * If enough space is available, rightmost sub-packages will be displayed in full * length. * * ** * * * * * <table><caption>Examples</caption> <tbody> <tr> <td>className</td> <td>len</td> <td>return</td> <td>null</td> <td>1</td> <td>""</td> <td>"java.lang.String"</td> <td>5</td> <td>"jlString"</td> <td>"java.lang.String"</td> <td>15</td> <td>"j.lang.String"</td> <td>"java.lang.String"</td> <td>30</td> <td>"java.lang.String"</td> </tr> </tbody> </table> * @param className the className to get the abbreviated name for, may be {@code null} * @param len the desired length of the abbreviated name * @return the abbreviated name or an empty string * @throws IllegalArgumentException if len <= 0 * @since 3.4 */我們要解決的問題是自動維護示例。 要使用Java :: Geci做到這一點,我們必須做三件事:
相依性
Java :: Geci在Maven Central存儲庫中。 當前版本是1.2.0 。 它必須作為測試依賴項添加到項目中。 最終的LANG庫沒有依賴性,就像對JUnit或用于開發的其他任何東西都不具有依賴性。 必須添加兩個顯式依賴項:
com.javax0.geci javageci-docugen 1.2.0 test com.javax0.geci javageci-core 1.2.0 test工件javageci-docugen包含文檔處理生成器。 工件javageci-core包含核心生成器。 該工件還帶來了javageci-engine和javageci-api工件。 引擎本身就是框架,API本身就是API。
單元測試
第二個更改是新文件org.apache.commons.lang3.docugen.UpdateJavaDocTest 。 該文件是一個簡單且非常常規的單元測試:
/* * Licensed to the Apache Software Foundation (ASF) ... */ package org.apache.commons.lang3.docugen; import *; public class UpdateJavaDocTest { @Test void testUpdateJavaDocFromUnitTests() throws Exception { final Geci geci = new Geci(); int i = 0 ; Assertions.assertFalse(geci.source(Source.maven()) .register(SnippetCollector.builder().files( "\\.java$" ).phase(i++).build()) .register(SnippetAppender.builder().files( "\\.java$" ).phase(i++).build()) .register(SnippetRegex.builder().files( "\\.java$" ).phase(i++).build()) .register(SnippetTrim.builder().files( "\\.java$" ).phase(i++).build()) .register(SnippetNumberer.builder().files( "\\.java$" ).phase(i++).build()) .register(SnipetLineSkipper.builder().files( "\\.java$" ).phase(i++).build()) .register(MarkdownCodeInserter.builder().files( "\\.java$" ).phase(i++).build()) .splitHelper( "java" , new MarkdownSegmentSplitHelper()) .comparator((orig, gen) -> !orig.equals(gen)) .generate(), geci.failed()); } }我們在這里可以看到巨大的Assertions.assertFalse調用。 首先,我們創建一個新的Geci對象,然后告訴它源文件在哪里。 在不深入討論細節的情況下,用戶可以通過多種不同方式指定來源。 在此示例中,我們只是說,當我們使用Maven作為構建工具時,源文件通常位于這些文件中。
接下來要做的是注冊不同的生成器。 生成器,尤其是代碼生成器通常獨立運行,因此框架不保證執行順序。 在這種情況下,如我們稍后將看到的,這些生成器在很大程度上取決于彼此的動作。 確保它們以正確的順序執行很重要。 該框架讓我們可以分階段實現這一目標。 詢問生成器,它們需要多少個階段,并且在每個階段中,還詢問是否需要調用它們。 每個生成器對象都是使用構建器模式創建的,在此模式中,每個生成器對象都被告知應運行哪個階段。 當生成器配置為在階段i運行(調用.phase(i) )時,它將告訴框架它至少需要i階段,而對于階段1..i-1 ,它將處于非活動狀態。 這樣,配置可確保生成器按以下順序運行:
從技術上講,所有這些都是生成器,但它們不會“生成”代碼。 SnippetCollector從源文件中收集片段。 當某些示例代碼需要程序不同部分的文本時, SnippetAppender可以將多個代碼片段附加在一起。 SnippetRegex可以在使用正則表達式和replaceAll功能之前修改代碼段(我們將在此示例中看到)。 SnippetTrim可以從行的開頭刪除前導制表符和空格。 當對代碼進行深列表時,這一點很重要。 在這種情況下,只需將摘錄片段導入文檔中,就可以輕松地將實際字符從右側的可打印區域中移出。 如果我們有一些代碼在文檔中引用了某些行,則SnippetNumberer可以對代碼段行進行編號。 SnipetLineSkipper可以從代碼中跳過某些行。 例如,您可以對其進行配置,以便跳過導入語句。
最后,可以更改源代碼的真正“生成器”是MarkdownCodeInserter 。 創建它是為了將片段插入以Markdown格式的文件中,但是當需要將文本插入JavaDoc部件中時,它對于Java源文件也同樣有效。
最后兩個配置調用告訴框架使用MarkdownSegmentSplitHelper并使用簡單的equals比較原始行和代碼生成后創建的行。 SegmentSplitHelper對象可幫助框架在源代碼中查找段。 在Java文件中,這些段通常是默認情況下的
和
線。 這有助于將手冊和生成的代碼分開。 在所有高級編輯器中,該編輯器折疊也是可折疊的,因此您可以專注于手動創建的代碼。
但是,在這種情況下,我們將插入到JavaDoc注釋內的段中。 這些JavaDoc注釋可能包含一些標記,但也友好HTML,因此它們比Java更像Markdown。 尤其是,它們可能包含不會出現在輸出文檔中的XML注釋。 在這種情況下,由MarkdownSegmentSplitHelper對象定義的片段開始于
<!-- snip snipName parameters ... -->和
<!-- end snip -->線。
必須出于非常特定的原因指定比較器。 該框架具有兩個內置的比較器。 一個是默認的比較器,該比較器逐行比較每個字符。 它用于除Java外的所有文件類型。 在Java的情況下,使用了一個特殊的比較器,該比較器可以識別何時僅更改注釋或僅重新格式化代碼。 在這種情況下,我們將更改Java文件中注釋的內容,因此我們需要告訴框架使用簡單的比較器,否則它將不會影響我們進行任何更新。 (花了30分鐘的時間調試為什么不先更新文件。)
最后一個調用是generate() ,它將啟動整個過程。
標記代碼
記錄此方法的單元測試代碼是org.apache.commons.lang3.ClassUtilsTest.test_getAbbreviatedName_Class() 。 外觀應如下所示:
@Test public void test_getAbbreviatedName_Class() { // snippet test_getAbbreviatedName_Class assertEquals( "" , ClassUtils.getAbbreviatedName((Class<?>) null , 1 )); assertEquals( "jlString" , ClassUtils.getAbbreviatedName(String. class , 1 )); assertEquals( "jlString" , ClassUtils.getAbbreviatedName(String. class , 5 )); assertEquals( "j.lang.String" , ClassUtils.getAbbreviatedName(String. class , 13 )); assertEquals( "j.lang.String" , ClassUtils.getAbbreviatedName(String. class , 15 )); assertEquals( "java.lang.String" , ClassUtils.getAbbreviatedName(String. class , 20 )); // end snippet }我不會在此顯示原始內容,因為唯一的區別是插入了兩個snippet ...和end snippet行。 這些是SnippetCollector收集它們之間的線并將其存儲在“ snippet store”(沒有什么神秘的東西,實際上是一個很大的哈希圖)中的觸發器。
定義一個細分
真正有趣的部分是如何修改JavaDoc。 在本文開頭,我已經介紹了今天的完整代碼。 新版本是:
/** * Gets the abbreviated class name from a {@code String}. * * The string passed in is assumed to be a class name - it is not checked. * * The abbreviation algorithm will shorten the class name, usually without * significant loss of meaning. * The abbreviated class name will always include the complete package hierarchy. * If enough space is available, rightmost sub-packages will be displayed in full * length. * * ** you can write manually anything here, the code generator will update it when you start it up * <table><caption>Examples</caption> <tbody> <tr> <td>className</td> <td>len</td> <td>return</td> <!-- snip test_getAbbreviatedName_Class regex=" replace='/~s*assertEquals~((.*?)~s*,~s*ClassUtils~.getAbbreviatedName~((.*?)~s*,~s*(~d+)~)~);/* </tr><tr> <td>{@code $2}</td> <td>$3</td> <td>{@code $1}</td> </tr> /' escape='~'" --><!-- end snip --> </tbody> </table> * @param className the className to get the abbreviated name for, may be {@code null} * @param len the desired length of the abbreviated name * @return the abbreviated name or an empty string * @throws IllegalArgumentException if len <= 0 * @since 3.4 */重要的部分是15…20行的位置。 (您會看到,有時對代碼段行進行編號很重要。)第15行表示段開始。 段的名稱為test_getAbbreviatedName_Class并且在沒有其他定義的情況下,該段也將用作要插入的代碼段的名稱。 但是,在插入代碼段之前,它會由SnippetRegex生成器進行轉換。 它將替換正則表達式的每個匹配項
\s*assertEquals\((.*?)\s*,\s*ClassUtils\.getAbbreviatedName\((.*?)\s*,\s*(\d+)\)\);與字符串
* {@code $2}$3{@code $1}由于這些正則表達式位于字符串內部,因此也需要\\\\而不是單個\ 。 那會使我們的正則表達式看起來很糟糕。 因此,可以將生成器SnippetRegex配置為使用我們選擇的其他一些字符,這種字符不太容易出現籬笆現象。 在此示例中,我們使用波浪號字符,并且通常可以使用。 當我們運行它時,最終結果是:
<!-- snip test_getAbbreviatedName_Class regex=" replace='/~s*assertEquals~((.*?)~s*,~s*ClassUtils~.getAbbreviatedName~((.*?)~s*,~s*(~d+)~)~);/* < tr > <td>{@code $2}< /td > <td>$3< /td > <td>{@code $1}< /td > < /tr > / ' escape=' ~'" --> * {@code (Class) null}1{@code "" } * {@code String.class}1{@code "jlString" } * {@code String.class}5{@code "jlString" } * {@code String.class}13{@code "j.lang.String" } * {@code String.class}15{@code "j.lang.String" } * {@code String.class}20{@code "java.lang.String" } <!-- end snip -->摘要/外賣
文檔更新可以自動化。 首先,這有點麻煩。 開發人員不必復制和重新格式化文本,而是必須設置新的單元測試,標記代碼段,標記段,使用正則表達式構造轉換。 但是,完成后,任何更新都是自動的。 單元測試更改后,您將無法忘記更新文檔。
這與創建單元測試時遵循的方法相同。 首先,創建單元測試而不是只是臨時地調試和運行代碼,然后查看調試器,以查看它是否確實如我們預期的那樣工作,這有點麻煩。 但是,完成后會自動檢查所有更新。 當影響舊代碼的代碼發生變化時,就不會忘記檢查舊功能。
我認為文檔維護應與測試一樣自動化。 通常,任何可以在軟件開發中自動化的東西都必須自動化,以節省工作量并減少錯誤。
翻譯自: https://www.javacodegeeks.com/2019/09/tools-keep-javadoc-date.html
javadoc提取工具
總結
以上是生活随笔為你收集整理的javadoc提取工具_使JavaDoc保持最新状态的工具的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring boot简介_Spring
- 下一篇: linux系统有哪些版本(linux 系