谁在害怕大的复杂类?

作为软件领域的黑洞,大规模的类往往导致一些不好的反馈:这些类增长的如此迅速,使得程序员害怕重构他们,而不得不只是简单地加一些功能到里面,这也导致了下一个程序员做出同样的选择。所有人都知道这些类应该被销毁,将他们的功能分散到一个个更小的类中,但是没人知道从哪开始。哪一个方法应该被最先移出?做出这个决定的依据是什么?有什么准则可以遵循?

Fowler在他的重构书中这样写道:

“如果我不确定是否应该移动一个方法,我就去看看其他方法。移动其他方法通常比较容易决定。不过有时还是很难下决定,但这也没什么大不了的。如果决定很难下,就说明了怎么选择都可以。然后我就根据直觉来做;再者,我还能在以后改变决定。”

这个真的比一些客观技术标准更有帮助。

下图展示了一个spoiklin图。图中每个圆点代表一个方法,一条线表示SequenceDiagram类中的一个依赖。

图1:SequenceDiagram类

 

这个类增长的很大,需要将其中的一些功能移动到一个新的类中。一个程序员应该从哪开始搜索需要移动的方法?可以考虑下面四个条件:

  1. 重构的目的是求取供体类和新类之间的某种平衡。如果只是将供体类的大部分方法移动到新类,似乎没什么作用。因为这只是将复杂性从一个类移动到另一个,没解决任何问题。
  2. 新类自身应该具有完整性。不应该是随机选择一些方法或没联系的方法移动到新类:它的方法必须服务于一些共同的目的。共享依赖提供了一个很强的共享职责,通过从供体类中选择一些已经合作过的方法来使得新类形成一个内部依赖的整体,实现一个最好实现的功能。所以程序员不是随意挑选一些方法,而是移动一些有关联的方法。
  3. 任何候选方法的结合都将对系统其他部分产生影响。现代的工具和IDE能最大程度简化方法移动的操作,但是只有程序员才能从设计层面避免不恰当的移动;就像新类必须享受完整性,但也必须供体类因此而引发的不完整性。从候选集中选择方法的个数反映了由重构引起的不完整性的程度。在做出选择时应该慎重考虑这个数字。

有了这三项标准(第四项在后面会给出),程序员就能系统和客观的搜索方法。这些准则不保证能给出解决方案,但是他们至少避免了过早地放弃寻找优化办法。在下图中,被影响的集合将被审查(一个被影响的集合是那些从某些特定方法能够到达的方法)。每一个方法的圆点根据以下原则被标记为红色:一个深红色圆点表示该方法的孩子节点(和孙子节点…)被很多其他方法依赖,不适合移动。暗红色圆点表示该方法的后继节点很少被依赖,建议移出。黑色节点表示少于两个方法依赖。

在下图中,程序员点击execute()方法来高亮显示它的影响集合,然后会发现只有5个其他方法依赖于该集合中的元素。

图2:可以移动的节点太多

 

上面的选择会因为规则1而失败:移动大量方法到新类,使得新类和供体类一样庞大和复杂。然后进一步探索,点击下图中的drawDiagram()

图3:可以移动的节点还是太多

 

不幸的是,和上面情况一样,drawDiagram()方法的影响集合太大,以至于不能在新类和供体类之间找到一个平衡,所以否定了该方案。下面来点击drawClassLines()方法,见下图。

图4:可以移动的节点太多碰撞

 

现在,需要移动的方法集合看起来应该不太大也不太小。这种选择体现在工具中就是:12个其他方法依赖于这个集合中的一些方法,那些深红色的圆点就是个警告。下图所示的搜索过程是点击drawLineFromCallingToCalled()方法的结果。

图5:可以移动的节点比较合适

 

成功。这个选择提供了一个由6个方法组成的集合,他们都有一个共同的祖先,也就是这6个方法是相互依赖的。这在集合规模上是个很好的选择,并且对第一次重构很有帮助。下图则展示了重构的过程,6个方法中的每一个都从SequenceDiagram类中移出。

图6:移动6个方法

 

这一次重构后,SequenceDiagram类如下图所示。

图7:第一次重构结果

 

注意到一些额外的方法被添加到第一行:这些都是允许新类能够访问的SequenceDiagram类的一些数据;这些方法都将提取出来作为一个接口,以避免循环依赖。它们只是给供体类增加了一点复杂性,所以该方案可行。下图展示了新类CallingLine和它的新“用户”。

图8:产生一个新类CallingLine

 

当然,这项工作还没有结束。尽管刚才成功提取出了一个新类,供体类还是很笨重,需要继续移出一些方法集合。这里,第4项也是最后一项标准是:深度。好的设计要求尽可能小的传递依赖。研究图7会发现,两个新方法集合会非常明显:它们在图形的最下面非常显眼,它们是最深的可到达方法。图9展示了positionOwningSetNameBoxes()的影响集合。

图9:继续探索一个新的候选方法

 

图10展示了第二个方法stripeBackground()

图10:继续探索第二个候选方法

 

尽管影响集合没有变得更大,这两个方法只被另外4个方法依赖,所以它们的关系基本没有变化。图11展示了被SequenceDiagram类影响的两个集合的移出过程。注意在这个操作中,图形深度的降低。

图11:移出另外7个方法

 

第二次重构后的SequenceDiagram类如图12所示。

图12:第二次重构后的SequenceDiagram类

 

最后,研究图12的结构发现的另一个可移出集合:processOwningSetNamesPrinting()方法的影响集合,见图13。

图13:最后一次试探

 

这个方法集合尽管很小,但是扩展依赖到了新创建的类,使得它更像是新类中的一部分。图14展示了这三个方法的移动过程。

图14:移动最后3个方法

 

图15展示了最后的结果,重构的SequenceDiagram类,它相对独立,更容易从依赖关系上理解其独立性。

图15:类SequenceDiagram

 

图16是重构之前的类,比较下它们的区别。

图16:原始SequenceDiagram类

 

图17展示了新类CallingLine的最终形式,它在结构上并不复杂。

图17:类CallingLine

 

总结

害怕是因为缺乏经验。

一些程序员害怕重构复杂类仅仅是因为他们不知道从哪开始。有的程序员知道:不管一个类有多复杂,通过不断地应用一些简单的规则,能帮助阐明整个过程(这一方法也适用于包)。这些人慢慢学会了如何去解决这类问题,找出隐藏在系统中的复杂类,通过把这些类一点点减小来寻找乐趣。

原文链接: javacodegeeks 翻译: ImportNew.com - 秋双
译文链接: http://www.importnew.com/12482.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 秋双

java码农一枚,兼顾DB和algorithm

查看秋双的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部