有多少人还在用 Maven 构建项目?

108 天前
 diagnostics

最近用 maven 有些槽点(当然也可能是我自己对 maven 的学习也不够深入), 我既写 java 也写 scala, 有人在 scala 帖子诟病的 sbt 难用,相比之下 maven 才是真的“难用”。

我说的难用不是指难安装、简单打包,而是带一些场景:

1. xml 问题

xml 可读性某些层面太差了,过于繁琐,对版本管理,子模块管理全部揉杂在一起

2. 子模块管理问题

我们的项目组开发是一个 framework 的,也就是自身需要维护一大堆依赖,被别人引用的时候也会带上这些依赖的版本。

我们尝试过像 spring 那样用:

但是 IDEA 的识别不够好,总是出现某些子模块的版本找不到( IDEA 无法识别到这些子模块就是我维护的,而不是第三方库,而是反过来,总是去远仓库下载而不是项目的 target 上查找)

后面我们直接用 root 管理所有的 dependenices ,还是有问题,但是遇到问题只能本地 install 一下

3. CICD 编写问题

我们用的是 Gitlab CI/CD ,由于模块太多,一个 mvn test 运行太慢了,等个 CI 的功夫能干很多事情,但是我们尽可能希望快一点验证 commit ,继续做后续的测试,所以我们搞了按模块区分的单测:例如 coreTest 、serverTest 、extensionTest 等。。。

maven 可以用 mvn test -pl core -pl server 这样解决,但是部分 extension 模块是依赖 server 或者 core 的,就只能用 mvn test -pl extension -am also make 依赖,这样的后果就是跑 extension 的时候,把 core 的单测也跑了(这还加速啥,依赖多的项目,很大概率直接跑个完整的 test )。 这个问题,我们后面用 profiles 解决了,但是维护起来太鸡巴蛋疼,太繁琐了(重构的时候)

4. 构建 Task 诊断问题

我改造 CICD 的时候,希望能利用上缓存机制,多个 task 来加速,但是我发现 compile 的时候也会把 validate 阶段的 enforce 插件也给跑了... 问题是,我看了下好像没有命令来看执行 xx 的时候会同时执行什么,只能跑一下 xx 然后看。。。

结论

可能我用 maven 的姿势不太对,但是越深入就越感觉这玩意不适合复杂项目,就适合简单做个:

clean 、test 、package 、install 、deploy

怀念 sbt...(尝试过 gradle 、迁移看了半天,需要考虑点有点多,还没正式打算改过去,改过去也不知道好不好)

ps:写 Java 的人里可能有大神,但是只写 Java ,写久了真的会降智(不思考合理性,不愿意接受其他,是的,我说的是我自己)

9419 次点击
所在节点    Java
127 条回复
iseki
107 天前
@duanluan

> 2. 需要换个插件,description 、scm.url 等必填,不再支持快照版本。

破坏掉兼容性这一点太令人难以接受了,好在应该在未来相当一段时间旧的设施仍然可以使用。
iseki
107 天前
@zhenjiachen Gradle 主要是冷启动比较慢,很大程度上这还是 Kotlin 导致的(因为要编译 kts 脚本和 buildSrc ,Kotlin 编译慢的离谱),后续的话,任务缓存和 daemon 等等机制理论上是非常高效的
31415926535x
106 天前
问个问题,为什么这种多个模块的项目都要放到同一个仓库中并且由一个 idea 打开呢,maven 确实一般都是用在层级比较小的项目的构建,那为什么不拆分到多个仓库中,分开并行构建(或者用 git submodule ),因为每次改动不是所有的模块都是会修改的吧,而且即使有应该也是一步步来开发测试的

然后为什么 ut 会依赖其他模块的类呢,ut 不是一般都是测当前类的方法么,,如果是集成测试,是不是还是在一个统一的维护 case 的平台来操作呢,pipeline 的单测通过之后,触发集成测试的验证
iseki
106 天前
@31415926535x 多个版本库之间管理是额外的成本,git submodule 并不好用,特别是模块间关系紧密,连版本号都完全统一时。
blueswhisper
106 天前
@diagnostics 不至于。只是做框架的研发,对构建工具使用认知在这种水平,让我质疑写的框架代码的质量。
diagnostics
106 天前
@31415926535x 你能这么问,说明你没维护过结构复杂点的项目呗

以 spring 来说,
- aop 依赖 core 、bean
- bean 依赖 core
- context 依赖 aop 、bean 、core

你想单独跑 aop 的单测是不行的,一定会先去编译相应的依赖( core 、bean )两个方案:

1. 在单测前编译一轮,缓存起来
2. 用 maven 参数 --also-make 编译依赖

如果你用 2 ,不在 ci 搞文章,或者你本地想测试了,直接 mvn clean test -pl aop -am 的话,会把 core 、bean 的单测一起执行了,这是因为 maven 对依赖只有一个 artifact id 声明,没有其他依赖关系,以在 V2EX 被人吐槽的 sbt 为例,依赖可以是

.dependsOn(bean % "compile->compile;test->test", core)


上面的含义是,依赖 bean 的代码和测试,依赖 core 的代码。

maven 能做到吗?也可以:mvn clean compile -pl aop -am & mvn test -pl aop

我一直吐槽的是,maven 做起来复杂,而不是 maven 无法实现,但是楼里有些人冷嘲热讽,说白了这些人不愿意接受新鲜东西,老古董,我发现文化人大多都有这个问题,学多了就认为自己的体系是对的。
yusheng88
106 天前
竟然这么多人评论了, 我也不吐不快一下吧。

1 、你不是来讨论问题的,是来评判 maven 不好的
你用过其它语言跟构建工具,与你遇到 maven 问题有什么关系?
正常提问,应该是如何解决 maven 的 xx 问题吧

2 、你的标题跟内容有一毛钱关系吗?
别人进来一看,通篇都是吐槽 maven 不行,其它语言、构建工具多好啥,哪里会关注你真正问题
用个语言会降智,看你的描述确实是。
自己用起来不顺手,无法解决的就是复杂项目,也是搞笑。

3 、maven 问题描述不清晰
我之前待过基础框架开发组,没遇到过你说的问题
你的问题描述就跟实习生说的一样:我怎么连接数据库失败?

建议你直接在 github 弄个 demo ,你的描述问题能力太差了。
diagnostics
106 天前
@yusheng88 “Talk is cheap. Show me the code.”

https://github.com/d789a08a-66a8298fd305/maven-issue
diagnostics
106 天前
@yusheng88 你说的没错,我确实是来骂 maven 的,因为我遇到的问题,基本上都在 maven 不好解决,设计问题,我觉得 maven 太老了,吐槽一下。就算能解决我说的问题,也是很复杂的解决,这算一个好框架?

基础框架组遇到过,但是没遇到过这问题,emmmmm

其实我说的问题,不搞 cicd 并行化,也比较难遇到,只能说人和人的追求不一样,有些人写代码只是能跑就行,和基础框架组没什么关系。有些人会去思考,怎么跑得更好,怎么设计的更好。

我的同事,非组内的,学习积极吗?积极,各种书籍天天看,实际在工作里,代码能写多捞就写多捞。
diagnostics
106 天前
@blueswhisper https://github.com/d789a08a-66a8298fd305/maven-issue

“Talk is cheap. Show me the code.” 问题直接贴到 Github 了,欢迎您提 PR 给出一个 Maven 下优雅的解决方案
31415926535x
106 天前
@iseki 但是现在的问题不就是模块太多且在相互依赖时影响效率么,那应该拆分才对的,如果非常紧密是应该在一起并且统一管理的
submodule 好像确实是有很多坑,,不过应该可以用这种思想来开发,主要还是得维护好多个模块在 push 之后的触发的 ci 逻辑就行
31415926535x
106 天前
@diagnostics
为什么假定结构复杂的项目就是所谓的中间件、某某框架呢?

我的讨论点是既然一开始就提到了开发编译测试构建这个流程效率很低了,且明知 mvn 工具的羸弱,为什么不审视自己的项目是否结构合理而进行重构,以选择更好的工具链来加速流程(如果你是为了来讨论 出 一个解决这个场景问的话)

> 你想单独跑 aop 的单测是不行的,一定会先去编译相应的依赖( core 、bean )两个方案:

假定你说是要只关心 aop 这个模块,那可以认为其他有依赖的都是已经搞好的,那何不分出来多个仓库让 ci 并行编译其他的依赖项,这不是天然的缓存么,而本地只关心 aop 这个模块,重新导入编译好的依赖项不久可以测试 aop 模块了
而且我认为 mvn 的命令应该 i 是用来维护当前项目的(即应该是对根 pom 操作),而不应该从子模块中间来执行

而且都已经是大型项目了,难道从头到尾每次 future 都是一个人维护?一个仓库模式多个人公用一个开发分支来协同开发吗?

工具不好用,不应该立马选择更优的工具链路么。没有选择那不是说明没有到 “工具不好用”的瓶颈吗。这怎么还有
iseki
106 天前
@31415926535x 模块之间的紧密度,在切分仓库和单一模块之间还有一个层级,这个层级刚好使用 Maven 的多模块和 Gradle 的多 Project 能力处理。每一个“模块”是一个编译单元,有时候编译单元的切分是必须的。
一般来说一个稍微复杂点的可扩展功能就必须切分为至少两个模块,模型与实现,比如一个叫 xxx-api 另一个叫 xxx-core 之类的。这时,两个模块都会被存储到制品仓库(比如 Maven Central ),而扩展模块只需要依赖 xxx-api 即可。
iseki
106 天前
此外就是考虑到 Kotlin 这种编译比较缓慢的语言,我在实践中倾向于让每一个模块保持很小的体积,同时让依赖树尽量扁平,这样,IDE 更流畅,Gradle 自身的并发执行任务的能力也对我有很大帮助。
diagnostics
106 天前
@31415926535x #112

1. 我没看懂你提的用更好的工具链来加速这个事情,目前我们是 gitlab ci/cd ,即使我做了 cache 了提前 compile 的产物,再 test ,也没法增量编译,你可以去深入 maven 增量编译这个事情。

2. 项目结构合理性:这个我们考虑过,可以用接口把一些依赖耦合给解耦,例如 aop -> bean 的依赖,bean 可以搞个接口模块,这样即使 aop 测试需要一来 bean-interface, 但是也不会跑任何实现的逻辑,bean 可以单独跑。但问题是:不是所有模块都支持这样、以及工程会有点复杂。换句大白话说,这是你构建工具可以解决的事情。

3. 你说的先构建并行吧啦巴拉,这些,我大概理解你的意思。和 1 差不多,maven 不支持增量编译,所以应该需要先打包 deploy 到 nexus ,才能被其他模块拉到并更新。我们要做的事情就是 ci/cd 里面搞并行化。aop 、bean 本质上是同级模块,只不过有依赖关系,强行搞成两个 stage ,会遇到我说的,先 deploy 才行。

4. 是 feature 不是 future ,我感觉你工作经验不久?或者接触的项目比较单一(都是公司里面做单个微服务项目),你随便找个开源项目就知道开源 git 的工作流和公司里面的业务项目不太一样,简单来说分为两个:

- Trunk Based Development:基于 dev 、sit 、prd 这种分支开发,每个人提交到 dev ,提交合并需要 review
- Github flow/ Gitlab flow:拉出一个分支开发,再合并到 main 分支,根据发版还有不同的 release

这是 Git 用法的两个模式,和本文完全没关系,我没太理解你认为适合的 workflow 是那一种,目前我们是 gitlab flow
diagnostics
106 天前
@iseki #114 不知道你用的是不是 maven 、假如不搞 profiles ,那么多项目多插件,绝对让你的编译速度直线下降:

你可以看我帖子里的 github 仓库的分析有解释,maven 没有定义具体能依赖什么阶段,你执行 package 就会把之前的所有阶段一起跑了,包括你依赖的模块

多项目,多插件之后,执行速度是 :模块数量 * 插件数量

对于大型项目,spotless, findbugs, enforce, sortpom 等校验插件是平常的事, 如果内部还有其他定制化和功能插件,那速度更别说了
iseki
106 天前
@diagnostics 我只有一个迫不得已用 Maven 的项目用了 Maven ,那是因为目标平台用了 OSGi ,相关构建插件在 Gradle 中非常不成熟。我观测的也是,Maven 在构建和配置缓存的利用上非常不优化,我不确定 Maven 平台有没有提供这种能力。
Gradle 的一个大问题在于冷启动实在是太慢了,如果使用了 buildSrc (aka. convention precompiled plugin) 问题就更加严重,偏偏官方鼓励使用这个···
ikas
105 天前
看了你的 maven 仓库
在 maven3 中,如果不是直接操作 root 项目,那么就需要你指明依赖
在 maven4 中,maven 会自动查找 root 项目

关于无法指定依赖是依赖模块源码,还是仓库中的模块
我在前面说了,我们一般采用平级模块加各种 root 项目的方案,在 root 中控制 modules 否包含指定的 module 来实现源码依赖与否

在 root 项目中
<profiles>
<profile>
<id>with-1</id>
<modules>
<module>module1</module>
</modules>
</profile>
<profile>
<id>with-2</id>
<modules>
<module>module2</module>
</modules>
</profile>
</profiles>


那么首先你需要先保证 module1 已经安装到仓库(已本地仓库为例)
cd root
mvn install -P with-1

如果你只需要执行 module2 的 test
cd root
mvn test -P with-2
diagnostics
104 天前
@ikas 1. maven4 解决了依赖问题吗?不过我还没听过 maven4 , 我印象最新的版本还是 3.9 ,刚才去看了下好像还是 alpha 版本

2. 你说的那个在我的 PR 了提了,我的问题其实不是怎么解决,你这个方案,本质上还是 maven 没有特性,导致用户需要投机取巧的方法来实现
abcbuzhiming
104 天前
@diagnostics 我前面说了那么多,看来你没听进去。
maven 一点也不老,作为一个包管理框架,它工作的非常好。
构建不过是它使用插件实现的附加功能。在这方面,肯定不如天生是为构建工具而生的 grade 。
你的复杂度已经到达了 maven 使用插件无法满足你的地步,那就就应该换更强的工具,而不是在原地抱怨一个并不是专门为构建而生的工具

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1050791

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX