g代码生成器 源代码

在本文中,我将讨论软件开发的不同阶段,在这些阶段中可以以编程方式生成源代码,并且我将比较不同的方法。 我还将描述在特定阶段生成代码的特定工具的体系结构和想法(尤里卡时刻的种类)。

手动地

这是标题中设置的问题的答案。 如果有可能,您必须手动生成代码。 一年前,我已经写了一篇有关代码生成的文章 ,但我没有改变主意。

除非确实需要,否则不应生成代码。

奇怪的说法,尤其是当我推广完全针对Java代码生成的FOSS工具时。 我知道并且仍然声明​​,您必须手动编写所有代码。 不幸的是,或者出于我的小工具的考虑,在很多情况下无法选择手动代码生成,或者至少自动代码生成似乎是一个更好的选择。

为什么要手动生成

我已经在引用的文章中讨论了它,但是在这里我们再次进行。 如果最好的选择是生成源代码,那么系统中就会出现错误或至少不是最优的。

  • 创建代码的开发人员低于标准水平,
  • 编程语言是低于标准的语言,或者
  • 在环境方面,一些框架是低于标准的。

不要感到冒犯。 当我谈论“低于标准的开发人员”时,我并不是说你。 最后,您远高于一般开发人员,但并非最不重要,这是因为您开放并且对新事物感兴趣,这是您正在阅读本文所证明的。 但是,在编写代码时,还应考虑普通的开发人员Joe或Jane,他们将来会维护您的程序。 而且,普通开发人员有一个非常特殊的功能:他们不好。 它们也不错,但是,顾名思义,它们是平均水平。

标配开发商的传奇

您可能会发生几年前发生在我身上的事情。 它像下面这样。

解决了一个问题,我创建了一个微型框架。 并不是像Spring或Hibernate这样的框架,因为单个开发人员无法开发类似的框架。 (尽管其中有些人甚至在专业环境中尝试也没有停止,这是矛盾的,因为它不是专业的。)您需要一个团队。 我创建的是一个单一的类,该类正在做一些反射“魔术”,将对象转换为地图并返回。 在此之前,我们需要所有需要此功能的类中的toMap()fromMap()方法。 它们是手动创建和维护的。

幸运的是我并不孤单。 我有一个团队。 他们告诉我toMap()编写的代码,并继续手动创建toMap()fromMap() 。 原因是必须由跟随我们的开发人员来维护代码。 而且我们不知道它们,因为甚至没有选择它们。 他们可能仍在大学学习,甚至未出生。 我们知道一件事:他们将是普通的开发人员,而我创建的代码比普通的技能还需要一点点时间。 另一方面, toMap()手工维护的toMap()fromMap()方法易于出错,但所需的技能并不比一般技能高。 但这只是成本问题,需要在质量检查方面投入更多的资金,并且比雇用高级开发人员要少得多。

您可以想象我的矛盾情绪,因为我辉煌的代码被拒绝了,但垫子上赞美了我的自我。 我必须说,他们是对的。

低于标准框架

好吧,从这个意义上讲,许多框架都低于标准水平。 “ sub-par”一词也许并不是最好的。 例如,您从WSDL文件生成Java代码。 为什么框架会生成源代码而不是Java字节码? 有充分的理由。

生成字节码很复杂,需要特殊知识。 它具有与此相关的成本。 它需要一些字节代码生成库,例如Byte Buddy,对于使用该代码的程序员而言,调试起来更加困难,并且与JVM版本有关。 如果代码是作为Java源代码生成的,即使它是针对Java的更高版本的,而项目使用的是滞后版本,则机会更好,如果这是Java源代码,则项目可以某种方式降级生成的代码而不是字节码。

不及格语言

显然,在这种情况下,我们不是在谈论Java,因为Java是世界上最好的,没有更好的东西。 还是? 如果有人声称只使用任何一种编程语言,那么该语言就是完美的,请忽略该人。 每种语言都有优点和缺点。 Java也不例外。 如果您考虑到该语言是20多年前设计的,并且根据开发哲学,它对后向兼容性的要求非常严格,则仅意味着应该存在某些其他语言更好的领域。

考虑一下在Object类中定义的equals()hashCode()方法,这些方法可以在任何类中重写。 没有什么发明能覆盖所有这些。 重写的实现是相当标准的。 实际上,它们是如此的标准,以至于集成开发环境均支持为其生成代码。 我们为什么要为其生成代码? 为什么它们不以某种声明的方式成为语言的一部分? 这些问题应该有很好的答案,因为在语言中实现这样的事情实际上并不是什么大问题,但事实并非如此。 必须有一个很好的理由,我不是最值得写的人。

作为本部分的摘要:如果您不能依赖手动生成的代码,则可以确保某些东西不合格。 这不是可耻的。 这就是我们一般的职业。 大自然就是这样。 没有理想的解决方案,我们必须妥协。

接下来的问题是

什么时候生成代码?

代码生成主要可能发生:

  • (BC)编译前
  • (DC)编译期间
  • (DT)在测试阶段
  • (DCL)在类加载期间
  • (DRT)在运行时

在下文中,我们将讨论这些不同的情况。

(BC)编译前

常规阶段是在编译之前。 在这种情况下,代码生成器将读取某些配置或可能的源代码,并通常将Java代码生成到与手册源代码分开的特定目录中。

在这种情况下,生成的源代码不是进入版本控制系统的代码的一部分。 代码维护必须处理代码生成,并且几乎不能选择从流程中省略代码生成器并继续手动维护代码。

代码生成器无法轻松访问Java代码结构。 如果生成的代码必须以任何方式使用,扩展或补充已经存在的手动代码,则它必须分析Java源代码。 可以逐行或使用某些解析器来完成。 无论哪种方式,这都是一项任务,稍后将由Java编译器再次完成,并且还有很小的机会Java编译器和用于解析代码生成器代码的工具可能不是100%兼容的。

(DC)编译期间

Java使创建由编译器调用的所谓注释处理器成为可能。 它们可以在编译阶段生成代码,并且编译器将编译生成的类。 这样,代码生成便成为了编译阶段的一部分。

在此阶段运行的代码生成器无法访问已编译的代码,但是它们可以通过Java编译器为注释处理器提供的API访问已编译的结构。

可以生成新的类,但不能修改现有的源代码。

(DT)在测试阶段

首先,它似乎有点偏离。 为什么有人要在测试阶段执行代码生成? 但是,我在这里尝试“出售”的FOSS确实做到了这一点,我将详细介绍此阶段代码生成的可能性,优点和缺点。

(DCL)在类加载期间

在类加载期间也可以修改代码。 执行此操作的程序称为Java代理。 它们不是真正的代码生成器。 它们在字节码级别上工作,并修改已编译的代码。

(DRT)在运行时

一些代码生成器在运行时工作。 其中许多应用程序直接生成Java字节码,并将代码加载到正在运行的应用程序中。 也可以生成Java源代码,编译代码并将结果字节加载到JVM中。

在测试阶段生成代码

这是Java :: Geci(Java GEnerate代码内联)生成代码的时间和地点。 为了帮助您理解在单元测试期间执行代码生成的怪异想法(当为时已晚:代码已经编译)让我告诉您另一个故事。 这个故事是虚构的,它从未发生过,但并没有使解释的能力相形见.。

我们有一个包含几个数据类的代码,每个数据类都有几个字段。 我们必须为每个这些类创建equals()hashCode()方法。 最终,这意味着代码冗余。 当类更改时,添加或删除了一个字段,则方法也必须更改。 删除字段不是问题:编译器不会编译引用不存在的字段的equal()hashCode()方法。 另一方面,编译器不介意这种方法不引用新的现有字段。

我们时不时地忘记更新这些方法,我们试图发明越来越多的复杂且更好的方法来抵消容易出错的人类编码。 最奇怪的想法是创建字段名称的MD5值,并将其作为注释插入到equals()hashCode()方法中。 如果字段发生更改,则测试可以检查源代码中的值是否与根据字段名称计算出的值不同,然后发出错误消息:单元测试失败。 我们从未实施过。

甚至更怪异的想法,也并不是那么怪异,最终导致了Java :: Geci实际上是在测试期间从通过反射获得的可用字段中创建预期的equals()hashCode()方法测试,并将其与已经在代码中了。 如果它们不匹配,则必须重新生成它们。 但是,此时的代码已经重新生成。 唯一的问题是它在JVM的内存中,而不在包含源代码的文件中。 为什么只发出错误信号并告诉程序员重新生成代码? 测试为什么不回写更改? 毕竟,我们应该告诉计算机应该怎么做,而不是相反!

这就是导致Java :: Geci的顿悟。

Java :: Geci体系结构

Java :: Geci在编译,部署和执行生命周期的中间生成代码。 在构建阶段运行单元测试时,将启动Java :: Geci。

这意味着手册和先前生成的代码已经被编译,并且可以通过反射用于代码生成器。

在测试阶段执行代码生成还有另一个优势。 以后运行的任何代码生成都应仅生成与手动代码功能正交的代码。 这是什么意思? 从产生的代码不应以任何方式修改或干扰单元测试可能发现的现有手动创建的代码的意义上说,它必须是正交的。 这样做的原因是,在以后的任何阶段发生的代码生成已经在单元测试执行之后进行,因此不可能测试生成的代码是否以任何不希望的方式影响代码的行为。

在测试过程中生成代码可以考虑手册以及生成的代码对整个代码进行测试。 本身不应该对生成的代码本身进行测试(这是对代码生成器项目进行测试的任务),但是程序员编写的手动代码的行为可能取决于生成的代码以及测试的执行可能取决于生成的代码。

为确保生成的代码对所有测试都可以,在生成任何新代码的情况下,应再次执行编译和测试。 为了确保这一点,从测试中调用代码生成,并且在生成新代码的情况下测试失败。

为了更正此问题,通常会调用Java :: Geci中的代码生成
来自具有以下结构的三行单元测试:

Assertions.assertFalse(...generate(...),"code has changed, recompile!");

generate(...)的调用是配置框架和生成器的方法调用链,当框架执行时,将确定所生成的代码是否与现有代码不同。 如果代码更改,它将Java代码写回到源代码,但是如果生成的代码没有更改,则将代码保留完整。

方法generate()是代码链中的最终调用
代返回true ,如果任何代码被更改,并写回
源代码。 这将使测试失败,但是如果我们再次运行测试 使用已修改的源,则测试应该可以正常运行。

此结构对生成器有一些约束:

  • 如果生成器在相同的源和类上执行,则生成的代码应完全相同。 通常这不是一个严格的要求,代码生成器通常不会生成随机源。 一些代码生成器可能希望在代码中插入时间戳作为注释:他们不应该这样做。
  • 生成的代码成为源代码的一部分,它们不是编译时工件。 所有将代码生成到现有类源中的代码生成器通常都是这种情况。 Java :: Geci可以生成单独的文件,但是它主要是为内联代码生成而设计的(因此得名)。
  • 生成的代码必须保存到存储库中,手动源以及生成的代码必须处于不需要进一步生成代码的状态。 这样可以确保开发中的CI服务器可以使用原始工作流程:提取–编译–测试–将工件提交到存储库。 代码生成已经在开发人员机器上完成,并且CI上的代码生成器仅确保它确实完成了(否则测试失败)。

请注意,代码是在开发人员机器上生成的事实
不违反构建应独立于机器的规则。
如果存在任何机器依赖性,那么代码生成将 导致CI服务器上的代码不同,因此构建将中断。

代码生成API

代码生成器应用程序应该很简单。 框架必须执行与大多数代码生成器相同的所有任务,并应提供支持,否则框架的职责是什么?

Java :: Geci为代码生成器做了很多事情:

  • 它处理文件集的配置以查找源文件
  • 扫描源目录并找到源代码文件
  • 读取文件,如果文件是Java源代码,则有助于查找与源代码相对应的类
  • 支持反射调用以帮助确定性代码生成
  • 统一配置处理
  • Java源代码生成方式不同
  • 仅在更改时修改源文件并写回更改
  • 提供功能齐全的示例代码生成器。 其中之一是成熟的Fluent API生成器,仅此一个就可以是一个整个项目。
  • 支持Jamal模板和代码生成。

摘要

阅读本文,您可以了解Java :: Geci的工作方式。 您实际上可以在Java :: Geci的GitHub主页上开始使用它。 我还将在2019年5月8日(星期三) 在JAX会议上在美因茨就这一主题发表演讲。18:15 – 19:15

在接下来的几周中,我计划就Java :: Geci中遵循的设计注意事项和实际解决方案撰写更多文章。

我们鼓励您与我联系,以获取代码,然后在Twitter上创建票证,链接为whatnot。 很好玩。

翻译自: https://www.javacodegeeks/2019/04/generate-source-code.html

g代码生成器 源代码

更多推荐

g代码生成器 源代码_如何生成源代码?