使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

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

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

相关文章

RS485转换器常见问题总结

通讯转换器主要应用于支持RS485通讯总线结构的工业控制自动化系统&#xff0c;其中包括一卡通、门禁系统、消费系统、考勤系统、、监控、楼宇对讲、停车场系统、自助银行系统、公共汽车收费系统、公路收费站系统等等。但是&#xff0c;我们在使用485转换器的过程中难免会遇到各…

hd-sdi转hdmi转换器产品规格参数及接口定义详解

hd-sdi转hdmi转换器可方便的将HD-SDI 信号转为HDMI 信号&#xff0c;同时会将SDI所带音频信号分离转换后嵌入到HDMI 信号中&#xff0c;以实现声音及图像同步传输。专为电视台、平安城市、公安交警、高速公路、应急指挥中心的高清视频显示等环境提供项目解决方案。接下来就由飞…

singleton设计模式_Java Singleton设计模式

singleton设计模式它是Java中最简单的设计模式之一。 如果有人问我哪种设计模式好&#xff0c;那么我会很自豪地说Singleton。 但是&#xff0c;当他们深入询问单身人士的概念时&#xff0c;我感到很困惑。 真的单身是那么困难吗&#xff1f; 确实不是&#xff0c;但是它有…

HD-SDI转HDMI转换器工作原理及功能介绍

高清HD-SDI转HDMI转换器是一款专用于单路高清数字分量串行接口(HD-SDI和3G-SDI)信号转换为HDMI信号的设备。该设备集成SDI接收器及HDMI调制器&#xff0c;可方便的将HD-SDI信号转为HDMI信号&#xff0c;同时会将SDI所带音频信号分离转换后嵌入到HDMI信号中&#xff0c;以实现声…

注释处理和JPMS

TLDR&#xff1b; 代替annotation.getClass().getMethod("value")调用annotation.annotationType().getMethod("value") 。 所有Java开发人员都听说过注释。 自Java 1.5&#xff08;或者您坚持认为只有1.6&#xff09;以来&#xff0c;我们便有了注释。 根…

HD-SDI转HDMI转换器怎么连接?hd-sdi转hdmi转换器常见故障有哪些?

高清HD-SDI转HDMI转换器是一款专用于单路高清数字分量串行接口(HD-SDI和3G-SDI)信号转换为HDMI信号的设备。hd-sdi转hdmi转换器将广播级输出图像使用的SDI信号转换成HDMI影像信号&#xff0c;中继广播与消费型产品的链接器&#xff0c;让SDI信号格式不用经剪辑软件再一次的转换…

sdi转hdmi转换器应用领域及规格参数详解

SDI转HDMI转换器 是一款专用于单路高清数字分量串行接口(HD-SDI&#xff0c;3G-SDI和SD-SDI)信号转换为HDMI 信号的设备。该设备集成SDI 接收器及HDMI 调制器&#xff0c;可方便的将SDI 信号转为HDMI 信号&#xff0c;同时会将SDI所带音频信号分离转换后嵌入到HDMI 信号中&…

Cassandra中的数据建模

在关系数据模型中&#xff0c;我们为域中的每个对象建模关系/表。 对于Cassandra&#xff0c;情况并非如此。本文将详细介绍在Cassandra中进行数据建模时需要考虑的所有方面。 以下是Cassandra数据建模的粗略概述。 从上图可以看出&#xff0c; 概念数据建模和应用程序查询是构…

485串口光纤转换器产品介绍

光纤转换器是RS-232/422/485串行数据通过光纤的远距离传输&#xff0c;可以完成串口到光纤的转换&#xff0c;并且可以延长串行通信信号的传输距离。接下来我们就跟随飞畅科技的小编一起来详细了解下光纤转换器的转换类别有哪些&#xff1f;一起来看看吧&#xff01; 光纤转换器…

什么是485转光纤?485光纤转换器功能特点及技术参数详解

RS485转光纤转换器实现一路RS-485在光纤上的透明传输&#xff0c;由于采用光纤通信&#xff0c;解决了电磁干扰、地环干扰和雷电破坏的难题&#xff0c;大大提高了数据通讯的可靠性、安全性和保密性&#xff0c;可广泛用于各种工业控制、过程控制、交通控制和分布式数据采集等场…

可视对讲网络协议转换器怎么使用,协议转换器使用方法详细介绍

对于可视对讲网络协议转换器使用方法这块&#xff0c;可能很多人对此都不是很理解&#xff0c;在这里&#xff0c;我们首先来了解下可视对讲的原理&#xff0c;以便大家更好的了解接下来的内容。今天&#xff0c;杭州飞畅的小编就来为大家详细介绍下可视对讲网络协议转换器的使…

什么是协议转换器?协议转换器的定义

现如今&#xff0c;随着各种类型的转换器的出现&#xff0c;在很大程度上扩大了各类仪表的使用范围&#xff0c;使的自动控制系统具有更多的灵活性和更广的适应性&#xff0c;在这方面&#xff0c;协议转换器的应用范围也是非常的广泛。今天&#xff0c;杭州飞畅的小编就来为大…

什么是网络协议转换器?

通过之前的介绍&#xff0c;我们了解到协议转换是一种映射&#xff0c;就是把某一协议的收发信息(或事件)序列映射为另一协议的收发信息序列。那么&#xff0c;什么是网络协议转换器呢&#xff1f;接下来就跟随飞畅科技的小编一起来看看吧&#xff01; 网络协议转换器是什么&a…

midlet_如何在J2ME中创建MIDlet

midlet总览 Java移动应用程序称为J2ME。 通常&#xff0c;当我们在移动技术领域工作时&#xff0c;我们必须考虑J2ME应用程序。 通过这种方式&#xff0c;我们可以开发我们的移动应用程序&#xff0c;也可以通过jad或jar文件将其安装在我们的设备中。 近年来&#xff0c;手机开…

网关到底是什么?协议转换器是网关吗?

网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连&#xff0c;是最复杂的网络互连设备&#xff0c;仅用于两个高层协议不同的网络互连。网关既可以用于广域网互连&#xff0c;也可以用于局域网互连。那么&#xff0c;网关到底是什么呢&#xff1f;接下来…

穿越JUnit流

关于JUnit 5迁移的好处之一是&#xff0c;您可以在老式模式下运行JUnit 4测试&#xff0c;并且所有内容仍然兼容。 不利的一面是&#xff0c;某些注释和方法在JUnit 4和JUnit 5中具有相同的名称&#xff0c;并且当两组库依赖项都可用时&#xff0c;很容易导入错误的内容并产生不…

什么是无线路由器网络协议?

上一篇我们介绍了什么是网络协议转换器&#xff0c;相信看过的朋友对此都有了一定的认知&#xff0c;可能有些朋友在使用协议转换器的时候用的是无线路由器网络&#xff0c;那么&#xff0c;什么是无线路由器网络协议呢&#xff1f;接下来飞畅科技的小编就来为大家详细介绍下无…

协议转换器安全使用须知

协议转换器能使处于通信网上采用不同高层协议的主机仍然互相合作&#xff0c;完成各种分布式应用。可以将IEEE802.3协议的以太网或V.35数据接口同标准G.703协议的2M接口之间进行相互转换。也可以在232/485/422串口和E1、CAN接口及2M接口进行转换。那么&#xff0c;我们在使用协…

JDBC –模拟序列

也许我们每个人在程序员的生命中至少遇到过一次这个问题- 如何模拟数据库序列&#xff1f; 在下面&#xff0c;您可以找到我对这个问题的解决方案的变形。 假设我们有一个接口定义了所需的API&#xff0c;用于返回整数序列&#xff1a; public interface Sequences { int next…

怎么安装协议转换器?协议转换器安装方法解析

协议转换器是有很多种的&#xff0c;多数基本上是个2层设备&#xff0c;经常碰见的一种RAD的协议转换器是将2M的E1线路转换成V.35的数据线路连接路由器的设备&#xff0c;当然也有&#xff0c;2M转双绞线以太的&#xff0c;借助2M通信线路可以实现局域网范围的远程接入和扩大。…