6

我继承了一个 Visual Studio 解决方案,其中包含项目之间的大量循环引用。

有没有一种情况可以远程接受?

只是想证实我怀疑这个应用程序设计得很糟糕。提前致谢。

4

4 回答 4

8

我曾经读过一个专栏,他们比较了三种模型:意大利面条模型、千层面模型和馄饨模型。

Spaghetti模型中,所有的代码都是相互联系的,没有清晰的结构。这太可怕了,我们可能都同意这一点。

Lasagna模型中,代码被划分为不同的层,只有高层才能访问低层,从不反过来。

馄饨模型中,代码被分组在更小的模块中。每个模块只公开需要公开的内容,但每个模块仍然可以访问其他所有模块。

大约 10 年前,在我看来,馄饨模型比千层面模型更好。毕竟,在 Java 中,您也有可以轻松相互调用的 Java 模块(我的印象是所有不同的 Java 模块之间没有真正的结构)。对我来说,Lasagna 模型似乎是非面向对象的旧代码的结果,而 Ravioli 模型似乎更现代,更面向对象。

如今,我倾向于回到千层面模型,但内置了馄饨模型。这是:

  • 该应用程序是使用不同的层构建的,例如在千层面模型中
  • 但是在这些层中,代码仍然被分割在可以相互访问的不同模块之间,就像在馄饨模型中一样。

某些循环引用可能很难或不可能删除。下面是一个示例:假设您的应用程序中有一个 FileWriter 类和一个 Debug 类。Debug 类将需要 FileWriter 类,因为它需要编写带有调试信息的文件。另一方面,FileWriter 类可能还想使用 Debug 类。

注意这个例子中的循环引用可能已经导致问题(FileWriter类可以在写一行的时候调用Debug类,但是Debug类使用FileWriter类来写调试信息,结果:栈溢出)。

在这种情况下,通过不使用 Debug 类中的 FileWriter 类,而是使用本机 iostream(如果您使用 C++ 进行开发),可以轻松解决问题。在其他情况下,问题可能更难解决。

于 2010-08-27T16:22:25.077 回答
2

好的软件是分层设计的,它们之间有明确的界限。即:如果你有一个层,你需要能够清楚地阐明它的作用,它为什么存在,以及它依赖于什么。循环使这难以实现,通常应该被删除。(微软在 Windows 7 中花费了很多精力来改善 Windows 的分层,通过删除循环。)

只是想证实我怀疑这个应用程序设计得很糟糕。

这肯定会支持该理论,但是 IMO,您需要的不仅仅是几个循环引用来得出这个结论。

回答您最初的问题:是的,循环引用有时会有所帮助。相互递归函数就是这类事情的一个很好的例子。但是......这是一个安全地隐藏在模块中的循环引用。对于模块间的依赖关系,循环依赖通常意味着您的代码没有正确地跨模块拆分,这可能需要进行一些重大的重构来修复。(包括添加新类型的抽象来弥补差距等)

于 2010-08-27T16:02:37.813 回答
2

具体来说,我建议使用 NDepend 来检测和避免依赖循环

替代文字

文章摘录(我写的):控制组件依赖以获得干净的架构

组件之间的依赖循环导致了通常所说的意大利面条代码或纠结代码。如果组件 A 依赖 B 依赖 C 又依赖 A,则组件 A 不能独立于 B 和 C 进行开发和测试。 A、B 和 C 形成一个不可分割的单元,一种超级组件。由于规模不经济现象,这个超级组件的成本比 A、B 和 C 的成本总和要高(在软件估算:史蒂夫·麦康奈尔的《揭秘黑色艺术》中有详细记载)。基本上,这认为开发一段不可分割的代码的成本呈指数级增长。

这表明开发和维护 1,000 LOC(代码行)的成本可能是开发和维护 500 LOC 的三到四倍,除非它可以分成两个独立的块,每块 500 LOC。因此,与描述无法维护的纠结代码的意大利面条进行比较。为了使架构合理化,必须确保组件之间没有依赖循环,还要检查每个组件的大小是否可接受(500 到 1000 LOC)。

于 2010-08-29T19:11:15.210 回答
1

循环项目引用是糟糕设计的标志,应尽可能删除。

我能想到的保留循环引用的唯一理由是向后兼容问题。即使那样,它似乎也可以通过使用类型转发器和另一个程序集来修复

于 2010-08-27T15:57:02.540 回答