如果可以生成某种东西,那么那件事就是数据,而不是代码。
鉴于此,整个生成源代码的想法难道不是一个误解吗?也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将生成的”代码本应执行的正确操作的适当函数呢?
如果出于性能原因执行此操作,那么这听起来像是编译器的缺点。
如果要进行两种语言的桥接,那么这似乎缺少接口库。
我在这里遗漏了什么吗?
我知道代码也是数据。我不明白的是,为什么要生成源代码?为什么不使其成为可以接受参数并对其执行操作的函数?
#1 楼
源代码生成是一种反模式吗?
从技术上讲,如果我们生成代码,即使它是人类可以阅读的文本,它也不是源代码。源代码是由人类或其他真正智慧生成的原始代码,未经机械翻译且不能从(真实)源(直接或间接)立即复制。
如果可以生成某些内容,那东西就是数据,而不是代码。
我会说一切都是数据。甚至源代码。特别是源代码!源代码仅仅是一种旨在完成编程任务的语言中的数据。这些数据将根据需要转换,解释,编译,生成为其他形式的数据,其中某些形式恰好是可执行的。
处理器执行内存不足的指令。用于数据的相同内存。在处理器执行指令之前,程序会作为数据加载到内存中。
所以,所有内容都是数据,甚至是代码。
鉴于[生成的代码是数据],这不是整个代码生成的概念都会引起误解吗?
进行多个编译步骤是完全可以的,其中一个步骤可以是中间代码生成为文本。 br />
也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将要生成”代码的正确操作的适当函数
这是一种方法,但还有其他方法。
代码生成的输出是文本,其中是供人类使用的东西。
并非所有文本形式都供人类食用。特别是,生成的代码(作为文本)通常旨在供编译器使用而不是人类使用。
源代码被认为是原始代码:大师-我们编辑和开发的内容;我们使用源代码控制归档的内容。即使是人类可读的文本,生成的代码通常也会从原始源代码中重新生成。一般来说,生成的代码不必受源代码控制,因为它是在构建过程中重新生成的。
评论
评论不作进一步讨论;此对话已移至聊天。
– Maple_shaft♦
17年12月1日在13:31
#2 楼
实用推理好吧,我知道代码也是数据。我不明白的是,为什么要生成源代码?
通过此编辑,我认为您是在相当实际的水平上提出问题,而不是理论上的计算机科学。
以静态语言生成源代码的经典原因像Java一样,这种语言实际上并没有真正使用易于使用的语言工具来完成非常动态的工作。例如,在Java的早期,根本无法轻松地创建一个具有动态名称(与数据库中的表名匹配)和具有动态数据类型(与数据库中的表名匹配)的动态方法(与该表中的属性匹配)的类。所述属性的类型)。特别是因为Java在编译时能够捕获类型错误方面非常重要,不能保证。
因此,在这种情况下,程序员只能创建Java代码并编写代码。手动编写很多代码。通常,程序员会发现,每当表发生更改时,他都必须返回并更改代码以使其匹配。如果他忘了那件事,就会发生坏事。因此,程序员将到达编写自己的工具的地步。因此,这条路开始了更加智能的代码生成。
(是的,您可以即时生成字节码,但是用Java编程这样的事情并不是随机程序员会在两者之间做的事情。编写几行域代码。)
将其与动态性很强的语言(例如Ruby)进行比较,我会在大多数方面考虑Java的对立面(请注意,我在说这时并不重视任何一种方法;它们只是有所不同)。在这里,在运行时动态生成类,方法等是100%正常和标准的,最重要的是,程序员可以在代码中轻松完成它,而无需进入“元”级别。是的,诸如Ruby on Rails之类的东西是随代码生成一起提供的,但是我们在工作中发现,我们基本上将它用作新程序员的一种高级“教程模式”,但是过了一会儿它就变得多余了(因为代码太少了)在那个生态系统中编写代码,当您知道自己在做什么时,手动编写它比清理生成的代码要快。)
这只是“现实世界”中的两个实际示例。然后,您就有了像LISP这样的语言,其中的代码实际上就是数据。另一方面,在编译语言中(没有像Java或Ruby这样的运行时引擎),(或者曾经,我没有跟上现代C ++功能...)根本没有在运行时定义类或方法名称的概念,因此,代码生成构建过程是大多数事情的首选工具(其他更特定于C / C ++的示例将是诸如flex,yacc等之类的东西)。
评论
我认为这比投票率更高的答案更好。特别是,使用Java和数据库编程提到的示例在实际解决使用代码生成的原因并且是有效的工具方面做得更好。
–装甲危机
17年11月29日13:57
这些天来,是否有可能用Java从数据库创建动态表?还是仅通过使用ORM?
–本体
17年12月1日下午5:18
“(或者曾经,我没有跟上最新的C ++功能...)”由于函数指针,这肯定已经在C ++中实现了二十多年了吗?我还没有测试过,但是我确定应该应该分配一个char数组,用机器代码填充它,然后将指向第一个元素的指针转换为函数指针,然后运行它? (假设目标平台没有采取任何安全措施来阻止您这样做,它很可能会这样做。)
–法老王
17年2月2日在19:43
“分配一个char数组,用机器代码填充它,然后将指向第一个元素的指针转换为函数指针,然后运行它吗?”除了具有不确定的行为,它在C ++中等同于“动态生成字节码”。它属于“普通程序员不考虑”的同一类别
– Caleth
18年8月2日在11:22
@Pharap,“可以肯定的是,在C ++中这已经有二十多年的历史了”……我不得不轻笑一下;自从我上一次编码C ++以来,大约有20年的历史。 :)但是我关于C ++的句子表达得很糟糕。我已经对其进行了一些更改,现在应该更清楚我的意思了。
– AnoE
18年8月2日在11:28
#3 楼
为什么生成代码?
因为用打孔卡(或记事本中的alt代码)进行编程很麻烦。
出于性能原因而被执行,那么这听起来像是编译器的缺点。
是的。除非被迫做,否则我不会在乎性能。
如果要完成两种语言之间的桥梁,那么听起来就好像缺少接口库。
嗯,不知道您在说什么。
看起来像这样:生成并保留的“源”代码永远都是永远的难题。它存在仅出于一个原因。有人想用一种语言工作,而其他人则坚持用另一种语言工作,任何人都不会费心找出如何在它们之间进行互操作,因此其中一个人想出如何将自己喜欢的语言变成所强加的语言,以便他们可以做什么他们想要的。
在我必须维护之前,还可以。到这时你们都可以死。
这是一种反模式吗? igh,不。如果我们不愿意告别以前的语言的缺点,那么甚至没有很多语言会出现,而生成旧语言的代码就是新语言的开始。
这是一个代码库,遗留在一半我无法忍受的弗兰肯斯坦怪物拼凑而成的代码库中。生成的代码是不可修改的代码。我讨厌看无法触及的代码。但是人们一直在检查它。为什么?您可能还需要检入可执行文件。
现在我正在咆哮。我的观点是,我们都是“生成代码”。当您将生成的代码像源代码一样对待时,会让我发疯。只是因为看起来像源代码并没有使其成为源代码。
评论
如果生成它,则不是源代码。这是中间代码。我现在要哭了。
–candied_orange
17年11月29日在5:02
!!看起来是什么都没关系!!!文本,二进制,DNA,如果不是源,则不是进行更改时应该触摸的内容。如果我的编译过程中使用了42种中间语言,这无关紧要。别碰他们。停止检入它们。在源代码处进行更改。
–candied_orange
17年11月29日在5:07
XML是文本,它显然不是供人类使用的。 :-)
–尼克·基利(Nick Keighley)
17年11月29日在9:25
@utku:“如果不是人类要食用的东西,就不应该是文字”:我完全不同意。我想到了一些反例:HTTP协议,MIME编码,PEM文件-几乎任何在任何地方使用base64的东西。将数据编码为7位安全流的原因很多,即使没有人可以看到它也是如此。更不用说通常人类永远不应该与之交互的更大空间,而是他们偶尔可能想要的空间:日志文件,Unix上的/ etc /文件等。
–丹尼尔·普赖登(Daniel Pryden)
17年11月29日在14:03
我认为“用打孔卡编程”并不意味着您认为的含义。我去过那里,做了那件事,是的,这很痛苦。但它与“生成的代码”无关。一副打孔卡只是另一种文件,例如磁盘上的文件,磁带上的文件或SD卡上的文件。过去,我们会将数据写入卡片组,然后从卡片组中读取数据。因此,如果我们生成代码的原因是使用打孔卡进行编程很麻烦,那么这意味着使用任何类型的数据存储进行编程都是很痛苦的。
–所罗门慢
17年11月29日在17:46
#4 楼
为什么生成源代码
我在职业生涯中最常使用的代码生成器用例是生成器,
将某种数据模型或数据库模式作为输入(可能是关系模式或某种XML模式)进行了高级元描述
,并为数据访问类生成了样板CRUD代码作为输出,也许还有诸如相应的SQL或文档之类的其他内容。
这里的好处是,从简短输入规范的一行可以得到5到10行可调试,类型安全,无错误的代码(假定代码生成器的输出已成熟),否则您必须手动实现和维护代码。您可以想象这会减少多少维护和开发工作。
让我也回答您的第一个问题
源代码生成是否是反模式? >
不,本身不是源代码生成,但是确实存在一些陷阱。如实用程序程序员中所述,在生成难以理解的代码时,应避免使用代码生成器。否则,增加使用或调试此代码的工作量可能会轻易超过不手动编写代码而节省的工作量。
我还要补充一点,通常是将生成的部分分开是个好主意从物理上以手动方式生成的代码中,重新生成的方式不会覆盖任何手动更改。但是,我还不止一次地处理过这种情况,即任务是将用旧语言X编写的某些代码迁移到另一种更现代的语言Y上,并打算随后使用语言Y进行维护。这是有效的用法一次性生成代码的情况。
评论
我同意这个答案。使用类似Torque的Java,我可以使用与sql数据库匹配的字段自动生成Java源文件。这使操作更加容易。主要好处是类型安全,包括仅能够引用数据库中存在的字段(谢谢您自动完成)。
– MTilsted
17年11月29日在12:34
是的,对于静态类型的语言,这是重要的部分:您可以确保手写代码实际上适合所生成的代码。
–PaŭloEbermann
17年11月29日在23:15
“迁移一些用旧语言编写的代码”-即使那样,一次性代码生成也可能会很麻烦。例如,在进行一些手动更改后,您将检测到生成器中的错误,并且需要在修复后重做生成。幸运的是,git或类似的方法通常可以减轻疼痛。
– maaartinus
17年1月1日,下午3:46
#5 楼
为什么生成源代码?
我遇到了两个生成的用例(在构建时,从未检入):
通过用于指定特定语言的语言(例如Java的lombok)自动生成样例代码,例如getters / setters,toString,equals和hashCode。
根据某些接口规范自动生成DTO类型类(REST) ,SOAP等),然后在主代码中使用。这类似于您的语言桥梁问题,但最终变得更简洁,更简单,并且与尝试在没有生成类的情况下实现相同的事物相比,具有更好的类型处理。
评论
表达力强的语言中的高度重复的代码。例如,我不得不编写一些代码,这些代码在许多相似但不完全相同的数据结构上做同样的事情。它可能可以用C ++模板完成(嘿,那不是代码生成吗?)。但是我使用的是C语言。代码生成使我不必编写很多几乎相同的代码。
–尼克·基利(Nick Keighley)
17年11月29日在9:29
@NickKeighley也许您的工具链不允许您使用其他更合适的语言?
– OmarL
17年11月29日在10:23
通常您不会选择您的实现语言。该项目是用C语言编写的,这不是一个选择。
–尼克·基利(Nick Keighley)
17年11月29日在10:49
@Wilson更具表现力的语言经常使用代码生成(例如lisp宏,rails上的ruby),但它们不需要同时将它们另存为文本。
– Pete Kirkham
17年11月29日在10:53
是的,代码生成本质上是元编程。诸如Ruby之类的语言允许您使用语言本身进行元编程,但是C不允许,因此您必须使用代码生成。
– Sean Burton
17年11月29日在11:45
#6 楼
Sussmann在他的经典著作《计算机程序的结构和解释》中对这些事情有很多有趣的说法,主要是关于代码数据二元性的。对我来说,adhoc代码生成的主要用途是利用可用的编译器将一些特定于领域的语言转换为可以链接到我的程序中的语言。想想BNF,想想ASN1(实际上,不要,这很丑陋),想想数据字典电子表格。
专用于特定领域的语言可以节省大量时间,并且创建可以由标准语言工具编译的内容是创建此类内容时要走的路,而您宁愿对其进行编辑,也不是一件容易的事用您正在编写的任何本地语言破解了解析器,还是自动生成的BNF解析器?
通过输出文本,然后将其输入到某些系统编译器中,我无需进行考虑即可获得所有这些编译器优化和特定于系统的配置。
我正在有效地将编译器输入语言用作另一种中间表示形式,这是什么问题?文本文件不是天生的源代码,它们可以是编译器的IR,如果它们看起来像C或C ++或Java或其他内容,谁在乎?
现在,如果您很难想到可以编辑玩具语言解析器的输出,那么下次有人编辑输入语言文件并进行重建时,它显然会令人失望,答案是不要提交自动翻译器。生成由仓库生成的IR,由您的工具链生成(并且避免在开发团队中有这样的人,他们通常在市场营销方面会更快乐)。
这并不是我们语言表达能力的失败,而是一种表达的事实,有时您可以将规范的某些部分(或按摩)成可以自动转换为代码的形式,这通常会减少更少的错误,并且更容易维护。
如果我可以给我们的测试和配置人员一个电子表格,他们可以对其进行调整,然后他们运行一个工具来获取数据,并在ECU上为闪存生成一个完整的十六进制文件,那么与由他人手动翻译相比,这将节省大量时间将最新的设置转换为一天中用语言编写的一组常量(用拼写法完成)。
在Simulink中构建模型也是如此,然后用RTW生成C,然后使用有意义的工具进行定位,中间的C是不可读的,那又如何呢?高级Matlab RTW东西只需要知道C的子集,C编译器负责平台的细节。只有当RTW脚本有错误时,人类才需要仔细检查生成的C,这种事情用名义上可读的IR进行调试要比使用二进制分析树进行调试容易得多。
当然,您可以编写这样的东西来输出字节码甚至可执行代码,但是为什么要这样做呢?我们有将IR转换为这些东西的工具。
评论
很好,但是我要补充一点,在确定使用哪个IR时需要权衡:与例如x86汇编语言相比,使用C作为IR会使某些事情变得更容易,而其他事情则变得更难。例如,在Java语言代码和Java字节码之间进行选择时,选择就更加重要了,因为还有许多操作仅以一种或另一种语言存在。
–丹尼尔·普赖登(Daniel Pryden)
17年11月29日在14:21
但是,当针对ARM或PPC内核时,X86汇编语言的IR较差!万物都是工程学的权衡,这就是为什么他们称其为工程学。人们希望Java字节码的可能性是Java语言的可能性的严格超集,并且随着您与金属的距离越来越近而不论工具链和注入IR的位置,这通常都是正确的。
–丹·米尔斯(Dan Mills)
17年11月29日在14:29
哦,我完全同意:我的评论是对您的最后一段的回答,询问您为什么要输出字节码或一些较低级别的东西-有时您确实需要较低级别的东西。 (特别是在Java中,字节码可以做很多有用的事情,而Java语言本身无法做到。)
–丹尼尔·普赖登(Daniel Pryden)
17年11月29日在16:38
我没有不同意,但是在金属上使用IR不仅要付出一定的代价,不仅降低通用性,而且事实上,您通常最终会承担更多真正令人烦恼的低级优化。这些天我们通常在优化算法选择而不是实现方面进行思考,这反映了编译器已经走了多远,有时您在这些事情上必须走得很近,但是在扔掉编译器之前要三思而后行通过使用过低的IR进行优化的能力。
–丹·米尔斯(Dan Mills)
17年11月29日在16:58
“他们通常在市场营销方面更快乐”,凯蒂,但很有趣。
– dmckee ---前主持人小猫
17年12月1日在2:40
#7 楼
务实的答案:代码生成是否必要且有用?它是否提供了真正非常有用的专有代码库所需要的东西,或者它似乎只是在创建另一种工作方式,从而为次优结果贡献了更多的智力开销?好吧,我知道代码也是数据。我不明白的是,为什么要生成代码?为什么不让它成为一个可以接受参数并对其执行操作的函数?
如果您不得不问这个问题而又没有明确的答案,那么代码生成可能是多余的,而仅仅是在代码库中增加了异国情调和大量的知识开销。
同时使用OpenShadingLanguage之类的代码:
https://github.com/imageworks/OpenShadingLanguage
/> ...然后就不必再提出这样的问题了,因为令人印象深刻的结果将立即回答这些问题。
OSL使用LLVM编译器框架将着色器网络转换为计算机动态代码(即时或“ JIT”),并在此过程中
充分了解着色器参数和其他本来不可能的其他运行时值,对着色器和网络进行了优化。 />从源代码编译着色器时已知。结果,我们看到我们的OSL着色网络的执行速度比用C手工制作的等效着色器快25%! (这就是我们的旧着色器
在渲染器中的工作方式。)
在这种情况下,您无需质疑代码生成器的存在。如果您在这种VFX域中工作,那么您的直接反应通常是,“闭嘴,拿走我的钱!”或者,“哇,我们还需要做这样的事情。”
评论
将着色器网络转换为机器代码。这听起来像是编译器,而不是代码生成器,不是吗?
– Utku
17年11月29日在4:32
用户连接一个节点网络并生成中间代码,该中间代码由LLVM编译为JIT。编译器和代码生成器之间的区别是模糊的。您是否在考虑使用诸如C ++或C预处理器中的模板之类的语言编写代码的功能?
–user204677
17年11月29日在4:34
我在想可以输出源代码的任何生成器。
– Utku
17年11月29日在4:37
我认为,这里的产出仍供人类消费。 OpenSL还生成中间源代码,但是它是低级代码,对于LLVM消耗来说,它接近于汇编。通常,不是要维护的代码(而是程序员维护用于生成代码的节点)。我大多数时候认为这些类型的代码生成器更可能被滥用,而不是足以证明其价值有用,尤其是在构建过程中必须不断重新生成代码的情况下。有时候,他们仍然有真正的位置来解决缺点。
–user204677
17年11月29日在4:39
...用于特定域时可用的语言。 QT的元对象编译器(MOC)就是其中之一。 MOC减少了您通常需要在C ++中提供属性,反射,信号和插槽等所需的样板,但没有达到明显证明其存在的合理程度。我经常认为,如果没有MOC代码生成的繁重负担,QT可能会更好。
–user204677
17年11月29日在4:39
#8 楼
不,生成中间代码不是反模式。问题的另一部分“为什么要做?”的回答是一个非常广泛(和单独)的问题,尽管无论如何我都会给出一些原因。从没有中间人的历史分支可读的代码
以C和C ++为例,因为它们是最著名的语言之一。
您应该注意,编译C代码输出的逻辑过程不是机器代码,而是易于阅读的汇编代码。同样,旧的C ++编译器通常将C ++代码物理地编译为C代码。在那一系列事件中,您可以从人类可读的代码1到人类可读的代码2到人类可读的代码3到机器代码进行编译。 “为什么?”为什么不呢?
如果从未生成中间的,人类可读的代码,我们甚至可能根本没有C或C ++。这肯定是有可能的。人们走的道路对他们的目标的抵抗最小,如果其他一些语言由于C开发的停滞而首先流行,那么C可能在它还很年轻的时候就死了。当然,您可以争论“但是也许我们会使用其他语言,也许会更好。”也许,也许会更糟。也许我们都还是用汇编语言编写。
为什么使用人类可读的中间代码?
有时需要中间代码以便您可以修改它在进行下一步之前。我会承认这一点是最弱的。
有时是因为原始工作根本不是用任何人类可读的语言完成的,而是使用GUI建模工具完成的。
有时您需要做一些非常重复的事情,并且该语言不应满足您的工作需求,因为它是一件小众事物或一件如此复杂的事情,以至于为了适应您而增加了编程语言的复杂性或语法,这无济于事。
有时,您需要做一些非常重复的事情,而无法以一种通用的方式将想要的内容理解为语言。
计算机的目标之一是减少人类的工作量,并且有时不太可能再次被触及的代码(维护的可能性很低)可以具有元代码。编写代码以十分之一的时间生成较长代码的代码;如果我可以在1天而不是2个星期内完成它,并且不太可能再维护一次,那么我最好生成它-并且从现在起5年后某个人会因为自己确实需要维护它而感到烦恼,他们可以花2周的时间完全将其编写出来,也可以花1周的时间来维护笨拙的代码(但是我们仍要提前1周),这会使他们感到烦恼,那就是该维护工作是否需要全部完成。
我肯定还有更多原因被我忽略。
示例
我曾在需要根据数据或信息生成代码的项目上工作过在其他一些文件中。例如,一个项目的所有网络消息和常量数据都在电子表格中定义,并且该工具可以遍历电子表格并生成大量的C ++和Java代码,使我们可以处理这些消息。
我并不是说这是建立该项目的最佳方法(我不是启动项目的一部分),但这就是我们所拥有的,它是数百个(甚至可能是数千个,不确定)结构和对象和正在生成的常量;到那时,尝试以Rhapsody之类的方式重做可能为时已晚。但是,即使重做了Rhapsody之类的东西,我们还是仍然有从Rhapsody生成的代码。
此外,将所有这些数据存储在电子表格中也是一种很好的方式:它允许我们表示数据,如果它们都只存在于源代码文件中,则无法实现。
示例2
在进行编译器构造工作时,我使用了Antlr工具进行词法分析。我指定了一种语言语法,然后使用该工具在C ++或Java中吐出了大量代码,然后将生成的代码与自己的代码一起使用,并将其包含在构建中。
那应该怎么做呢?也许您可以想出另一种方法;可能还有其他方法。但是对于这项工作,其他方式不会比我生成的lex / parse代码好。
评论
当两个系统不兼容但具有某种深奥的脚本语言的稳定api时,Ive将中间代码用作一种文件格式和调试跟踪。本来不是要手动读取的,但本来可以以相同的方式读取xml。但是,正如有人指出的那样,在所有网页都这样工作之后,这比您想的要普遍得多。
– joojaa
17年11月30日在21:38
#9 楼
您缺少的是重用。我们有一个了不起的工具,可以将源代码文本转换为二进制文件,称为编译器。它的输入定义明确(通常是!),并且它已经通过大量工作来完善其优化方式。如果您实际上想使用编译器来执行某些操作,则要使用现有的编译器而不是自己编写。
很多人确实发明了新的编程语言并编写自己的编译器。几乎无一例外,他们之所以这样做是因为他们乐于接受挑战,而不是因为他们需要该语言提供的功能。他们所做的一切都可以用另一种语言完成;他们只是在创造一种新的语言,因为他们喜欢这些功能。但是,经过优化的,快速,高效,优化的编译器并不能使它们获得成功。当然,它将为他们提供一些可以将文本转换为二进制文件的东西,但它并不像所有现有的编译器一样好。
文本不仅是人类可以读写的东西。电脑也很适合在家中放置文字。实际上,像XML这样的格式(以及其他相关格式)是成功的,因为它们使用纯文本。二进制文件格式通常晦涩难懂且文档记录不清,因此读者无法轻松找到它们的工作方式。 XML是相对自文档的,因此人们可以更轻松地编写使用XML格式文件的代码。并且所有编程语言均已设置为读取和写入文本文件。
因此,假设您想添加一些新的设施来简化您的生活。也许这是一个GUI布局工具。也许是Qt提供的信号和插槽接口。 TI的Code Composer Studio也许就是这种方式,它使您可以配置正在使用的设备并将正确的库放入构建中。也许是要使用数据字典并自动生成typedef和全局变量定义(是的,这在嵌入式软件中还是很重要的事情)。无论是什么,利用现有编译器的最有效方法是创建一个工具,该工具将接受您所需要的配置并自动以您选择的语言生成代码。
很容易开发并且易于测试,因为您知道发生了什么,并且可以阅读它吐出的源代码。您不需要花很多年的时间来构建与GCC竞争的编译器。您不需要学习一种全新的语言,也不需要其他人来学习。您需要做的就是自动化这一小区域,其他一切保持不变。做完了。
评论
XML的基于文本的优势仍然在于,如果有必要,它可以由人类进行读写(通常,一旦工作,他们通常就不会打扰,但是在开发过程中肯定会这样做)。就性能和空间效率而言,二进制格式通常要好得多(尽管这通常并不重要,因为瓶颈在其他地方)。
–leftaround关于
17年11月30日在9:49
@leftaroundabout如果您需要那种性能和空间效率,请确定。如今,许多应用程序都采用基于XML的格式的原因是,性能和空间效率不再是它们曾经的最高标准,并且历史证明了二进制文件格式的维护程度很差。 (旧的MS Word文档提供了一个经典示例!)重点仍然是-文本与计算机一样适合人类阅读。
–格雷厄姆
17年11月30日在11:06
当然,设计得不好的二进制格式实际上可能比经过适当考虑的文本格式更糟糕,甚至一个体面的二进制格式通常也不会比带有某些通用压缩算法的XML紧凑。 IMO的两全其美是通过代数数据类型使用人类可读的规范,并根据这些类型的AST自动生成有效的二进制表示形式。参见例如平面库。
–leftaround关于
17年11月30日,11:23
#10 楼
答案比较实用,集中在为什么而不是源代码上。请注意,在所有这些情况下,生成源代码都是构建过程的一部分-因此,生成的文件不应进入源代码控制。互操作性/简单性
以Google的协议缓冲区为首要示例:您编写了一个高级协议描述,然后可以使用该描述描述以多种语言生成实现-通常,系统的不同部分以不同的语言编写。
实现/技术原因
使用TypeScript-浏览器无法解释它,因此构建过程使用翻译器(代码到代码翻译器)生成JavaScript。实际上,许多新的或深奥的编译语言在获得合适的编译器之前都是先编译为C。
易于使用
对于用C编写的嵌入式项目(例如IoT),仅使用单个二进制文件(RTOS或没有OS),就很容易用普通的源代码生成要编译的数据的C数组,因为它们直接作为资源链接在一起。
编辑
扩展protobuf:代码生成允许生成的对象是任何语言的一流类。在编译语言中,通用解析器必须返回键值结构-这意味着您需要大量样板代码,错过了一些编译时检查(特别是在键和值类型上),导致性能下降,没有代码完成。想象一下用C语言编写的所有
void*
或用C ++语言编写的巨大的std::variant
(如果您有C ++ 17),某些语言可能根本没有这种功能。评论
出于第一个原因,我认为OP的想法是在每种语言中都有一个通用的实现(采用协议缓冲区描述,然后解析/使用在线格式)。为什么这比生成代码更糟糕?
–PaŭloEbermann
17年11月29日在23:12
@PaŭloEbermann除了通常的性能参数外,这种通用解释将使得不可能将这些消息用作已编译(并且可能已解释)语言中的第一类对象-例如,在C ++中,这样的解释器必然会返回键值结构。当然,您可以将该kv放入您的类中,但是它可以变成很多样板代码。并且也有代码完成。并进行编译时间检查-编译器不会检查文字是否没有错字。
– Jan Dorniak
17年11月29日在23:21
我同意...您可以将此添加到答案中吗?
–PaŭloEbermann
17年11月29日在23:25
@PaŭloEbermann完成
– Jan Dorniak
17年11月29日在23:47
#11 楼
源代码生成是一种反模式吗?
这是一种解决方案,可用于处理表达能力不足的编程语言。无需使用包含适当的内置元编程的语言来生成代码。
评论
这也是一种变通方法,必须为一种更具表现力的语言编写完整的,从本机到目标的代码编译器。生成C,让具有良好优化器的编译器负责其余工作。
– Blfl
17年11月29日在18:01
不总是。有时您有一个或多个包含某些定义的数据库,例如总线上的信号。然后,您希望将这些信息汇总在一起,也许要进行一些一致性检查,然后编写代码,以将总线信号与您希望在代码中包含的变量之间建立接口。如果您可以向我展示一种具有元编程的语言,从而可以轻松使用某些客户端提供的Excel工作表,数据库和其他数据源,并创建我需要的代码,并对数据的有效性和一致性进行一些必要的检查,则可以一切手段告诉我。
– CodeMonkey
17年11月30日在6:25
@CodeMonkey:像Ruby on Rails的ActiveRecord实现一样。无需在代码中复制数据库表架构。只需将类映射到表,然后使用列名作为属性编写业务逻辑即可。我无法想象代码生成器可能会产生的任何模式,而Ruby元编程也无法管理这种模式。 C ++模板也非常强大,尽管有点不可思议。 Lisp宏是另一个功能强大的语言内元编程系统。
–kevin cline
17年12月1日在8:11
@kevincline我的意思是基于数据库中某些数据(可以从中构造)的代码,而不是数据库本身。即我有关于在Excel表A中收到哪些信号的信息。我有一个包含有关这些信号的信息的数据库B,等等。现在,我想拥有一个访问这些信号的类。与运行代码的计算机上的数据库或Excel工作表没有连接。使用真正复杂的C ++模板在编译时生成此代码,而不是简单的代码生成器。我将选择codegen。
– CodeMonkey
17 Dec 4'在11:43
#12 楼
源代码生成并不总是一种反模式。例如,我目前正在编写一个框架,该框架根据给定的规范生成两种不同语言(Javascript和Java)的代码。框架使用生成的Javascript记录用户的浏览器操作,并在框架处于重播模式时使用Selenium中的Java代码实际执行操作。如果不使用代码生成,则必须手动确保两者始终保持同步,这既麻烦又在某种程度上也是逻辑上的重复。如果有人正在使用源代码代以替换通用性之类的功能,那么它就是反模式。
评论
您当然可以在ECMAScript中编写一次代码,然后在JVM上的Nashorn或Rhino中运行它。或者,您可以在ECMAScript中编写JVM(或尝试使用Emscripten将Avian编译为WebAssembly),然后在浏览器中运行Java代码。我并不是说这些想法很棒(好吧,它们可能是可怕的想法:-D),但是如果不可行,至少它们是可能的。
–Jörg W Mittag
17年11月29日在9:15
从理论上讲,这是可能的,但这不是一个通用的解决方案。如果我无法在另一种语言中运行一种语言,该怎么办?例如,还有其他事情:我只是使用代码生成创建了一个简单的Netlogo模型,并具有系统的交互式文档,该文档始终与记录器和重放器同步。通常,创建需求然后生成代码可使语义上同步运行的事物保持同步。
–赫里斯托(Hristo Vrigazov)
17年11月29日在9:36
#13 楼
我在这里遗漏了什么吗?
也许是一个很好的例子,证明中间代码是成功的原因?我可以为您提供HTML。
我相信,对于HTML来说,简单和静态非常重要-它使浏览器的制造变得容易,可以使移动浏览器尽早启动。等等。作为进一步的实验(Java小程序) ,Flash)显示-更复杂,更强大的语言会导致更多问题。事实证明,用户实际上受到Java小程序的威胁,而访问此类网站就像尝试通过DC ++下载的游戏破解程序一样安全。另一方面,纯HTML足够无害,以至于我们可以合理地相信设备的安全性来检出任何网站。
但是,如果没有HTML,它就不会比现在更远了。不是计算机生成的。在有人手动将其从数据库重写为HTML文件之前,我的答案甚至不会显示在此页面上。幸运的是,您几乎可以使用任何一种编程语言来使HTML可用:)
也就是说,如果有某种东西的代码生成器,那么为什么不将该某物变成可以接收该信息的适当函数呢?
您能想象比向用户显示问题以及所有答案和注释更好的方法吗?使用HTML作为生成的中间代码?
评论
是的,我可以想象一种更好的方法。 HTML是Tim Berners-Lee决定允许快速创建纯文本Web浏览器的决定的遗产。当时那很好,但是在事后看来,我们不会做同样的事情。 CSS使所有各种表示元素类型(DIV,SPAN,TABLE,UL等)都不再需要。
–kevin cline
17年12月1日在8:25
@kevincline我并不是说HTML本身没有缺陷,我是在指出引入标记语言(可以由程序生成)在这种情况下效果很好。
–Džuris
17年12月1日23:31
因此HTML + CSS比HTML更好。我什至为一些直接在HTML + CSS + MathJax中进行过的项目编写了内部文档。但是我访问的大多数网页似乎都是由代码生成器生成的。
– David K
17年2月2日于16:04
#14 楼
为什么生成源代码?
因为它比手动编写代码更快,更容易(并且更不容易出错),特别是对于繁琐,重复的任务。您还可以在编写一行代码之前使用高级工具来验证和验证设计。
常见用例:
建模工具如Rose或Visual Paradigm;
诸如嵌入式SQL的高级语言或必须预先处理成可编译内容的接口定义语言;
诸如flex / bison的Lexer和解析器生成器;
对于您的“为什么不仅仅使它成为一个函数并将参数直接传递给它”,请注意,以上都不是其自身的执行环境。无法将代码与它们链接。
#15 楼
有时,您的编程语言只是没有所需的功能,实际上使编写函数或宏来执行所需的操作实际上是不可能的。也许您可以做您想做的事,但是编写它的代码却很难看。然后,一个简单的Python脚本(或类似脚本)就可以在构建过程中生成所需的代码,然后将其编码到实际的源文件中。我怎么知道的?因为这是我在使用各种不同的系统(最近是SourcePawn)时多次接触到的解决方案。当最终得到两打这样的代码行(创建我的所有cvar)时,一个简单的Python脚本可以解析一行简单的源代码并生成两到三行已生成的代码,远胜于手工制作已生成的代码。 >
演示/示例源代码,如果人们需要的话。
#16 楼
“源”代码的生成指示所生成的语言的缺点。使用工具来克服这种情况是一种反模式吗?绝对不是-让我解释一下。通常使用代码生成,因为存在一个更高级别的定义,该定义可以描述生成的代码,而无需使用较低级别的语言。因此,代码生成可提高效率和简洁性。
写C ++时之所以这样做,是因为它使我可以比使用汇编器或机器码更高效地编写代码。机器代码仍然由编译器生成。最初,c ++只是生成C代码的预处理器。通用语言对于生成通用行为非常有用。
以同样的方式,通过使用DSL(域特定语言),可以编写简洁的代码,但可能仅限于特定任务的代码。这将使生成正确的代码行为变得不那么复杂。请记住,代码是到达终点的手段。开发人员正在寻找的是一种生成行为的有效方法。
理想情况下,生成器可以从输入中创建更易于操作和理解的快速代码。如果满足此要求,则不使用生成器是反模式。这种反模式通常源于“纯”代码是“更清洁”的概念,与木材工人或其他工匠使用电动工具或使用CNC来“生成”工件的方式大同小异(认为是金色的)。另一方面,如果生成的代码的源代码难以维护或生成的效率不够高,则用户会陷入使用错误工具的陷阱(有时是因为相同的金锤)。
#17 楼
需要文本形式以便于人类容易使用。计算机也很容易以文本形式处理代码。因此,生成的代码应以最易于生成和最易于计算机使用的形式生成,并且通常是可读文本。当您生成代码时,代码生成过程本身通常需要由人来调试。如果生成的代码是人类可读的,那么它非常非常有用,这样人们就可以在代码生成过程中发现问题。毕竟,有人必须编写代码才能生成代码。这不是凭空发生的。
#18 楼
只是一次生成代码并非所有源代码生成都是生成某些代码的情况,
然后再不触摸它;然后在需要更新时从原始源重新生成它。
有时您只生成一次代码,然后丢弃原始源,然后继续维护新源。
将代码从一种语言移植到另一种语言时,有时会发生这种情况。
特别是如果人们不希望以后再移植原始语言的新更改时(例如,将不再维护旧语言代码,或者它实际上是完整的(例如,在具有某些数学功能的情况下)。
一种常见的情况是编写一个代码生成器来执行此操作,实际上只能正确地转换90%的代码。 br />,然后需要手动修复最后10%。
比手动翻译100%要快得多。
这种代码生成器通常与一种代码生成器,可以生成完整的语言翻译程序(如Cython或
f2c
)。由于目标是使维护代码一次。
它们通常做成1 off,t o完全按照他们的要求去做。
在许多方面,这是使用正则表达式/查找替换来移植代码的下一个版本。您可以说“工具辅助的移植”。
仅从例如
密切相关的是,如果您从某个来源生成代码,则不想再次访问。
例如。如果生成代码所需的动作是不可重复的,不一致的,或者执行它们的代价很高。
我现在正在研究两个项目:
DataDeps.jl和
DataDepsGenerators。 jl。
DataDeps.jl帮助用户下载数据(如标准ML数据集)。
为此,我们需要一个称为RegistrationBlock的代码。
这是一些代码,用于指定一些元数据,
例如从何处下载文件,校验和,以及一条向用户解释任何条款/条件/数据的许可状态的消息。
写这些块可能很烦人。
这些信息通常可以从托管数据的网站上的(结构化或非结构化)信息中获取。
因此,DataDepsGenerators.jl,
使用网络抓取工具来生成RegistrationBlockCode,用于某些托管网站很多数据。
它可能无法正确生成它们。因此,使用生成的代码的开发人员可以并且应该对其进行检查和更正。
他们希望确保它没有误删除许可信息。例如,
重要的是,用户使用DataDeps.jl的/ devs不需要安装或使用Webscraper来使用生成的RegistrationBlock代码。
(不需要下载和安装Web scraper可以节省大量时间。特别是对于CI运行)
一次生成源代码不是反模式。
通常无法用元编程代替。
评论
“报告”是一个英文单词,表示除“再次端口”外的其他含义。尝试“重新报告”以使该句子更清楚。 (由于无法进行建议的编辑,因此进行了评论。)
– Peter Cordes
17年11月30日在4:38
好习惯@PeterCordes我改写了。
–林登·怀特(Lyndon White)
17年11月30日在4:46
更快,但潜在的可维护性要差得多,这取决于生成的代码的可怕程度。从Fortran到C的时代很早(使用C编译器的人越来越多,所以人们会使用f2c + cc),但是生成的代码并不是C程序AFAIK的良好起点。
– Peter Cordes
17年11月30日在4:49
可能,可能不会。某些代码生成器生成不可维护的代码并不是代码生成器概念中的错误。尤其是,不需要捕获所有情况的手工工具通常可以编写出完美的代码。例如,如果90%的代码只是数组常量的列表,那么将这些数组构造函数一次性生成就可以非常轻松地完成,而且很省力。 (另一方面,Cython输出的C代码无法由人维护。因为它不是故意的。就像您回想f2c一样)
–林登·怀特(Lyndon White)
17年11月30日4:56
大表只是最简单,最简化的参数。对于转换for循环或条件可以说类似。的确,sed有很长的路要走,但有时它需要更多的表达能力。程序逻辑和数据之间的界线通常很好。有时,区分没有用。 JSON是(/曾经)只是javascript对象构造函数代码。在我的示例中,我还生成了对象构造函数代码(是否是数据?也许(也许不是,因为有时它具有函数调用)。是否最好将其视为代码?是。)
–林登·怀特(Lyndon White)
17年11月30日在5:42
#19 楼
有几种使用代码生成的不同方式。它们可以分为三个主要组:生成不同语言的代码作为编译过程中某个步骤的输出。对于典型的编译器,这将是一种较低级的语言,但对于使用JavaScript进行编译的语言,则可能是另一种高级语言。
以源代码语言的形式生成或转换代码编译过程中的步骤。宏就是这样做的。
使用工具生成代码的方式与常规编译过程不同。此代码的输出是与常规源代码一起作为文件存在并与之一起编译的代码。例如,可能从数据库模式自动生成ORM的实体类,或者可能从接口规范(例如SOAP的WSDL文件)生成数据传输对象和服务接口。
我猜您在谈论第三种生成的代码,因为这是最有争议的形式。在前两种形式中,生成的代码是一个中间步骤,它与源代码非常清晰地分开。但是在第三种形式中,源代码和生成的代码之间没有形式上的分隔,只是生成的代码可能带有注释,即“请勿编辑此代码”。仍然存在开发人员编辑生成的代码的风险,这确实很丑陋。从编译器的角度来看,生成的代码是源代码。
但是,这种形式的生成代码在静态类型的语言中可能确实有用。例如,当与ORM实体集成时,为数据库表使用强类型的包装器真的很有用。当然,您可以在运行时动态处理集成,但是会丢失类型安全性和工具支持(代码完成)。静态类型语言的一个主要优点是在编写类型时(而不只是在运行时)支持类型系统。 (相反,这种类型的代码生成在动态类型化的语言中不是很普遍,因为与运行时转换相比,在这种语言中它没有任何好处。)
一个代码生成器,那么为什么不使该对象成为可以接收所需
参数并执行“将要生成的”代码
正确执行的正确功能的函数呢? br />
因为类型安全和代码完成是您在编译时(以及在IDE中编写代码时)想要的功能,但是常规函数仅在运行时执行。
但是,可能有一个中间立场:F#支持类型提供程序的概念,它基本上是在编译时以编程方式生成的强类型接口。此概念可能会替代代码生成的许多用途,并提供更清晰的关注点分离。
#20 楼
源代码生成绝对确实意味着生成的代码是数据。但这是一流的数据,程序其余部分可以操纵的数据。我知道,集成到源代码中的两种最常见的数据类型是有关窗口的图形信息(数字以及各种控件的位置)和ORM。在这两种情况下,通过代码生成进行集成都使操作数据变得更加容易,因为您不必经过额外的“特殊”步骤即可使用它们。
在使用原始(1984)Mac时,使用资源编辑器创建对话框和窗口定义,该编辑器将数据保留为二进制格式。与在“二进制格式”中使用Pascal相比,在应用程序中使用这些资源要困难得多。
因此,不,源代码生成不是反模式,它允许生成数据应用程序的一部分,使其更易于使用。
#21 楼
当代码产生的成本超过完成的成本时,它就是一种反模式。这种情况发生在从A到B生成的地方,其中A与B几乎是相同的语言,但是有一些小的扩展,只需用A编码即可完成,而无需花费所有定制工具为A到B构建阶段。这种折衷更加不利于在没有元编程功能(结构宏)的语言中生成代码,这是因为通过外部文本处理来实现元编程的复杂性和不足之处。
权衡取舍还可能与使用量有关。语言A可能与语言B完全不同,但是整个项目及其自定义代码生成器仅在一个或两个小地方使用A,因此总的复杂程度(A的小部分加上A-> B代码生成器,
基本上,如果我们致力于代码生成,我们可能应该“变大或变家”:使其具有实质性的语义,并且经常使用它,或者不要打扰。
评论
为什么删除“何时Bjarne Stroustrup首次实现C ++ ...”段落?我认为这很有趣。
– Utku
17年11月30日在18:22
@Utku其他答案从编译整个复杂的语言的角度来解决,该语言将项目的其余部分全部编写成该语言。我认为它不能代表大多数所谓的“代码生成”。
–卡兹
17年12月1日在16:36
#22 楼
我看不清楚这句话(我确实看过一两个答案,但似乎不太清楚)生成代码(正如您所说,好像是数据一样) )没问题-这是一种将编译器用于次要目的的方法。
编辑生成的代码是您将遇到的最阴险,邪恶,恐怖的反模式之一。不要这样做。
最好,编辑生成的代码会将大量不良代码拖入您的项目中(整个代码集现在是真正的源代码-不再是数据)。最糟糕的是,拉入程序的代码是高度冗余的,名称不正确的垃圾,几乎完全无法维护。
我想第三类是您使用过一次的代码(gui生成器?),然后进行编辑以帮助您获得代码开始/学习。这只是一个小问题,它可能是一个很好的启动方式,但是您的GUI生成器将针对使用“可生成的”代码,而这对于您作为程序员来说并不是一个很好的起点。此外,您可能会试图再次将其用于第二个GUI,这意味着将冗余的SOURCE代码提取到您的系统中。如果没有,我将其称为最糟糕的反模式之一。
#23 楼
代码和数据都为:信息。数据完全按照您需要的形式(和值)提供信息。代码也是信息,但采用间接或中间形式。从本质上讲,代码也是一种数据形式。
更具体地讲,代码是使机器从人员自身处理信息的工作中解放出来的信息。
从信息处理中卸载人员是最重要的动机。中间步骤是可以接受的,只要它们使生活变得轻松即可。这就是存在中间信息映射工具的原因。像代码生成器,编译器,编译器等。
为什么生成源代码?为什么不使它成为可以
接受参数并对其执行操作的函数?只要函数按承诺运行,您是否会在内部生成源代码?
#24 楼
如果可以生成某些东西,那么那件事就是数据,而不是代码。
由于您稍后在代码中规定的是数据,因此您的主张简化为“如果某些东西可以被生成,那么那不是代码。”那么,您会说C编译器生成的汇编代码不是代码吗?如果碰巧与我手工编写的汇编代码完全吻合怎么办?如果愿意,欢迎您到那里去,但我不会和您一起去。
让我们从定义“代码”开始。无需太过技术,就本次讨论而言,一个很好的定义是“用于执行计算的机器可操作指令。”
鉴于此,不是整个概念
好吧,您的开始主张是不能生成代码,但我拒绝这种主张。如果您接受我对“代码”的定义,那么一般而言代码生成就不会出现概念上的问题。
,也就是说,如果有某种东西的代码生成器,那为什么不做可以接收所需参数并执行“将生成的”代码将要执行的正确操作的适当函数吗?
那是一个完全不同的问题,关于使用代码生成,而不是代码的本质。您正在提出一种替代方案,而不是编写或使用代码生成器,而是编写一种直接计算结果的函数。但是用什么语言呢?任何人直接用机器代码直接编写代码的日子已经一去不复返了,如果您用任何其他语言编写代码,那么您将依靠编译器和/或汇编程序形式的代码生成器来生成实际运行的程序。 >
那么,为什么您更喜欢用Java或C或Lisp或其他语言编写?甚至汇编程序?我断言这至少是部分原因是因为这些语言为数据和操作提供了抽象,从而使表达想要执行的计算的细节变得更加容易。
对于大多数更高级别的情况也是如此。代码生成器。原型情况可能是扫描器和解析器生成器,例如
lex
和yacc
。是的,您可以直接使用C或您选择的其他某种编程语言(甚至包括原始机器代码)编写扫描器和解析器,有时也可以。但是对于任何非常复杂的问题,使用高级特殊用途语言(例如lex或yacc)会使手写代码更易于编写,阅读和维护。通常也要小得多。您还应该考虑“代码生成器”的确切含义。我认为C预处理和C ++模板的实例化是代码生成中的练习。你反对这些吗?如果不是,那么我认为您需要进行一些心理上的操练,以合理地接受这些内容,但拒绝其他形式的代码生成。
如果出于性能方面的考虑而这样做,那听起来像是编译器的缺点。
为什么?您基本上认为,应该有一个通用程序,用户向其提供数据,有些程序被归类为“指令”,而另一些程序被归类为“输入”,然后该程序将继续执行计算并发出更多的数据,我们称之为“输出”。 (从某种角度来看,可以将这样的通用程序称为“操作系统”。)但是为什么您认为编译器在优化通用程序方面应该与在优化更专业的程序方面同样有效。程序?这两个程序具有不同的特性和不同的功能。
如果要完成两种语言之间的桥梁,那么这听起来像是缺少接口库。
您说的好像拥有一个通用的某种程度的接口库是一件好事。也许可以,但是在许多情况下,这样的库很大,难以编写和维护,甚至可能很慢。如果实际上没有这种野兽可以解决当前的特定问题,那么当代码生成方法可以更快,更轻松地解决问题时,您是谁坚持要创建一个这样的野兽呢?
我在这里丢失了什么吗?
我想了几件事。
我知道代码也是数据。我不明白的是,为什么要生成源代码?为什么不让它成为一个可以接受参数并对其执行操作的函数?
代码生成器将用一种语言编写的代码转换为另一种通常是较低级语言的代码。那么,您在问的是,人们为什么要使用多种语言编写程序,特别是为什么他们可能想要混合主观不同级别的语言。
但是我已经谈到了。一个人为特定任务选择一种语言,部分是基于该任务的清晰度和表现力。由于较小的代码平均具有较少的错误,并且更易于维护,因此至少在大规模工作中也倾向于使用高级语言。但是复杂的程序涉及许多任务,通常可以用一种语言更有效地解决其中的一些任务,而用另一种语言更有效或更简洁地解决其他任务。使用正确的工具完成工作有时意味着要使用代码生成。
#25 楼
在您的评论上下文中回答问题:编译器的职责是接受以人类可读形式编写的代码并将其转换为机器可读形式。因此,如果编译器无法创建有效的代码,则编译器将无法正确执行其工作。是吗?
编译器永远不会为您的任务而优化。这样做的原因很简单:它已经过优化,可以执行许多任务。这是许多人用于完成许多不同任务的通用工具。一旦知道了您的任务是什么,就可以以特定于域的方式处理代码,从而权衡编译器无法实现的功能。
例如,我开发的软件可以让分析师需要写一些代码。他们可以用C ++编写算法,并添加他们依赖的所有边界检查和提示技巧,但这需要对代码的内部工作有很多了解。他们宁愿写一些简单的东西,而让我抛出一个算法来生成最终的C ++代码。然后,我可以做一些奇特的技巧来最大化性能,例如静态分析,这是我永远都不会期望分析师忍受的。代码生成使他们能够以特定于域的方式进行编写,这使他们比任何通用工具都更容易将产品推向市场。我还完成了另一项工作,任务是“不生成代码”。我们仍然希望使使用该软件的人员的工作变得轻松,因此我们使用了大量的模板元编程来使编译器即时生成代码。因此,我只需要通用C ++语言即可完成工作。
但是,有一个陷阱。保证错误可读是非常困难的。如果您曾经使用过模板元编程代码,那么您会知道,一个无辜的错误会产生一个错误,该错误需要100行无法理解的类名和模板参数来理解出了什么问题。这种效果如此明显,以至于建议的语法错误调试过程是“滚动错误日志,直到看到自己的文件第一次出现错误为止。转到该行,斜视一下,直到意识到自己要做什么为止。做错了。”
如果我们使用代码生成,我们本来可以拥有更强大的错误处理功能,并且具有人类可读的错误。 C'est la vie。
#26 楼
处理器指令集从根本上来说是必须的,但是编程语言可以是声明性的。运行以声明性语言编写的程序不可避免地需要某种类型的代码生成。如本答案及其他中所述,以人类可读的语言生成源代码的主要原因是要利用编译器执行的复杂优化。#27 楼
一点也不代码生成最广泛使用的一种是Google的协议缓冲区。 Protobufs是一个序列化工具,微服务(或序列化的任何其他应用程序)可以用来相互通信。 Google工程师随着时间的推移发现,手工编写序列化协议非常昂贵,容易出错,并且很难扩展。为了解决这个问题,他们创建了一个库来允许您描述消息,然后简单地生成读取和写入消息的代码。
鉴于此,并不是整个源代码生成思想误解?也就是说,如果有某个东西的代码生成器,那么为什么不将该东西变成一个可以接收所需参数并执行“将生成的”代码本应执行的正确操作的适当函数呢?
Protobuf规范的Python实现实际上做了这种事情,但这是完全可行的唯一原因是因为Python具有大量的运行时元编程功能,而这些功能在静态编译语言中是不可能的。例如,Python允许您在运行时创建类型,这些类型由于使用鸭子输入而易于使用。
对于静态编译语言,您无法在运行时创建类型,因此通常更容易如果类型是提前生成的,则可以使用它,并且更灵活,而不是强迫您使用更通用的接口(例如JSON)
如果出于性能原因执行该操作,那么听起来编译器的缺点。
不可能编写出足够聪明的编译器来将已解释的模板转换为经过微调的机器代码(通常,这实际上是您所要的)感谢赖斯定理。您需要编写一个专门的代码生成器来完成此操作。
如果要完成两种语言之间的桥梁,这听起来像缺少接口库。对于Protobuf,它提供的序列化是接口库。
您能说代码生成是一种限制编程语言的变通方法吗?绝对。当您的语言支持反射时,序列化会容易得多。但是,即使这样做,生成符合整个组织使用的特定协议的代码也是很有用的,就像Protobufs的用例一样。
除此之外,我还发现了代码生成在使用C和C ++时非常有用。两者都对元编程的支持很差,因此我发现用Python脚本生成C / C ++代码要简单得多。尽管C ++模板可以实现许多功能,但这样做的代价是大大增加了编译时间和隐秘的错误消息。编写代码生成器既可以更快地编译,也更容易理解。
评论
与代码生成相关的术语是元编程zh.wikipedia.org/wiki/Code_as_data,Lisp,FP,脚本编写,元编程,冯·诺依曼/修改后的哈佛建筑等。 tl; dr区分“源代码”与“输出代码”,“代码”与“数据”等是为了简化事情。他们永远不要教条。
@Utku,进行代码生成的更好原因通常与希望提供比您的当前语言无法表达的高级描述有关。编译器是否可以创建高效的代码实际上与它没有任何关系。考虑解析器生成器-与用C手写的等效项相比,由flex生成的词法分析器或由bison生成的解析器几乎肯定会更可预测,更正确并且通常更快地执行。并且使用更少的代码构建(因此也减少了维护工作)。
也许您来自没有很多功能要素的语言,但是在许多语言中,功能是一流的-您可以传递它们,因此在这些类型的语言中,代码就是数据,您可以像这样对待它。
功能语言代码中的@Restioson不是数据。一流的功能恰好意味着:功能就是数据。并不一定是特别好的数据:您不必对它们进行一点突变(例如,将函数中的所有加法突变为减法)。代码是同源语言的数据。 (大多数谐音语言都具有一流的功能。但事实并非如此。)。