如果Java是一种通用语言,而构建程序可以用Java语言来描述,那么为什么这不是编写构建文件的最佳方法,而是使用Ant,Maven和Gradle之类的工具呢?那不是更直接,而且不需要学习另一种编程语言吗?(顺便说一句-这个问题也可以应用于其他语言,例如C#)
如果Java是一种通用语言,而构建程序可以用Java语言来描述,那么为什么这不是编写构建文件的最佳方法,而是使用Ant,Maven和Gradle之类的工具呢?那不是更直接,而且不需要学习另一种编程语言吗?(顺便说一句-这个问题也可以应用于其他语言,例如C#)
Answers:
细度
通用语言通常太冗长。如果我必须用Java管理构建,那么野兽的大小会让我非常沮丧。但是,使用Java编写的DSL可以很容易地对其进行管理。在某种程度上,这就是您可以看到Gradle(对于Groovy)和Buildr(对于Ruby)的方法。
困难
通用语言很难。好吧,我认为编程并不困难,任何人都不会接受,但是重点是:您的构建工程师不一定是您的程序员!
目的
这或多或少是困难和冗长的交集。语言是为特定目的而设计的,在这里我们谈论的是非常具体的东西。那么,为什么你需要或想要使用一般用途的语言?对于构建,您需要:
您实际上并不需要更多。
当您的构建系统对于您要使用的一种特殊用例而言似乎不够灵活时,肯定会很烦人,但对于大多数人而言,它可能比替代用例好得多。
这就是为什么在使用声明式构建系统而不是大多数可编程一次的人之间存在两极分化的原因:我猜开发人员可能会自然而然地寻求突破性的方法。
另一个相关的问题是:我们真的需要构建工具吗?它们是否存在于所有语言中,这不是事实,它们填补了最初不应该存在的空白的迹象吗?
某些语言不一定需要构建工具。例如,大多数脚本语言不需要一种,并且可以在加载时解析。或采用Go,后者的编译器将为您处理所有事情,这是一个很好的对策:如果像gcc这样的编译器突然不需要一堆标志,链接器和makefile来使所有内容都消失了怎么办?或者,如果javac不需要build.xml或pom.xml来告诉他该怎么办?依赖关系管理不应该是语言本身工具的一部分,因为依赖关系是最终程序的一部分吗?
对于用户(构建者)来说,这无疑似乎是一种更为简单的方法。尽管可能会说他们只是在幕后进行,并且剥夺了您的选择和机会来影响构建过程(但是您希望这样的工具可以允许编译器扩展和类似的东西)。另外,我们过去常常将工具和语言看作是两个独立的事物,因此他突然将它们如此紧密地结合在一起似乎并不纯洁。
我认为您用于构建程序的语言不是问题。重要的是您用于编程的语言,其核心平台和工具,而我们在此方面仍在取得进展。
就个人而言,我使用过make / gmake / autotools / pmk并为C感到满意,我从Java开始,那时我们只有make,然后是ant,现在我通常更喜欢Maven,而不是所有其他选择。尽管我可以在gradle,builder和其他工具中看到价值,但是到目前为止,我喜欢maven的盛行,直到发生更重要的转变为止。另外,我喜欢它很严格,但是如果需要的话,仍然使您无法解决它。这不容易是一件好事。
这是一个构建工具。只要学会拥抱它,就不要与它抗争。这是一场失败的战斗。或至少非常长的一个。
Java
是一种必要的语言Ant
,Maven
等等都是陈述性的语言:
我们可以将差异定义如下:
- 命令式编程:告诉“机器” 如何做某事,结果您想发生的事情就会发生。
- 声明式编程:说的是“机” 有什么你想发生,并让计算机计算出如何做到这一点。1个
构建语言告诉建设者什么应该做的,从那里应该采取等的引擎,它运行的版本(这是写在命令式语言,像@ElliottFrisch注意到),阅读这些说明,并满足他们。
在构建场景中,声明性语言似乎更合适,因为构建任务通常在整个过程中都是相同的,并且与正式的代码形式相比,以这种形式被认为更具可维护性和可读性。
如果查看典型构建系统的功能,则会发现:
如果您打算使用某种语言(Java / C#/ Python / etc)编写一系列构建文件,那么大约在第三或第四次迭代之前,您就可以(a)将大多数数据和外部命令作为数据保留在某些东西中例如XML(b)用您喜欢的语言编写“构建引擎”。
您还会发现将XML中的某些数据视为一种解释语言,以触发构建引擎中的各种功能很有用。您还可以在数据中解释一些宏或执行字符串替换。
换句话说,您将完成Make,Ant,Rake或MsBuild。现在,一种常用的命令式语言通常以XML表示其性能良好,并使用数据结构来描述您要执行的操作。
在这些情况下,有许多因素不利于使用Java / C ++ / C#。
首先,您必须先编译构建脚本,然后才能运行它来构建应用程序。您将如何指定构建构建脚本所需的任何软件包,标志,编译器版本,工具路径?当然,您可以想出一种解决方法,但是拥有一种不需要该构建步骤的语言(例如python)或您的构建工具本身可以理解的语言则要简单得多。
其次,构建文件的数据量很大,而Java / C ++ / C#更适合编写代码和算法。Java和朋友对您要存储的所有数据没有非常简洁的表示。
第三,Java和朋友们需要大量的样板才能有效。构建文件必须位于包含所有其导入的类中的方法内部。使用脚本语言或自定义语言时,您可以避免所有样板操作,而自己拥有构建详细信息。
确实!为什么不使用功能强大且富有表现力的产品语言来解决比人们通常(最初)想的还要复杂的问题?尤其是面对问题的人已经可以使用这种语言。(构建是程序员自己的问题,最好由程序员解决。)
几年前,我也问自己这个问题,并决定Java是定义内部版本的好语言,尤其是对于Java项目。结果,我开始对此做一些事情。
免责声明:在这个答案中,我正在推广iwant,这是我正在开发的构建系统。但这毕竟是一个经过深思熟虑的讨论,因此我确定是可以的。
我不会详细说明Java的好处(功能和表现力),也不会特别介绍。如果您有兴趣,可以在iwant页面上阅读更多内容。
相反,我将考虑为什么Java(以及其他GPL)如此容易被驳回为不适合构建。这里的许多答案和评论都是这种思考的很好例子。让我们考虑一些典型的参数:
他们可能会说:“ Java是必须的,但最好以声明性的方式定义构建。”
真正。但是,当使用一种语言作为内部DSL的主要语言时,真正重要的是其语法。甚至像Java这样的命令性语言也可以被欺骗为声明性的。如果看起来和感觉上是声明性的,则(出于实际目的)是声明性的。例如:
JacocoTargetsOfJavaModules.with()
.jacocoWithDeps(jacoco(), modules.asmAll.mainArtifact())
.antJars(TestedIwantDependencies.antJar(),
TestedIwantDependencies.antLauncherJar())
.modules(interestingModules).end().jacocoReport(name)
实际上,将其与一些假定的声明性构建系统进行比较,该构建系统会将其用户暴露于“测试”或“编译”等命令性动词。上面的声明仅包含名词,不包含动词。编译和测试是iwant隐式处理的任务,目的是向用户授予他/她想要的名词。这不是语言。这就是您的使用方式。
“ Java很冗长”
是的,很多Java代码都很冗长。但是,这又不是语言,而是您的使用方式。如果实现是冗长的,只需将其封装在漂亮的抽象后面即可。许多GPL为此提供了足够的机制。
试想一下上面用XML编写的Java代码段。用尖括号代替括号,然后将其移动。然后将每个关键字复制为结束标记!Java作为一种语法是不是冗长。
(我知道,与XML进行比较就像从婴儿身上拿糖果一样,但是很多构建恰好是用XML定义的。)
“您必须编译您的构建脚本”
这是有道理的。尽管如此,这只是一个需要解决的小技术问题。我可以通过使用beanshell或其他解释器解决它。相反,我通过将其视为另一个构建问题并使用编译和运行简单Java引导程序的简单shell或ant脚本进行引导来解决它。
“ Java有样板”
真正。您必须导入类,必须提及“ public”,“ class”等。简单的外部DSL在这里赢得了最初的轻松胜利。
如果您的项目如此琐碎,以至于这个样板很重要,那么恭喜您。您的问题并不难,而且解决的方式也无关紧要。
但是许多项目所需要的不仅仅是编译,覆盖报告和打包。如果Java样板可以解决客户的问题,那么为什么不构建问题呢?为什么只为别人的孩子做鞋?
我认为没有其他答案可以解决的一件事是,这些构建语言的局限性和透明性是使它们有用的很大一部分。让我们以Maven为例。是的,它执行构建,但它也定义了依赖关系。这使得Maven构建可以下拉那些依赖项并查看它们的依赖项,依此类推。
考虑一下这是否直接用Java完成。构建工具将看到存在依赖性。然后,需要拉下其他Java应用程序并执行它,以确定其依赖项是什么。但是对于Maven,它只是查看依赖项声明。Maven构建文件是透明的。一种图灵完整的语言本质上是非透明的,因此在某些目的(例如该目的)方面不如后者。