软件开发领域的很大一部分是使系统的复杂性尽可能地低。 但是复杂性到底是什么? 虽然确切的语义有很大不同,但取决于您询问的人,大多数人可能都认为这与系统中部件的数量及其交互有很大关系。
考虑太空中的大理石,即行星,月亮或恒星。 没有任何交互,这就像系统可能会变得无聊。 什么都没发生。 如果大理石移动,它会以完全相同的方式移动。 老实说,甚至没有办法确定它是否在移动。 笨蛋。
在系统中添加第二块大理石,让它们彼此吸引,就像地球和月亮一样。 现在,该系统更加有趣。 如果它们不太快,则这两个对象会彼此绕圈。 有点有趣。
现在添加第三个对象。 在一般情况下,事情变得如此有趣,以至于我们甚至无法预测会发生什么。 整个系统不仅变得复杂,而且变得混乱。 您现在有一个三体问题 。在一般情况下,此问题无法解决,即我们无法预测系统会发生什么。 但是有一些特殊情况。 尤其是其中两个对象彼此非常接近的情况(例如地球和月亮),而第三个对象相距太远,以至于两个第一个对象的行为就像一个对象。 在这种情况下,您可以用两个粒子系统来近似该系统。
但是,这与Java有什么关系? 这听起来更像物理学。
我认为软件开发在某些方面是相似的。 完整的应用程序是从整体上变得复杂的方式。 为了克服这种复杂性,我们将系统分为可以自己理解的部分(类),并隐藏了它们的内部复杂性,这样,当我们查看较大的图片时,不必担心代码中的每个代码行类,但仅将类作为一个实体。 实际上,这与物理学家对系统所做的非常相似。
但是,让我们看一下事物的规模。 软件的基本构建块是代码行。 为了控制复杂性,我们将在方法中一起工作的代码行捆绑在一起。 单个方法中有多少行代码会有所不同,但大约为10行代码。
接下来,将方法收集到类中。 一个类中有多少种方法? 通常按10种方法排序!
然后? 我们将100-10000个班级捆绑在一个罐中! 我希望我不是唯一认为某事不对劲的人。
我不确定从Jigsaw项目中会得到什么,但是目前Java仅提供软件包来捆绑类。 包并不是一个强大的抽象,但是它是我们唯一的抽象,因此我们最好使用它。
大多数团队确实使用软件包,但不是以非常结构化但临时的方式使用软件包。 结果类似于试图将月亮和太阳视为系统的一部分,而将地球视为另一部分。 结果可能有效,但可能与托勒密的行星模型一样直观。 取而代之的是,根据标准确定如何区分包装。 我个人称它们为切片,是受Oliver Gierke的一篇文章的启发。 按重要性顺序排列的可能切片为:
- 该类最终应位于的可部署jar文件
- 类所属的用例/功能/业务模型的一部分
- 类所属的技术层
结果生成的软件包将如下所示:<domain>。<deployable>。<domain part>。<layer>
决定去哪儿上课应该很容易。 并且即使您不使用技术层分隔,它也应将包装保持在合理的尺寸。
但是,您从中得到什么呢? 找到类比较容易,但是仅此而已。 您还需要一个规则来使它真正值得: 不得有循环依赖项!
这意味着,如果包A中的类引用了包B中的类,则B中的任何类都不能引用A。如果引用是通过多个其他包间接引用的,则这也适用。 但这还不够。 切片也应该是无周期的,因此,如果域部分X引用了其他域部分Y,则反向依赖性一定不存在!
实际上,这将对您的程序包和依赖项结构设置一些相当严格的规则。 这样做的好处是,它变得非常灵活。
没有这样的结构,将您的项目分成多个部分可能会很困难。 是否曾经尝试过在另一个应用程序中重用应用程序的一部分,只是为了意识到您必须包含大部分应用程序才能进行编译? 是否曾经尝试将应用程序的不同部分部署到不同的服务器,只是为了意识到自己做不到? 在使用上述方法之前,这肯定发生在我身上。 但是,通过这种更严格的结构,您可能想重用的部分将几乎完全依赖于依赖链的末端,因此您可以将它们打包并捆绑在自己的jar中,或者只是将代码复制到不同的容器中项目并在很短的时间内进行编译。
同样,在尝试保持软件包和分片周期自由的同时,您将不得不认真思考,每个涉及的软件包实际上都是关于什么的。 在许多情况下,这些可以极大地改善我的代码库。
因此,剩下一个问题:依赖关系很难看到。 没有工具,很难保持代码库的自由。 当然,有很多工具可以检查周期,但是清理这些周期很困难,而且大多数工具提供这些周期的方式也无济于事。 我认为一个需求是两件事:
- 一个简单的测试,可以与所有其他测试一起运行,并且在创建依赖项圈时失败。
- 可视化类之间所有依赖关系的工具,同时显示每个类所属的切片。
惊喜! 我可以推荐一个很棒的工具: Degraph ! (我是作者,所以我可能会有偏见)
您可以像这样在JUnit中编写测试:
assertThat(
classpath().including("de.schauderhaft.**")
.printTo("degraphTestResult.graphml")
.withSlicing("module", "de.schauderhaft.(*).*.**")
.withSlicing("layer", "de.schauderhaft.*.(*).**"),
is(violationFree())
);
该测试将分析类路径中以de.schauderhaft开头的所有内容。 它将以两种方式对类进行切片:通过获取包名称的第三部分和通过获取包名称的第四部分。 因此,类名de.schauderhaft.customer.persistence.HibernateCustomerRepository最终出现在模块客户和层持久性中。 并且它将确保模块,层和包是无周期的。
并且,如果找到依赖项圆,它将创建一个graphml文件,您可以使用免费的图形编辑器yed打开该文件 。 通过一点布局,您将得到如下所示的结果,其中导致循环依赖关系的依赖关系被标记为红色。
同样,有关如何实现良好的可用布局的更多详细信息,我必须参考Degraph的文档 。
另请注意,图表主要以绿色为主,并带有少许红色,非常适合本季节!
翻译自: https://www.javacodegeeks.com/2014/12/managing-package-dependencies-with-degraph.html