我应该在源代码管理中存储生成的代码

Ron*_*lev 96 version-control code-generation

这是我正在参与的辩论.我想得到更多的意见和观点.

我们有一些在构建时生成的类来处理数据库操作(在这个特定情况下,使用SubSonic,但我不认为这对于这个问题非常重要).生成设置为Visual Studio中的预构建步骤.因此,每次开发人员(或官方构建过程)运行构建时,都会生成这些类,然后将其编译到项目中.

现在有些人声称,在源代码控制中保存这些类可能会导致混淆,以防您获得的代码与您自己的环境中生成的代码不匹配.

我想有办法追溯代码的历史,即使它通常被视为黑盒子.

任何论据或反驳论点?


更新:我问过这个问题,因为我真的相信有一个明确的答案.看看所有回复,我可以高度肯定地说,没有这样的答案.应根据多个参数做出决定.阅读下面的答案可以为您在决定此问题时应该问自己的问题类型提供非常好的指导.

由于上述原因,此时我不会选择接受的答案.

Gle*_*len 49

将它保存在源代码管理中比它的价值更麻烦.

每次进行构建时都必须提交一个提交,以使其成为任何值.

通常我们将生成的代码(idl,jaxb等等)留在我工作的源代码控制之外,这从来就不是问题

  • 我不同意"你每次建造都必须做出承诺".这应该不会导致额外的提交,因为唯一应该影响提交的是对代码的更改,从而更改生成的源.因此,实际上只有在您已将更改提交到生成代码的源时,才必须提交生成的代码. (39认同)
  • 同意JaredPar.您的代码生成器也可能是一个外部工具,如果您更新它,生成的代码可能会更改,因此您可能需要提交更改.但在这种情况下,我真的希望看到源控制的变化. (3认同)

Kie*_*eli 31

每当我想在我自己的个人仓库中显示对源树的更改时,所有"生成的文件"都会显示为已更改并需要进行评估.

我更希望有一个更清晰的修改列表,其中只包含已执行的实际更新,而不是自动生成的更改.

将它们保留,然后在构建之后,在每个生成的文件上添加"忽略".

  • 我所在的地方,除非真的发生变化,否则它们不会显示为"已经改变".如果它们被重新生成但仍然具有相同的内容,那么唯一不同的是文件创建/修改日期,系统认为它们没有改变,一切都很好. (5认同)
  • 此外,在更新时,您可能会遇到VCS认为需要解决的奇怪冲突,但实际上会在您下次构建时自行解决.更不用说日志中的杂乱,我认为这比你当地树上的杂乱更糟糕. (3认同)
  • 我见过自动生成工具,每次运行时都会更新时间戳.我诅咒他们 (3认同)

Jar*_*Par 28

把它放在源代码控制中.拥有您为未来开发人员编写的所有内容的历史记录的优势超过了同步后偶尔重建的轻微痛苦.

  • 这不是一个优势 - 因为创建它的代码已经过检查,你已经拥有了"你写的所有内容",可供未来的开发人员使用. (18认同)
  • 通过该逻辑,您还应该检查编译的目标文件,库和可执行文件. (18认同)
  • @Shane,我非常不同意.拥有创建它的代码不等于拥有代码.在追踪错误时,任何必须包含在生成中的额外步骤都会带来额外的烦恼.通过代码的历史比查看N个版本的文件并重新生成N个版本的生成代码要简单得多. (11认同)
  • 将生成的文件放在源代码管理中有时是有益的.例如,如果升级组件(在本例中为SubSonic),则可以轻松检测生成的源中的更改.这可以用于跟踪错误和问题.我不会将所有生成的代码添加到源代码管理中.有时它非常有用.大多数源代码控制系统都会让你做一个差异来查看文件是否真的发生了变化,尽管如果你必须手动还原文件,即使唯一的变化是时间戳,它也可能更像是手动过程. (10认同)
  • 你使用什么样的代码生成器"原始语言毫无意义"?至于跟踪用于构建每个版本代码的工具的哪些版本,您需要为整个工具链解决该问题.毕竟,除非您知道当时使用的编译器和链接器版本,否则您希望如何将错误反向移植到旧版本的产品中?代码生成器与您的C++/Java/C#编译器没有什么不同.您可能能够读取其输出的事实并不重要:它的输入是源. (9认同)
  • @Joe:最初的问题是讨论构建过程生成的代码(即:"每次开发人员......运行构建时,都会生成这些类").IDE生成的代码是一种非常不同的情况,因为生成的代码没有"源". (6认同)
  • 至于"原始语言毫无意义是一个极端的例子":我同意,但这也是你的论证持有水的唯一情况.拥有代码生成器的重点是它的输入比输出更简洁.我使用的每个代码生成器生成的代码都比代码生成器的输入复杂得多.我不记得有人想要查看生成代码的历史记录. (5认同)
  • 矛盾的是,你声称在你认为很容易找出生成的文件需要重新提交的同时,很难确定要同步哪些文件.这些都是同样的问题.问题是,您是否希望在每次更改时都能弄清楚(几乎可以保证搞砸)或者您是否想要在生成的文件的历史更改中进行少数几次的操作(如果有的话 - 我在哪里工作,我们使用一堆代码生成器生成数百个文件而不是一次我听说有人需要这样做. (4认同)
  • @JaredPear,我提到了我的经验,但我的评论的真正意义在于,可能需要修复的真正逻辑是输入或代码生成器,而不是输出.因此,检查该历史记录将更有可能让您更深入地了解该错误. (4认同)
  • 您需要多久查看一次生成文件的历史记录,而不是它们生成源文件的历史记录?根据我的经验:几乎从来没有(而且我在过去7年中一直从事代码生成器的工作)。签入生成的代码所带来的额外费用虽然每次更改都不算大,但它们很快就累加了。每个将源修改为生成文件的更改都有一个额外的错误源(“哎呀,忘了签入生成的文件”),并且在代码检查中处理起来比较繁琐。编写脚本以重新生成历史版本是微不足道的。 (3认同)
  • 我也怀疑非开发人员更喜欢在XML文件中看到生成的代码.:-) (3认同)
  • @JaredPar很抱歉,但手动黑客生成的文件并不是一个更好的选择.你这样做,现在你有一些生成的文件和一些手工维护的文件. (3认同)
  • 我猜你下次运行代码生成器成为一个长期维护问题时,你不会考虑重新出现这个错误...... (3认同)
  • @JaredPar,希望在两年半的时间里你必须经历这些问题,你已经改变了对这一问题的看法.我坚信提交作为构建的一部分生成的源文件是一种有缺陷的方法. (3认同)
  • 我同意对此的批评.代码生成器将手动编写的输入转换为自动生成的输出 - 就像编译器,链接器或任何其他构建工具一样.除了源代码之外,您还需要适当版本中的所有构建工具才能重新创建旧版本,编译器或任何其他代码生成器之间没有任何区别.我见过的大多数公司都将发布二进制文件存储在某个地方,但没有一个将它们存储在源代码控制中.(毕竟,有一个原因是它被称为_"源"_控制.) (3认同)
  • @劳伦斯,我不同意。DLL 不提供任何历史价值,因为您无法通过检查差异来了解历史记录。 (2认同)
  • @Laurence Gonsalves.我不认为它是黑色的.例如,Visual Studio设计者生成的代码以及MSDataSetGenerator或ResXFileCodeGenerator等工具通常会签入源代码控制.但我可以想象我使用不在源代码控制中的预处理器生成文件的情况. (2认同)
  • @Laurence,关于我正在谈论的代码生成器主要是针对我在源代码树中看到过的几个老hacky生成器.原作者以及自左作者决定之后很久的奇怪格式毫无意义.在这种情况下,我所关心的只是将其纳入产品的代码.我不认为你的评论的其余部分解决了如何编写脚本来重新生成代码将是一个微不足道的操作.例如,如何让您的发电机了解我们使用发电机更改清单Y的版本X?更容易浏览源历史记录 (2认同)
  • @Laurence @JaredPear:我从未需要在10年的编码中查看生成文件的修订历史记录.我同意劳伦斯的观点,输入更容易处理输出.生成的代码应该是一个黑盒子,如果你使用它,你应该理解它的输入.根据我的经验,真正的逻辑在输入或代码生成器中.因此,必须在代码生成器或输入文件中修复错误. (2认同)

Lau*_*ves 25

这样看:你检查你的目标文件到源代码管理?生成的源文件是构建工件,就像目标文件,库和可执行文件一样.它们应该被对待.大多数人认为你不应该将生成的目标文件和可执行文件检查到源代码控制中.相同的参数适用于生成的源.

如果需要查看生成文件的历史版本,可以同步到其源的历史版本并重建.

将生成的任何类型的文件检入源控件类似于数据库非规范化.有偶尔的理由这样做(通常用于性能),但这应该只是非常小心,因为它变得更加困难,一旦数据被规格化,以保持正确性和一致性来完成.


Fea*_*eep 20

我会说你应该避免将任何生成的代码(或其他工件)添加到源代码控制.如果生成的代码对于给定的输入是相同的,那么您可以检查要扩散的版本并生成用于比较的代码.


Joh*_*ith 17

我称之为DRY原则.如果您已经在存储库中拥有用于在构建时生成这些代码文件的"源文件",则不需要将"相同的代码"提交"两次".

此外,如果例如代码生成有一天会中断,您可以通过这种方式避免一些问题.


tha*_*att 14

我真的不认为你应该检查它们.

当然,生成的代码中的任何变化都可能是噪声 - 环境之间的变化,或者由于其他原因而发生的变化 - 例如数据库中的变化.如果您的数据库的创建脚本(或任何其他依赖项)在源代码管理中,那么为什么还需要生成的脚本呢?


dkr*_*etz 13

不,有三个原因.

  1. 源代码是一些必要的,足以在当前或以前的某个时间点重现应用程序的快照 - 仅此而已.这意味着有人负责检查所有内容.通常我很乐意对我编写的代码负责,但不会因为我编写的代码而生成代码.

  2. 我不希望有人试图通过使用可能是也可能不是最新的中间代码来尝试从主要来源快捷构建构建(更重要的是我不想承担责任.)并且它也是如此诱惑某些人陷入无意义的过程中,基于部分构建调试中间代码中的冲突.

  3. 一旦它在源代码管理中,我就承担责任.它在那里,b.它是最新的,和c.它可以与那里的其他一切可靠地融为一体.这包括在我不再使用它时删除它.责任越少越好.


Sha*_*men 8

一般规则是否定的,但是如果生成代码需要时间(因为数据库访问,Web服务等),那么您可能希望在源代码管理中保存缓存版本并为每个人节省痛苦.

您的工具还需要了解这一点,并在需要时处理源控件的检出,太多工具决定从源控件中检出而没有任何理由.
一个好的工具将使用缓存版本而不触及它(也不修改文件的时间步长).

此外,您需要在生成的代码中添加大警告,以便人们不修改文件,顶部的警告是不够的,您必须每十几行重复一次.


Gar*_*thJ 7

这里提出了支持和反对的充分论据。作为记录,我在 Visual Studio 中构建了 T4 生成系统,我们默认的开箱即用选项会导致生成的代码被签入。如果您不想签入,则必须更加努力。

对我来说,关键的考虑因素是在输入或生成器本身更新时区分生成的输出。

如果您没有签入输出,则必须在升级生成器或修改输入之前获取所有生成代码的副本,以便能够将其与新版本的输出进行比较。我认为这是一个相当乏味的过程,但是通过签入输出,将新输出与存储库进行比较就很简单了。

此时,有理由问“为什么您关心生成代码的更改?” (特别是与目标代码相比。)我相信有几个关键原因,这些原因归结为当前的技术水平而不是任何固有的问题。

  1. 您可以编写与生成的代码紧密结合的手写代码。如今 obj 文件的整体情况并非如此。当生成的代码发生更改时,很遗憾,经常会出现一些手写代码需要更改以匹配的情况。人们通常不会在生成的代码中观察到与可扩展点的高度向后兼容性。

  2. 生成的代码只是改变其行为。您不会容忍编译器出现这种情况,但公平地说,应用程序级代码生成器正在针对不同领域的问题提供更广泛的可接受的解决方案。重要的是要看看你对以前行为所做的假设现在是否被打破了。

  3. 您只是不 100% 信任生成器从一个版本到另一个版本的输出。生成器工具具有很多价值,即使它们不是按照编译器供应商的严格构建和维护的。版本 1.0 可能对于您的应用程序来说是完全稳定的,但也许 1.1 现在对于您的用例来说有一些小故障。或者,您更改​​输入值并发现您正在使用以前未使用过的新生成器 - 可能您会对结果感到惊讶。

本质上,所有这些事情都归结为工具成熟度 - 大多数业务应用程序代码生成器还没有达到编译器甚至 lex/yacc 级工具多年来的水平。


Phi*_*Lho 6

我们不存储生成的DB代码:因为它是生成的,所以您可以从源文件中的任何给定版本随意获取它.存储它就像存储字节码等.

现在,您需要确保在给定版本中使用的代码生成器可用!较新的版本可以生成不同的代码......


Rya*_*yan 6

在某些项目中,我将生成的代码添加到源代码管理中,但这确实取决于。我的基本准则是,如果生成的代码是编译器的固有部分,那么我不会添加它。如果生成的代码来自外部工具,例如本例中的 SubSonic,那么我会将 if 添加到源代码管理中。如果您定期升级组件,那么我想知道生成的源代码中的更改,以防出现错误或问题。

至于需要签入的生成代码,最坏的情况是手动区分文件并在必要时还原文件。如果你使用 svn,你可以在 svn 中添加一个 pre-commit 钩子来拒绝提交,如果文件没有真正改变。


Phi*_*ler 5

在一种特殊情况下,您希望签入生成的文件:当您可能需要在用于生成其他文件的工具不可用的系统上进行构建时。这方面的经典示例,也是我使用的示例,是 Lex 和 Yacc 代码。因为我们开发的运行时系统必须在各种各样的平台和架构上构建和运行,所以我们只能依靠目标系统来拥有 C 和 C++ 编译器,而不是为我们的接口定义生成词法/解析代码所需的工具翻译。因此,当我们更改语法时,我们会检查生成的代码以对其进行解析。

  • 类似的注释适用于 autoconf/automake 生成的文件;大多数人检查他们的 ./configure 和 Makefile.in 文件,即使它们是生成的 - 大多数用户(和许多开发人员)不需要重建它们,并且通过检查这些文件,您不需要安装 autotools建立。 (2认同)

dil*_*ig0 5

到的有点晚了……反正……

您会将编译器的中间文件放入源代码版本控制中吗?在代码生成的情况下,根据定义,源代码是生成器的输入,而生成的代码可以被视为“真实”源代码和构建的应用程序之间的中间文件。

所以我会说:不要将生成的代码置于版本控制之下,而是将生成器及其输入置于版本控制之下。

具体来说,我使用我编写的代码生成器:我从来不必在版本控制下维护生成的源代码。我什至会说,由于生成器达到了一定的成熟度,尽管输入(例如模型描述)发生了变化,但我不必观察生成代码的内容。


ric*_*ich 5

把它拿出来.

如果您正在检入生成的文件,那么您做错了.什么是错的可能会有所不同,这可能是因为你的构建过程是低效的,还是其他什么东西,但我不能看到它永远是一个好主意.历史记录应与源文件相关联,而不是与生成的文件相关联.

对于那些最终试图解决差异,找到不再由构建生成的文件然后删除它们等的人来说,这只会让人头疼.

一个痛苦的世界等待那些签入生成文件的人!