Subversion外部引用是一种反模式吗?

70

Subversion使用外部功能可以嵌套其他仓库的工作副本,使得在项目中方便地对第三方库软件进行版本控制。

虽然这些看起来非常适合库的重用和供应商软件的版本控制,但也不是没有反对者:

请不要使用Subversion外部(或其他工具中类似的功能),它们是反模式,因此是不必要的

使用外部是否存在隐含风险?请解释为什么它们被认为是反模式。


还可以查看这个答案:https://dev59.com/1XVC5IYBdhLWcg3wlyMo#248367。 - DuckMaestro
我曾经看到过比这个更好的问题被管理员标记为不适合在Stack Overflow论坛上发布,因为可能的答案具有主观性质。令人感到奇怪的是,当一些情况下后续的回答被禁止时,管理员的标记却是如此不一致。 - shawn1874
7个回答

71

我是这个问题中引用的那句话的作者,它来自前一个答案

Jason之所以怀疑像我这样简短的陈述,并要求解释,是正确的。当然,如果我在那个答案中彻底解释了一切,我需要写一本书。

Mike也正确指出,类似于svn:external特性的问题之一是,目标源中的更改可能会破坏您自己的源,特别是如果该目标源位于您不拥有的存储库中。

进一步解释我的评论,首先让我说,有“安全”的方法使用svn:external类似的特性,就像使用任何其他工具或特性一样。但是,我将其称为反模式,因为该功能更容易被误用。根据我的经验,它总是被误用的,我发现自己很少以安全的方式使用它,也不太可能推荐这种用法。请进一步注意,我并不是贬低Subversion团队 - 我喜欢Subversion,尽管我计划转移到Bazaar。

这个特性的主要问题在于,它鼓励并且通常用于直接链接一个构建(“项目”)的源到另一个构建的源,或链接项目到其所依赖的二进制文件(DLL,JAR等)。这两种用法都不明智,并构成了反模式。

正如我在另一个答案中所说,我认为软件构建的一个基本原则是每个项目都构建一个二进制或主要交付成果物。这可以被视为将关注点分离的原则应用于构建过程。特别是当一个项目直接引用另一个项目的源代码时,这一点尤其正确,这也违反了封装的原则。另一种这种违规的形式是试图通过递归调用子构建来创建构建层次结构以构建整个系统或子系统。Maven强烈鼓励/强制执行这种行为,这也是我不推荐使用它的众多原因之一。
最后,我发现有各种实际问题使得这个特性不太理想。首先,svn:external 有一些有趣的行为特征(但细节暂时逃脱了我)。另外,我总是发现我需要这些依赖项明确地显示在我的项目(构建过程)中,而不是作为某些源代码控制元数据深藏不露。
所以,如何“安全”地使用此功能?我认为只有一个人暂时使用它是安全的,比如作为“配置”工作环境的一种方式。我可以看到程序员可能会在仓库中创建他们自己的文件夹(或每个程序员都有一个文件夹),在这些文件夹中,他们将配置svn:external链接到他们当前正在工作的仓库的各个其他部分。然后,检查该文件夹将创建所有当前项目的工作副本。当添加或完成一个项目时,可以调整svn:external定义并相应地更新工作副本。但是,我更喜欢不与特定源代码控制系统绑定的方法,例如使用调用检出的脚本来执行此操作。
记录上,在2008年夏天,我最近接触到这个问题是在一家咨询客户那里,他们在大规模使用svn:external——EVERYTHING都是交叉链接以产生单个主工作副本。他们基于这个主工作副本构建了Ant和Jython(用于WebLogic)构建脚本。净结果:没有任何东西可以独立构建,字面上有数十个子项目,但没有一个是安全的,可以单独进行检出/工作。因此,对这个系统的任何工作都需要首先检出/更新超过2 GB的文件(他们还将二进制文件放在仓库中)。完成任何事情都是徒劳无功的,我尝试了三个月后离开了(当时还存在许多反模式)。
编辑:详细说明递归构建-
多年来(尤其是过去的十年),我为财富500强公司和大型政府机构构建了庞大的系统,这些系统涉及许多子项目,这些子项目按目录层次结构排列,层数很深。我使用Microsoft Visual Studio项目/解决方案来组织基于.NET的系统,使用Ant或Maven 2来组织基于Java的系统,以及开始使用distutils和setuptools(easyinstall)来组织基于Python的系统。这些系统还包括通常在Oracle或Microsoft SQL Server中的大型数据库。
我已经成功地设计了这些大型构建系统,使其易于使用和可重复性。我的设计标准是,新开发人员可以在第一天出现,获得一个新的工作站(可能直接从戴尔安装了一个典型的操作系统),获得一个简单的设置文档(通常只有一页的安装说明),并能够在半天或更短的时间内无需监督、无需协助就能完全设置工作站并从源代码构建整个系统。调用构建本身涉及打开命令 shell,切换到源树的根目录,并发出一个命令来构建 EVERYTHING。
尽管取得了成功,但构建如此庞大的构建系统需要极其谨慎和密切遵守坚实的设计原则,就像构建大规模的业务关键应用/系统一样。我发现一个关键部分是每个项目(生成单个工件/可交付成果物)必须有一个单独的构建脚本,该脚本必须具有明确定义的接口(命令来调用构建过程的各个部分),并且它必须与所有其他(子)项目分开。历史上,很容易构建整个系统,但很难/不可能构建只有一个部分。直到最近,我才学会仔细确保每个项目确实是独立的。
实际上,这意味着必须有至少两层构建脚本。最底层是项目构建脚本,用于生成每个可交付成果/工件。每个这样的脚本都位于其项目源树的根目录中(确实,此脚本定义了其项目源树),这些脚本不知道源代码控制,它们期望从命令行运行,相对于构建脚本引用项目中的所有内容,并基于一些可配置的设置(环境变量、配置文件等)引用其外部依赖项(工具或二进制工件,没有其他源项目)。
第二层构建脚本也旨在从命令行调用,但这些脚本知道源代码控制。实际上,这第二层通常是一个单独的脚本,它使用项目名称和版本被调用,然后将指定项目的源代码检出到新的临时目录(可能在命令行上指定),并调用其构建脚本。
可能需要更多的变化来适应持续集成服务器、多个平台和各种发布方案。
有时需要第三层脚本来调用第二层脚本(调用第一层)以构建整个项目集的特定子集。例如,每个开发人员可能会有自己的脚本,用于构建他们今天正在开发的项目。可能会有一个脚本来构建所有内容以生成主文档或计算指标。
无论如何,我发现试图将系统视为项目层次结构是不利的。它将项目彼此绑定,以便不能自由地单独构建它们,或在任意位置(持续集成服务器上的临时目录)或任意顺序(假设满足依赖项)中构建它们。通常,试图强制使用层次结构会破坏任何尝试的 IDE 集成。
最后,构建一个庞大的项目层次结构可能会导致性能问题。例如,在2007年春季,我尝试使用Ant构建了一个简单的源代码层次结构(Java加Oracle),但最终失败了,因为构建总是因Java OutOfMemoryException而中止。这是在一台2 GB RAM工作站上进行的,具有3.5 GB交换空间,我已经调整了JVM以便能够使用所有可用内存。该应用程序/系统在代码量方面相对较小,但递归构建调用最终耗尽了内存,无论我给它多少内存。当然,执行时间也非常长(通常在30-60分钟之前中止)。我知道如何进行优化,但最终我只是超出了工具的限制(在这种情况下是Java/Ant)。
所以,请为自己着想,将您的构建作为独立项目构建,然后将它们组合成完整的系统。保持轻盈和灵活。享受吧。
编辑:更多关于反模式的内容
严格来说,反模式是一种常见的解决方案,看起来可以解决问题,但实际上却不能,因为它要么留下了重要的空白,要么引入了其他问题(通常比原始问题更糟糕)。解决方案必然涉及一个或多个工具以及将它们应用于手头问题的技术。因此,将工具或工具的特定功能称为反模式是一种牵强附会,并且似乎人们正在检测并对其做出反应-这是公平的。
另一方面,由于在我们的行业中,似乎常见做法是专注于工具而不是技术,因此工具/功能得到了关注(StackOverflow上的问题调查似乎很容易说明这一点)。我的评论以及这个问题本身反映了那种做法。
然而,有时候似乎特别有理由进行这种扩展,比如在这种情况下。有些工具似乎会"引导"用户采用特定的应用技巧,甚至有人认为工具塑造思维(稍作改述)。正是基于这种精神,我建议svn:external是一种反模式。
更严格地说,反模式是设计一个构建解决方案,包括在源代码级别将项目绑定在一起,或隐含地对项目之间的依赖关系进行版本控制,或允许这些依赖关系隐含地发生变化,因为每个问题都会引发非常负面的后果。类似svn:external的特性的性质使得避免这些负面后果非常困难。
正确处理项目之间的依赖关系涉及到解决这些动态问题以及基本问题,而工具和技术会引导走向不同的路径。一个应该考虑的例子是Ivy,它以类似于Maven但没有许多缺点的方式帮助解决问题。我正在调查Ivy,结合Ant,作为我的短期Java构建问题解决方案。长期来看,我希望将核心概念和功能纳入一个开源工具中,以便实现多平台解决方案。

4
非常出色的回复,其中包含了一些非常有说服力的观点。特别是我非常喜欢将SOLID/OOP设计原则应用于构建过程中的想法。+1 - Matt Campbell
2
递归调用子构建。您能详细解释一下吗?我已经阅读了《递归Make的危害》,但我仍然不明白问题出在哪里,或者应该采取什么措施来避免它。 - KeyserSoze
8
共享库怎么处理?你如何提出优雅地处理存储库内共享资源的方法?例如,在单个存储库中有多个项目,所有项目都使用共同的代码。 - Clint Pachl
1
我对“环境变量”感到不满。我不会插入这样的黑客来避免我的存储库中的链接。你谈论了很多构建过程,但很少谈论组织存储库。我仍然没有被说服。 - Gusdor
3
SVN外部依赖在一个由约100人组成的公司中表现得非常完美,该公司拥有约50个小型软件模块,用于制作大约10个产品(每个产品由多个模块组成)。SVN externals也在一个由3人组成的初创公司中表现良好。初创公司没有时间发明自己的基于脚本的构建系统。答案很值得一读。 - Danijel
显示剩余3条评论

67
我认为这不是反模式。我在谷歌上进行了几次快速搜索,基本上什么也没有……没有人抱怨使用svn:externals是坏的或有害的。当然,你必须注意一些注意事项……并且这不是你应该随意向所有存储库中添加的东西……但就原始引用而言,那只是他个人(和主观)的意见。他从未真正讨论过svn:externals,只是将其谴责为反模式。这种没有任何支持或至少解释一个人如何得出陈述的概括性陈述总是可疑的。
话虽如此,使用外部资源确实存在一些问题。像Mike回答的那样,它们可以非常有用地指向已发布软件的稳定分支......特别是您已经控制的软件。我们在许多项目中内部使用它们来处理实用库等。我们有一个小团队来增强和处理实用程序库基础代码,但该基础代码跨许多项目共享。我们不希望各个团队只是检查实用程序项目代码,我们也不想处理数百万个分支,因此对于我们来说,svn:外部资源非常有效。对于有些人而言,它们可能不是答案。但是,我强烈反对"请不要使用..."这种说法,认为这些工具代表反模式。

21
我同意,被采纳的答案是垃圾。我读了整个帖子,没有看到任何一个具体的理由来支持他的观点。“例如,svn:external有一些有趣的行为特征(但细节暂时逃离了我的记忆)”,这是他提到的最接近的内容。 - Quick Joe Smith

19

使用 svn:externals 的主要风险是所引用的仓库可能会发生改变,并导致您的代码出现故障或引入安全漏洞。如果外部仓库也在您的控制之下,则这可能是可以接受的。

个人而言,我只使用 svn:externals 指向我拥有的仓库的“稳定”分支。


16
因此,将svn:externals链接指向已标记的发布版本是常见做法(或者应该是),甚至需要指定特定修订版本,而不仅仅是 /trunk@HEAD,否则会带来麻烦。 - Quick Joe Smith
1
好主意,快速乔。我想我会采纳它作为我的新政策。 - Mike
2
我不是一个喜欢阅读官方文档的人,但是对于像源代码控制这样复杂和重要的事情,我倾向于在使用前阅读我计划使用的功能的文档。Subversion 的文档建议:“您应该认真考虑在所有外部定义中使用显式修订号。” 除此之外,他们还提供了很多充分的理由。 - jpierson
2
指向主版本并不一定是坏事,而指向已标记的发布版本可能会使您的代码陈旧且缺少错误修复。指向标签或主版本应根据具体情况进行决策,这取决于您的项目要求。 - live-love

18

虽然这是一个旧的主题,但我想解决一个问题,那就是外部更改可能会破坏您的代码。正如以前指出的那样,这通常是由于外部属性的不正确使用所导致的。几乎在所有情况下,外部引用应该指向外部存储库URI中的特定修订版本号码。这可以确保除非您将其更改为指向不同的修订版本号码,否则外部将永远不会更改。

对于我们的某些内部库,在我们的最终用户项目中用作外部引用,我发现创建一个标记,其版本号为Major.Minor版本很有用,其中我们强制执行不会发生任何重大变化。使用四点版本控制方案(Major.Minor.BugFix.Build),我们允许标记随BugFix.Build更改而保持当前状态(再次强制执行不会发生重大变化)。这使我们可以使用无修订版本号的标记的外部引用。在出现重大或其他破坏性变化的情况下,将创建新标记。

外部引用本身并不是坏的,但这并不能阻止人们创建不良实现。只需要进行一些研究,通过一些文档阅读,就可以学习如何安全有效地使用它们。


纠正错误从未为时过晚。 :-) 很棒的回答。 - Clint Pachl
1
"在几乎所有情况下,外部引用应指向外部存储库URI中的特定修订号。如果您的代码(针对过时的外部引用)完全有效且通过了所有测试,那么您将遇到另一个问题,当您发现与您的代码相关联的提交时,它会破坏您的代码 - 这通常发生在最后期限。" - Lazy Badger

9
如果普通的外部引用是一种反模式,因为它可能会破坏你的仓库,那么带有显式版本的引用就不应该是反模式。
来自svn book的摘录:
外部定义是将本地目录映射到版本化资源的URL(可能还包括特定版本)的操作。
我认为这完全取决于你使用该功能的目的,它本身并不是反模式。

2
在这里,在这里!我无法相信所有关于外部变量的抱怨,却没有真正、理智的例子。 - Clint Pachl

9
在subversion externals中确实存在一些缺陷,但我们似乎已经相当成功地使用它们来包含当前项目所依赖的库(包括我们自己的和供应商的)。因此我不认为它们是“反模式”。对我来说,重要的用法点有:
  • 它们指向其他项目的特定修订版或标签(永远不是head)。
  • 它们被插入到当前项目的源代码等之外的位置(例如,在名为“support files”的子目录中)。
  • 它们仅引用其他项目的“接口”文件(例如include文件夹)和二进制库(即我们不会得到其他项目的全部源代码)。

我也很想知道这种安排的主要风险和更好的方法。


2
说“a是b”并不意味着除非你说明“为什么”,否则a就是b。
我认为subversion中外部引用的主要缺陷是,当您更新工作副本时,不能保证存储库存在。
Subversion外部引用可以被使用和滥用,而且它本身只是一个功能。它不能说是一种模式,也不是反模式。
我已经阅读了您引用的人的答案,我必须说我不同意。如果您的项目需要从存储库获取XYZ版本的文件,则外部subversion引用可以轻松提供给您。
是的,您可能会错误地使用它,没有明确指定所需引用的版本。那会给你带来问题吗?很可能!
这是一个反模式吗?好吧,这取决于情况。如果您遵循引用文本作者给出的链接,即此处,那么不是。某件事情可以被用来提供糟糕的解决方案,并不意味着整个方法都是反模式。如果这是规则,那么我会说编程语言基本上都是反模式,因为在每种编程语言中,您都可以做出糟糕的解决方案。

2
反模式是一种解决方案,它会带来更多问题,通常比它解决的问题还要多。使用像 svn:external 这样的依赖项功能就是这样一个例子。我很快就会尝试说明。 - Rob Williams
4
@RobWilliams - 我希望看到你描绘 svn:externals 作为反模式的插图。此外,我还想看到你如何将共享数据纳入版本控制系统,而不会引入重复的解决方案。 - Clint Pachl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接