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将使测试失败。 只需再次运行编译和测试。

框架收集所有配置为要收集的文件,并调用已配置和注册的代码生成器。 代码生成器与代表源文件的抽象SourceSegment对象一起使用,并且源文件中的行可能会被生成的代码覆盖。 当所有生成器完成工作后,框架将收集所有段,将其插入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做到这一点,我们必须做三件事:

  1. 将Java :: Geci添加为项目的依赖项
  2. 创建一个运行框架的单元测试
  3. 在单元测试中标记零件,这是信息的来源
  4. 用Java :: Geci`Segment`替换手动复制的示例文本,以便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-enginejavageci-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 ,它将处于非活动状态。 这样,配置可确保生成器按以下顺序运行:

  1. 片段收集器
  2. SnippetAppender
  3. 片段正则表达式
  4. 片段修剪
  5. 片段编号器
  6. SnipetLine船长
  7. MarkdownCodeInserter

从技术上讲,所有这些都是生成器,但它们不会“生成”代码。 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提取工具

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/332034.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

栈应用(中缀表达式转后缀表达式并计算后缀表达式的值)

【0】README 0.1&#xff09; 本文旨在总结 中缀表达式转后缀表达式并计算后缀表达式的值 的步骤&#xff0c;并给出源代码实现&#xff1b; 0.2&#xff09; 本文中涉及到的源代码均为原创&#xff0c;是对中缀转后缀和计算后缀的简单实现&#xff0c;&#xff08;旨在理清它…

用户/账户/账号的理解

文章目录用户账户账号关系用户 用户概念的理解&#xff1a; 用户就是使用者&#xff0c;可以是个人用户&#xff0c;也可以是机构用户。 账户 账户概念的理解&#xff1a; 账户&#xff0c;所谓“账”&#xff0c;就是系统根据“账”来存储和管理数据&#xff0c;类似记账…

azure 部署java_jClarity:在Azure上升级到Java

azure 部署java在互联世界公共基础设施的新时代&#xff0c;最大和最重要的两个方面是Java和OpenJDK的诞生和兴起。 因此&#xff0c;许多公司将时间和资源投入到构建最先进的技术上&#xff0c;以确保整个行业在未来几年内在AdoptOpenJDK上拥有丰富的质量&#xff0c;而且免费…

黑苹果sd卡认不出来_天生一对:新入苹果M1笔记本,DOCKCASE拓展坞弥补缺憾

2010年刚上大学那会&#xff0c;入手了人生第一台笔记本电脑&#xff0c;两边密密麻麻的各种接口&#xff0c;也没感觉到接口多少的价值&#xff1b;2016年年中入手了人生第一台苹果笔记本&#xff0c;第一次觉得电脑接口不够用&#xff1b;如今四年过去了电脑也到了更新换代的…

利用树的先序和后序遍历打印os中的目录树

【0】README0.1&#xff09;本代码均为原创&#xff0c;旨在将树的遍历应用一下下以加深印象而已&#xff1b;&#xff08;回答了学习树的遍历到底有什么用的问题&#xff1f;&#xff09;你对比下linux 中的文件树 和我的打印结果就明理了&#xff1b;0.2&#xff09;我们采用…

Hibernate常用API

文章目录删除指定的记录新增记录更新记录清空缓存将实体对象从缓存中清除将缓存中更新的数据同步到数据库把数据库中的数据刷到缓存中查询多个对象&#xff08;也就是查询多条记录&#xff09;查询指定ID的对象&#xff08;查询指定ID值的记录&#xff09;参考删除指定的记录 U…

solid设计原则_SOLID设计原则

solid设计原则介绍&#xff1a; Robert C. Martin定义了五项面向对象的设计原则&#xff1a; 小号英格尔-责任原则 笔封闭原则 大号 iskov的替换原则 我覆盖整个院落分离原则&#xff0c;并 d ependency倒置原则 这些一起被普遍称为SOLID原则。 在设计面向对象的系统时&a…

nosql简答什么是最终一致性_NoSql的三大基石:CAP理论BASE最终一致性

关系型数据库的局限NoSql出现在关系型数据库之后&#xff0c;主要是为了解决关系型数据库的短板&#xff0c;我们先来看看随着软件行业的发展&#xff0c;关系型数据库面临了哪些挑战&#xff1a;1、高并发一个最典型的就是电商网站&#xff0c;例如双11&#xff0c;几亿大军的…

二叉树的先中后序遍历

【0】README 0.1&#xff09;本文旨在理清二叉树的先中后序遍历&#xff0c; 以及如何建立二叉树等相关内容&#xff1b; 0.2&#xff09;本文涉及代码均为原创&#xff1b; 0.3&#xff09;本文中遍历后的打印结果&#xff0c;朋友您可以直接写出二叉树的节点构造出来&…

表达式树

【0】README 0.1&#xff09;本文旨在总结出表达式树的构建步骤&#xff0c; 其中还涉及到中缀转后缀表达式&#xff0c;以及如何计算 表达式树中的值&#xff1b; 0.2&#xff09;本文源代码均为原创&#xff1b; 0.3&#xff09; 其实&#xff0c; 实现一个简单的计算器&a…

Date/Timestamp/String/LocalDate/LocalDateTime

文章目录String 转成 DateDate 转成 StringString 转成 Timestamp获取系统当前的毫秒数获取系统当前的日期时间毫秒数转成 Timestamp毫秒数转成 DateTimestamp 转成 StringDate 转成 TimestampTimestamp 转成 Datejava.util.Date 转成 java.sql.Date将带T的日期时间转成正常的日…

python可以用来写什么工具_python写工具

谷歌开源 Python Fire&#xff1a;可自动生成命令行接口今天我们很高兴地宣布 Python Fire 开源。Python Fire 可从任何 Python 代码生成命令行接口(command line interfaces (CLIs))&#xff0c;简单地调用任意 Python 程序中的 Fire 函数以将那个程序自动地转化为 CLI。该库可…

java原始类型和引用类型_Java中的8种原始类型

java原始类型和引用类型几年前&#xff0c;当我开始编辑Java Basics系列时&#xff0c;我认为将一些非常详细的信息拉到自己的帖子中是很有意义的。 这样&#xff0c;初学者的内容就更容易消化了。 首先&#xff0c;我将介绍有关Java的8种原始类型的所有信息。 Java基本类型 正…

androidtabhost缓存_FragmentTabHost布局的使用及优化方式

欢迎Follow我的GitHub, 关注我的简书. 其余参考Android目录.TabHostAndroidFragmentTabHost作为Android4.0版本的控件, 已经被项目广泛使用, 5.0版本又推出TabLayoutViewPager显示多页. 我来讲解如何使用FragmentTabHost.本文源码的GitHub下载地址主要包括:(1) 自定义Tab的图片…

二叉查找树

【0】README 0.1&#xff09;本文的重点在于介绍 二叉查找树的概念&#xff0c;以及写出 二叉查找树的操作例程的源代码&#xff0c; 其中当属delete 操作的源代码最不容易实现&#xff1b; 0.2&#xff09;本文源代码均为原创&#xff0c; 当然 代码中的idea 是借鉴人家的&a…

常用的命名规范/命名规则

文章目录骆驼式命名法&#xff08;CamelCase&#xff09;帕斯卡命名法&#xff08;PascalCase&#xff09;串式命名法&#xff08;KebabCase&#xff09;下划线命名法&#xff08;UnderScoreCase&#xff09;骆驼式命名法&#xff08;CamelCase&#xff09; 也叫小驼峰式命名法…

spring order_Spring @Order批注

spring order介绍&#xff1a; Spring Order注释是在Spring 2.0中首次引入的。 然后&#xff0c;它仅用于定义AspectJ建议中的顺序。 在Spring 4.0的后面&#xff0c;对该注释的实现进行了进一步改进。 从那时起&#xff0c;它还支持对Java数组或List之类的集合中的Spring组件或…

AVL树

【0】README 0.1&#xff09;本文给出了平衡二叉树&#xff08;AVL树&#xff09;的插入例程涉及到的单旋转双旋转的概念&#xff0c;并给出了代码实现&#xff1b; 0.2&#xff09;本文源代码均为原创&#xff0c; 当然相关idea 还是借鉴人家的&#xff1b;&#xff08;真心…

spring 注释_Spring@懒惰注释

spring 注释介绍&#xff1a; 默认情况下&#xff0c; Spring框架在应用程序启动时加载并热切初始化所有bean。 在我们的应用程序中&#xff0c;我们可能有一些非常消耗资源的bean。 我们宁愿根据需要加载此类bean。 我们可以使用Spring Lazy批注实现此目的 。 在本教程中&…

管理系统的账户设计(涉及注册/登录逻辑)

文章目录方案一方案二方案三方案一 类似华为云IAM&#xff08;Identity and Access Management 身份和访问管理&#xff09;用户&#xff0c;阿里云的 RAM&#xff08;Resource Access Management 资源访问管理&#xff09;用户 机构有独立的账户&#xff08;主账户&#xff…