十一、Hide Method(隐藏函数)
有一个函数,从来没有被其他任何类调用。
将这个函数修改为private。
动机
重构往往需要促使你修改函数的可见度。提高函数可见度的情况很容易想象:另一个类需要用到某个函数,因此你必须提高函数的可见度。但是要指出一个函数的可见度是否过高就稍微困难一些。理想状况下,你可以使用工具检查所有函数,指出可被隐藏的函数。即使没有这样的工具,你也应该时常进行这样的检查。
一种特别常见的情况是:当你面对一个过于丰富、提供了过多接口时,就值得将非必要的取值函数和设值函数隐藏起来。尤其当你面对的是一个只有简单封装的数据容器时,情况更是如此。随着愈来愈多行为被放入到这个类,你会发现许多取值设值函数不再公开,因此可以把它们隐藏起来。如果你把取值设值函数设为private,然后在所有地方都直接访问变量,那么就可以放心移除取值、设值函数了。
做法
1、经常检查有没有可能降低某个函数的可见度。
? 使用lint一类的工具,尽可能频繁的检查。当你在另一个类中移除对某个函数的引用时,也应该进行检查。
? 特别对设值函数进行上述的检查。
2、尽可能降低所有函数的可见度。
3、每完成一组函数的隐藏之后,编译并测试。
? 如果有不适当的隐藏,编译器很自然会检查出来,因此不必每次修改后都进行编译。如有任何错误出现,很容易被发现。
十二、Replace Constructor with Factory Method(以工厂函数取代构造函数)
你希望创建对象时不仅仅是做简单的创建动作。
将构造函数替换为工厂函数。
Employee (int type){
_type = type;
}
转换后:
Static Employee create (int type){
Return new Employee(type);
}
动机
使用 Replace Constructor with Factory Method的显而易见的动机,就是在派生子类的过程中以工厂函数取代类型码。你可能常常需要根据类型码创建相应的对象,现在,创建名单中不得不加上子类,那么子类也是根据类型码创建。然而由于构成函数只能返回单一类型的对象,因此你需要将构造函数替换为工厂函数。
此外,如果构造函数的功能不能满足你的需要,也可以使用工厂函数来代替它。工厂函数也是Change Value to Reference的基础。你也可以令你的工厂函数根据参数的个数和类型,选择不同的创建行为。
做法
1、新建一个工厂函数,让它调用现有的构造函数。
2、将调用构造函数的代码改为调用工厂函数。
3、每次替换后,编译并测试。
4、将构造函数声明为private。
5、编译。
十三、Encapsulate Collection (封装向下转型)
某个函数返回的对象,需要由函数调用者执行向下转型(downcast)。
将向下转型动作移到函数中。
动机
在强类型OO语言中,向下转型是最烦人的事情之一。之所以很烦人,是因为从感觉上来说它完全没有必要:你竟然越俎代庖地告诉编译器某些应该由编译器自己计算出来的东西。但是,由于计算对象类型往往比较麻烦,你还是常常需要亲自告诉编译器对象的确切类型。向下转型在java特别盛行,因为java没有模板机制,因此如果你想从集合中取出一个对象,就必须进行向下转型。
向下转型也许是一种无法避免的罪恶,但你仍然应该尽可能少做。如果你某个函数返回一个值,并且你知道所返回的对象类型比函数签名所昭示的更特化,你便是在函数用户身上强加了非必要的工作。这种情况下你不应该要求用户承担向下转型的责任,应该尽量为它们提供准确的类型。
以上所说的情况,常会 在返回迭代器或集合的身上发生。此时你应该观察人们拿这个迭代器干什么用,然后针对性地提供专用函数。
做法
1、找出必须对函数调用结果进行向下转型的地方。
? 这种情况通常出现在返回一个集合或迭代器的函数中。
2、将向下转型动作搬移到该函数中。
? 针对返回集合的函数,使用Encapsulate Collection.
十四、Replace Error Code with Exception(以异常取代错误码)
某个函数返回一个特点类型的代码,用以表示某种错误信息。
改用异常。
动机
和生活一样,计算机偶尔也会出错。一旦事情出错,你就需要有些对策。最简单的情况下,你可以停止程序运行,返回一个错误码。这就好像错过一班飞机而自杀一样(如果真那么做,哪怕我是只猫,我的九条命也早赔光了)。尽管我们的油腔滑调企图带来一点幽默,但这种“软件自杀”选择的确是有好处的。如果程序崩溃代价很小,用户又足够宽容,那么就放心终止程序的运行好了。但如果你的程序比较重要,就需要以更认真的方式来对待。
问题在于:程序中发现错误的地方,并不一定知道如果处理错误。当一段子程序发现错误时,它需要让它的调用者知道这个错误,而调用者也可能将这个错误继续沿着调用链传递上去。许多程序都使用特殊输出来表示错误,Unix和C-base系统的传统方式就是以返回值表示子程序的成功失败。
Java有一种更好的处理方式:异常。这种方式之所以更好,因为它清楚的将“普通程序”和“错误处理”分开了,这使得程序更容易理解--我希望你如今已经坚信:代码的可理解性应该是我们虔诚追求的目标。
做法
1、决定应该抛出受控(checked)异常还是非受控(unchecked)异常。
? 如果调用者有责任在调用前检查必要状态,就抛出非受控异常。
? 如果想抛出受控异常,你可以新建一个异常类,也可以使用现有的异常类。
2、找到该函数的所有调用者,对它们进行相应调整,让它们使用异常。
? 如果函数抛出非受控异常,那么就调整调用者,使其在调用函数前做适当检查。每次修改后,编译并测试。
? 如果函数抛出受控异常,那么就调整调用者,使其在try区段中调用该函数。
3、修改该函数的签名,令它反映出新用法。
如果函数有许多调用者,上述修改过程可能跨度太大。你可以将它分成下列数个步骤。
1)决定应该抛出受控异常还是非受控异常。
2)新建一个函数,使用异常表示错误情况,将旧函数的代码复制到新函数中,并做适当调整。
3)修改旧函数的函数本体,让它调用上述新建函数。
4)编译并测试
5)逐一修改旧函数的调用者,令其调用新函数。每次修改后,编译并测试。
6)移除旧函数。
十五、Replace Exception with Test(以测试取代异常)
面对一个调用者可以预先检查的条件,你抛出了一个异常。
修改调用者,使它在调用函数之前先做检查。
动机
异常的出现是程序语言的一大进步。运用Replace Error Code with Exception ,异常便可协助我们比蒙许多复杂的错误处理逻辑。但是,就像许多好东西一样,异常也会被滥用,从而变得不再愉快(就连味道极好的Aventinus啤酒,喝的太多也会让我厌烦)。“异常”只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误的行为,而不应该成为条件检查的替代品。如果你可以合理期望调用者在调用函数之前先检查某个条件,那么就应该提供一个测试,而调用者应该使用它。
做法
1、在函数调用点之前,放置一个测试语句,将函数内catch区段中代码复制到测试句的适当if分支中。
2、在catch区段其实处加一个断言,确保catch区段绝对不会被执行。
3、编译、测试。
4、移除所有catch区段,然后将try区段内的代码复制到try之外,然后移除try区段。
5、编译测试。