动机
文档很难-每个程序员都知道这是事实。 它也很无聊且重复,结果通常看起来很丑且难以阅读(因为并非所有程序员都是诗人)。 任何书面文档通常在完成之前就已经过时了,这并没有使它变得更好。
因此,编写文档通常是程序员应避免的事情。
最准确的文档是代码本身。
与程序员讨论文档时,您经常会听到一个简单的道理:最准确的文档是代码本身。 这是唯一不会过时,不会对您说谎并且永远完整的事情。
确实,编写良好的代码和测试对记录系统的目的非常有帮助。 特别是,通常遵循干净代码和域驱动设计所建立的实践的系统比不考虑这些概念的系统更容易理解。 其中一些做法是:
· 使用开发人员和客户之间的通用语言来命名的类型,方法和字段
· 以可读性为目标的代码结构
· 精心设计的模块,每个模块都有一个特定的概念或一组概念
测试还可以帮助记录系统的行为方式,特别是使用自然语言或接近自然语言(例如Cucumber)编写的验收测试。
但是,这些(好的)实践只是难题的一部分。 经验表明,坚持这些概念很困难-实际上,当面对时间压力和非功能性要求(例如性能或安全性)的事实时,该概念通常纯粹是学术性的。 而且,客户通常对这些问题非常不了解,并且很少提供对系统达成共识的支持。
因此,与在难以理解的过时的文档工件上投入大量精力相比,仅跳过文档是一个更糟糕的选择。 根据我们的经验,我们认为两者都不可行。
一个新概念:活文档
"活文档"是Gojko Adzik在他的《规范举例》中创造的一个术语。 他的工作(主要是关于BDD –行为驱动开发)的灵感来自法国软件工程师Cyrille Martraire的工作,他试图在他的《活文档》一书中将该方法推向一个新的高度。 生活文档的核心原则是:
· 这是可靠的-所有文档产品始终准确且与实际代码保持同步
· 生产和更新它很省力
· 它既是产品又是所有参与人员之间协作的媒介
· 它对消费者很有见识,并阐明了系统的重要方面
从Cyrille的书中获得的关键见解之一是,通过使用自动生成器遍历代码,可以产生许多有用的文档工件。 与大多数从代码生成文档(包括图表)的现有工具的主要区别在于,工具链的编写方式使开发团队可以适应和增强其成果,从而产生具有真正价值的结果 并且仍然尽可能接近真理的唯一来源(代码)。
活图历史
从源代码生成UML图的想法并不新鲜。 实际上,它在软件开发方面拥有悠久的历史,这一历史常常令人失望。 许多工具提供反向工程功能,例如:
· UML工具套件
· 集成开发环境
· 专用发电机
UML工具套件
有诸如Together,Rational Rose,Enterprise Architect等工具。它们通常在生成UML图方面做得不错,有时甚至提供了完整的Roundtrip-Engineering功能。 但是,它们通常非常昂贵,具有较高的学习曲线,并迫使开发团队完全购买其专有的工作方式。 此外,生成的图很难集成到工具本身之外编写的实际文档中,因此用户经常会发现自己不得不粘贴图像文件,这些文件又很难保持最新状态。
集成开发环境
像Netbeans,Eclipse或IntelliJ这样的IDE都在同一联盟中发挥作用。 它们的逆向工程能力通常不够全面,但价格也较便宜。 可悲的是,尽管如此,IDE的UML模块还拥有昂贵的弟兄们的大多数其他负面影响。 例如,这是IntelliJ IDEA渲染的图:
> Figure 1. Test classes hierarchy generated using IntelliJ IDEA
尽管这是一个干净漂亮的UML图,但是开发人员对其外观没有太大影响。 例如,它不能显示字段关联,也不能包含注释或注释来解释事物。 也不可能从图中排除不需要的部分或引用包外部的类型。
专用生成器
与前面提到的逆向工程工具完全相反,专用的UML图生成器通常很便宜甚至免费,例如UMLGraph JavaDoc doclet。 由于通常可以将它们作为自动化工具链的一部分,因此将其产品集成到文档中与代码紧密相关的部分(例如JavaDocs)通常会容易得多。 尽管有这些积极的方面,但通常仍然很难影响图表的显示方式和方式。
编辑观点:定制输出
让我们回顾一下"生活文件"的概念。 第四个关键概念是任何文档都应该具有洞察力,换句话说,它应该为其消费者提供真正的价值。 为此,应从编辑角度编写文档:
编辑观点基于所考虑文件的意图。 当然,这假设每个文档都有明确的目的,针对特定的受众,具体情况应如此(Cyrille Martraire:生活文档)
因此,对用户有用的图应满足以下要求:
· 它应仅显示与目标受众相关的系统部分
· 应该可以使用与目标受众相关的信息来注释图表
· 它应该始终是最新的
PlantUML类图生成器
PlantUML类图生成器是一种从带注释的Java源代码生成PlantUML类图的工具。 它会生成外观美观,易于使用的图表,这些图表很容易包含在现有文档工件中。 此外,开发人员可能会严重影响输出,以为其用户提供具有实际价值的图表。
什么是PlantUML?
PlantUML是一种根据书面规范生成各种类型的UML图的工具。 它具有用于描述UML图的简单但功能强大的语言,并具有进一步的注释和样式功能,可生成既有用又美观的图。 一些受支持的图表类型是:
· 用例
· 类
· 序列
· 活动
· 零件
PlantUML类图处理器仅创建类图,并可用于记录特定程序包中的类层次结构。 由于PlantUML能够将图导入到其他图中,因此也可以在单个图中显示整个程序包层次结构。 但是,由于其基本概念,可以对这些图进行定制,使其仅显示与特定用例相关的类,因此它们不会为读者带来过多或多余的信息。
设计原则
生成器的设计基于从"生活文档"的思想中衍生出来的有限的一组原则:
相关性:程序员决定源中的哪些元素应显示在生成的图中,因此它们始终与所描述的用例相关
邻近:图表控件是源代码的固有部分,因此它们不太可能会过时
可配置性:图表控件为开发人员提供了对生成内容的大量控制,并可能带有其他信息(例如, 注释
最新性:每次构建都会重新生成图
注释库
目前,注释库包含用于类图的以下注释:
@PlantUmlClass:这是用于类图的主要注释。 当添加到Java类型(接口,类或枚举)时,此类型的表示形式将包含在一个或多个图中。
@PlantUmlField:此注释可以添加到已经用@PlantUmlClass注释的类型的字段中。 它将将该字段呈现为类主体的一部分,并且/或者为该字段的类型添加关联,前提是该类型也是该图的一部分。
@PlantUmlExecutable:应该在已经用@PlantUmlClass注释的类型中显示的方法的注释。 如果类型也是图的一部分,它将把该方法呈现为类主体的一部分。
@PlantUmlNote:此批注可用于将一个或多个UML注释与一种类型相关联,以提供进一步的文本描述。
@PlantUmlDependency:可用于在未通过关联直接连接的类型之间绘制其他依赖关系。
注释处理器
注释处理器是普通的Java注释处理器,可以很容易地将其作为Java编译器参数包含在内-可以使用程序员喜欢的Java IDE的项目配置,也可以作为构建过程的一部分。 注释处理器生成要在结果图中呈现的元素的模型,然后输出消费社区PlantUML源代码。 可以使用以下选项(使用Java编译器的-A参数指定)配置注释处理器:
pumlgen.settings.dir:注释处理器将在其中搜索文件$ {diagramId} _classperties的目录,以进行其他图表设置。 默认值为。 (当前目录)。
pumlgen.out.dir:注释处理器将在其中写入图表文件的目录。 默认为./out。
pumlgen.enabled:尽管处理器存在于类路径中,但该设置可用于在编译时完全禁用处理器。 默认为true。
例子
测试源包含模拟不同类型车辆的人工分类层次结构,并被用作(非常简单的)示例。 请看一下图-它是使用注释处理器自动生成的:
示例1:整个测试类层次结构
> Figure 2. Test classes hierarchy generated using the processor
第一个示例显示包中所有带注释的类的层次结构。 我们发现值得注意的是,与使用常规方法绘制的图相比,此图的简洁性和表达力:
· 它显示了注释处理器设法从Java类型模型识别出的类之间的所有关联:继承(实现和实现)以及字段引用
· 它有一个音符。 在我们看来,注释通常是将一言不发的图转换成有助于读者完全理解软件的东西。
注意:我们认为在注释中呈现JavaDoc注释的内容并不有用。 首先,注释使用HTML标记,而PlantUML使用Creole标记语言。 其次,完整描述复杂类型的JavaDoc注释可能很大。 将简短的(可能是多余的)描述写到批注本身会更有意义。
示例2:仅选定的类
> Figure 3. Grund vehicles
也可以从同一来源渲染多个不同的图。 这是通过@PlantUmlClass批注的diagramIds属性控制的。 这是一个字符串列表,这些字符串定义了将在其中显示类型的图。
DiagramId:图ID是_class.puml之前的文件名的一部分。 因此,默认图表ID是package。 地面车辆图ID是地面车辆。
注释处理器内部
在本节中,我们将研究注释处理器的内部结构。 为了实现自动生成PlantUML类图的目标,我们必须解决以下问题:
· 注释定义
· 如何实现注释处理器
· 如何制作图表
注释定义
这实际上是最简单的部分。 我们从要处理的最上面的注释开始,即@PlantUmlClass。 从那里开始,我们考虑要显示哪些图元素,以及需要哪些其他信息来丰富生成的图:
类型元素
从Java中的类,接口或枚举创建类图中的类型。 可以从Java源代码中收集有关类型本身的所有必需信息,以下各项除外:
· 我们想知道类型将出现在哪些图中,因此我们引入了一个属性diagramIds
· 应当在类型上附加注释。 为此,创建了一个附加注释@PlantUmlNote,它具有定义文本主体的属性主体(可选地带有Creole标记),以及一个属性位置,该属性位置允许将注释相对于其所附加的元素进行定位。
领域与方法
与类型一样,我们不想在类图中任意包含所有字段和方法。 因此,我们至少需要一个其他注释来标记要显示的字段和方法。 考虑到根据元素的类型可能需要提供更多(且完全不同)的信息,因此我们决定实际上对字段使用两个单独的批注PlantUmlField,对方法和构造函数使用PlantUmlExecutable。
依赖关系
最后,我们希望能够定义在源代码中不可见的类之间的关系(通过字段)。 在UML中,这些称为依赖关系,并使用元素之间的虚线来描述。 因此,还有另一个带有可选描述的注释@PlantUmlDependency。
如何在Java中创建注释
清楚地记录了Java批注,而如何做到这一点的知识应成为每个Java程序员工具箱的一部分:
· 注释是一种特殊的界面形式,由关键字@interface标识
· 对于每个注释,需要指定可能的目标列表,以标识允许出现注释的元素(例如,类型,字段,方法等)
· 同样,对于每个注释,程序员都应指定保留策略。 这告诉编译器处理完注释后如何处理。 大多数Java程序员会非常自动地选择RetentionPolicyTIME,因为(1)它是大多数示例中使用的保留策略,并且(2)因为很多批注在运行时使用反射进行处理。 但是,无论是编译器还是运行时都不需要PlantUML类图处理器的注释,因此我们在处理阶段(RetentionPolicy.SOURCE)之后将其丢弃
如何实现注释处理器
所有Java程序员都知道如何使用注释,大多数高级程序员都知道如何在运行时编写和处理它们。 但是,要在编译时处理注释,需要一些附加步骤:
· 实施处理器接口
· 使处理器为Java编译器所知
· 基于Java(注释处理)语言模型实现逻辑
实现处理器接口
· 所有注释处理器必须实现接口Processor或其后代AbstractProcessor
· 注释处理器需要定义他们处理的注释。 但是,此列表不必包括处理器使用的所有注释! 这里只需要处理框架在调用process(..)方法时应该传递的顶级注释-在我们的情况下,仅是@PlantUmlType注释
· 处理器需要定义他们要处理的选项-使用-A参数放置在javac命令行中的选项
· 处理器需要定义他们了解的Java源代码版本
· 当前版本的处理器已通过Java 8进行了测试
添加所需的元信息
除了实现处理器之外,我们还必须添加以下文件:
该文件仅包含处理器的完全合格的类名,从而使它在注释处理环境中注册。
或者,可以使用Google Autoservice自动生成此文件。
资料模型
如果我们不"吃自己的狗粮",我们的小项目看起来会很糟糕。 因此,我们有关PlantUML类图生成器的文档的核心是一个类图,该类图是通过注释完全自动生成的:
> Annotation processor classes
下一步是什么?
某些东西的第一个版本很少是完美的。 还有很多事情可以做:
· 支持其他类图元素
· 支持其他图表类型
支持其他类图元素
尽管注释处理器支持的功能集足以以有效的方式使用它,但是可以呈现到结果类图中的功能仍然很明显。 例如,尚不支持:
· 方法
· 关系说明
· 特殊的关联,例如聚合和合成
· 浮动笔记
· 泛型
· ……
支持其他图表类型
对我们来说,生成类图只是第一步。 更进一步,我们想研究渲染其他图表类型。 类图是一个显而易见的起点,因为它的功能与注释处理环境收集的信息中的信息紧密匹配。
结论
在此博客文章中,我们表明,通过使用Java注释和工具集(尽管仍处于早期阶段)使开发人员对结果产生很大影响,很可能从源代码生成有用的图。 开发人员已经可以生成非常漂亮且完全准确的类图。 我们希望这篇博客文章将成为系列文章中的第一篇,我们将尝试将其功能扩展为一个完整的工具套件,该套件可帮助开发人员编写准确,接近代码且始终保持最新的文档。
可以在Github上找到代码。
Comsysto Reply公司充满激情地开发了复杂且创新的软件解决方案。 我们全面透明地处理项目:您从一个渠道获得一切。