我们试图重构Hudson.java,但没有成功; 幸亏有了第一次尝试的经验和更多的时间,我才能够成功地对其进行重构。 无论如何,这都是一个很好的学习机会 。
得到教训
我们学到的两个最重要的事情是:
- 永远不要低估遗留代码。 它比您预期的更加复杂和交织,并且它的袖子中有令人难以置信的令人讨厌的惊喜。
- 永远不要低估遗留代码。
还有一个重要的提示:当您感到疲倦和沮丧时,请在StackOverflow :-)上阅读“ 有史以来最好的评论 ”,获得一些乐趣。 看到别人的痛苦会使自己的痛苦看起来较小。
我也开始认为,重构过程必须更加严格,以防止您偏离最初的目标,以及避免在解决某些新问题的永恒循环中迷路。 人们倾向于进行深度优先的重构更改,这些更改很容易使他们误入歧途,远离他们实际需要去的地方。 重要的是要定期停下来看看我们在哪里,我们试图在哪里获得,以及我们是否迷路了,不应该仅仅修剪当前的重构“分支”并回到某个较早的地方,然后尝试完全不同的解决方案。 我猜想Mikado方法的主要优点之一是,它为您提供了这种全局概览-当仅在您的脑海中时,它很容易迷失-并带有回滚点。
遗留法典
看在上帝的份上,使用依赖注入框架! 单例及其手动检索确实使测试复杂化,并影响了代码的灵活性。
不要使用公共领域。 它们真的很难用接口替换类。
反射和多线程很难甚至很难发现特定代码的依赖性以及因此而带来的影响。 我很难找出在其构造函数仍在运行时调用Hudson.getInstance的所有位置。
我们通往失败和成功的道路
Hudson.java可以完成很多重构,因为它是典型的God Class ,它通过其邪恶的singleton实例将其触角扩展到整个代码库中,几乎每个人都将它用于多种不同目的。 Gojko描述了一些值得解决的问题 。
失败
我们尝试从小开始并“标准化”单例初始化,这不是在工厂方法中完成的,而是在构造函数中完成的。 我没有很好地选择目标,因为它带来的价值不高。 这个想法是为了使可能还有Hudson的其他实现(例如MockHudson)成为可能,但是就代码状态而言,这实际上是不可行的,即使是这样,一个简单的Hudson.setInstance可能就足够了。 无论如何,我们试图创建一个工厂方法并将单例实例的初始化移到那里,但最后我们迷失在并发问题中:要么有多个Hudson实例,要么应用程序自身死锁。 我们试图四处移动代码,但是依赖关系却不允许我们这样做。
成功
在反思我们的失败时,我已经意识到问题是Hudson.getInstance()在执行Hudson的构造函数期间已经被那里使用的对象和从那里开始的线程调用了很多次。 当然,在完全初始化之前访问半生的实例是一种可怕的做法。 解决方案非常简单:为了能够在构造函数外部初始化单例字段,我们必须从其上下文中删除对getInstance的所有调用。
从相应的GitHub提交中可以很好地看到这些步骤。 摘要:
1.我在构造函数上使用了“引入工厂”重构
2.我修改了ProxyConfiguration,使其不使用getInstance,但希望在首次使用之前设置根目录
3.我将不需要运行的代码从构造函数中移出,移到了新的工厂方法上–这导致了一些(希望微不足道的)代码重新排序 4.最后,我还将实例初始化移至工厂方法
我不能百分百确定所产生的代码在意义上是否具有相同的语义,因为我不得不在安全的自动重构之外进行少量更改,并且除了尝试运行该应用程序外,没有其他有用的测试(并且,这与旧版应用程序一样常见,因此事先创建它们是不可行的。
重构后的代码并没有提供更多的附加值,但是它是进一步重构的一个良好的开端(我没有时间尝试:-(),它摆脱了实例被滥用的麻烦。创建后,构造函数的代码变得更简单,更好,这次练习花了我大约四个pomodoros ,即不到两个小时。
如果有时间,我将继续从Hudson中提取一个接口,将其无关的职责转移到它们自己的类中(也许将方法保留在Hudson中以实现向后兼容并委托给那些对象),甚至使用AOP魔术在保留二进制兼容性的同时获得更简洁的代码(就像Hudson / Jenkins实际上已经做到的那样 )。
自己尝试一下!
设定
获取代码
git@github.com:iterate/coding-dojo.git # 50MB => takes a while
cd coding-dojo
git checkout -b mybranch INITIAL
编译代码
润·詹金斯/哈德森
cd coding-dojo/2011-04-26-refactoring_hudson/
cd maven-plugin; mvn install; cd .. # a necessary dependency
cd hudson/war; mvn hudson-dev:run
并浏览到http:// localhost:8080 / (Jetty应该自动选择对类文件的更改)。
进一步重构
如果您是喜欢冒险的类型,则可以尝试通过拆分God类的各个职责来进一步改进代码。 我将这样进行:
- 从Hudson提取接口并尽可能使用它
- 将相关的方法和字段移动到它们自己的(嵌套)类中,原始的哈德逊方法只是委托给它们(移动方法重构应该很有用); 例如:
–扩展和描述符的管理
–认证和授权
–集群管理 –应用程序级功能(控制方法,例如重新启动,配置更新,套接字侦听器的管理) -UI控制器(将其分解出来将需要重新配置订书机)
- 将嵌套类转换为顶级类
- 提供一种在没有哈德森的情况下获取类实例的方法,例如作为单例
- 尽可能使用单个类而不是Hudson,以便其他类仅取决于它们实际需要的功能,而不是整个Hudson
了解詹金斯/哈德森
如果您想了解有关Hudson的功能及其工作方式的模式,可以检查:
- 哈德森的体系结构,并可以选择继续进行
- 大厦哈德森
- UI框架Stapler的简介 (其主要功能是它巧妙地将URL映射到对象层次结构[以及查看文件和操作方法]),也许还可以查看Stapler的参考
旁注:哈德逊与詹金斯
曾经有一个名为Hudson的持续集成服务器,但是在其赞助商Sun死后,它最终落入了一个名为Oracle的人的手中。 他不太擅长沟通,没有人真正知道他的举止,当他开始表现得有点怪异时-至少至少让哈德森的朋友意识到了-那些担心哈德森未来的人(包括大多数最初在哈德森工作的人)项目)将其克隆并命名为Jenkins,这是男管家的另一个流行名称。 因此,现在我们得到了Oracle支持的Hudson以及来自Sonatype和Jenkins的专家团队,并得到了一个活跃社区的支持。 此练习基于Jenkins的源代码,但是为了保持混乱的程度,我经常将其称为Hudson,因为这是如何调用包和主类的。
结论
事实证明,重构遗留代码总是比您预期的要复杂和耗时。 遵循某种方法(例如Mikado方法)很重要,该方法可以帮助您对要去的地方,要去的地方保持全局了解,并定期考虑自己的行为和原因,以免迷路。一系列解决问题的步骤-发现新问题的步骤。 重要的是要意识到何时放弃并尝试其他方法。 为更改编写测试也非常困难或不可能,因此您必须非常小心(尽可能使用安全,自动的重构,并分步进行),但不要担心不要阻止您尝试保存代码以免被衰减。
参考: 我从 The Java博客的 JCG合作伙伴那里学到的东西(几乎)无法重构Hudson 。
相关文章:
翻译自: https://www.javacodegeeks.com/2011/05/refactor-hudson-god-class.html