/**
* @startTime 2020-12-26 14:30
* @endTime 2020-12-26 21:45
* @startPage 237
* @endPage 412
* @efficiency 412/11 = 37.5页/天
* @needDays 412/37.5 = 11天
* @overDay 2020-12-16 + 11天 = 2021-12-26
* @status 已完结
*/
将ifelse的段落分别提炼出独立函数。
如果你有一系列条件测试,都得到相同的结果,你就可以将这些测试合并为一个条件表达式,并将这个条件表达式提炼出成为一个独立函数。
在条件表达式的每个分支上有着相同的一段代码,将这段重复代码转移到条件表达式之外。
以break语句或return语句取代控制标记。
使用卫语句表现所有特殊情况。
如果某个条件极为罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回,这样的单独检查常常被称为“卫语句”。
你手上有个条件表达式,它根据对象类型的不同而选择不同的行为,将这个条件表达式的每个分支放进一个子类中的覆写函数中,然后将原始函数声明为抽象函数。
正因为有了多态,所以你会发现:“类型码的switch语句”以及“基于类型名称的ifelse语句”在面向对象程序中很少出现。
将null值替换为null对象。
以断言明确表现某种假设。
最简单也是最重要的一件事情就是修改函数名称,名称是程序员与阅读者交流的关键工具。只要你能理解一段程序的功能,就应该大胆地使用Rename Method将你所知道的东西传达给其它人。
某个函数需要从调用端得到更多信息,为此函数添加一个对象参数,让该对象带进函数所需信息。
某个函数既返回对象状态值,又修改对象状态。
建立两个不同的函数,其中一个负责查询,另一个负责修改。
若干函数做了类似的工作,但在函数本体中却包含了不同的值。
建立单一函数,以参数表达哪些不同的值。
你有一个函数,其中完全取决于参数值而采取不同行为。
针对该参数的每一个可能值,建立一个独立函数。
你从某个对象中取出若干值,将它们作为某一次函数调用时的参数。
改为传递整个对象。
「动机」
有时候,你会将来自同一对象的若干项数据作为参数,传递给某个函数。这样做的问题在于:万一将来将被调用函数需要新的数据项,你就必须查找并修改对此函数的所有调用。如果你把这些数据所属的整个对象传给函数,可以避免这种尴尬的处境,因为被调用函数可以向那个参数对象请求任何它想要的信息。
除了可以使参数列表更加稳定之外,还能提高代码的可读性。过长的参数列表很难使用,因为调用者和被调用者都必须记住这些参数的用途。
让参数接受者去除该项参数,并直接调用前一个函数。
「动机」
如果函数可以通过其他途径获取参数值,那么它就不应该通过参数取得该值。过长的参数列表会增加阅读者的理解难度,因此我们应该尽可能缩短参数列的长度。
某些参数总是很自然的同时出现,以一个对象取代这些参数。
类中的某个字段应该在对象创建时就被设值,然后就不再改变。
去掉该字段的所有设值函数。
有一个函数,从来没有被其他任何类用到。
将这个函数修改为private。
你希望在创建对象时不仅仅是做简单的建构动作。
将构造函数替换为工厂函数。
Employee(int type){
_type = type;
}
static Employee create(int type){
return new Employee(type);
}
某个函数返回的对象,需要由函数调用者向下转型,将向下转型的操作转移到函数中。
Object lastReading(){
return readings.lastElement();
}
Reading lastReading(){
return (Reading)readings.lastElement();
}
某个函数返回一个特定的代码,用以表示某种错误情况,改用异常。
代码的可理解性应该是我们虔诚追求的目标。
非受控异常
class Account{
void withdraw(){
Assert.isTrue("sufficient funds",amount <= _balance);
_balance-= amount;
}
}
class Assert{
static void isTrue(String comment, boolean test){
if(!test){
throw new RuntimeException("Assertion failed:"+comment);
}
}
}
15、以测试取代异常
面对一个调用者可以预先检查的条件,你抛出一个异常。
修改调用者,使它在调用函数之前先做检查。
「动机」
异常只应该被用于异常的、罕见的行为,也就是哪些产生意料之外的错误的行为,而不应该成为条件检查的替代品。如果你可以合理期望调用者在调用函数之前先先插某个条件,那么就应该提供一个测试,而调用者应该使用它。
两个子类拥有相同的字段,将该字段移至超类。
有些函数,在各个子类中产生完全相同的结果,将该函数移至超类。
你在各个子类中拥有一些构造函数,它们的本体几乎完全一致。
在超类中新建一个构造函数,并在子类构造函数中调用它。
class Manager extends Employee{
public Manager(String name, String id, int grade){
_name = name;
_id = id;
_grade = grade;
}
}
//改变为
class Manager extends Employee{
public Manager(String name, String id, int grade){
super(name, id);
_grade = grade;
}
}
超类中的某个函数只与部分子类有关,将这个函数移到相关的那些子类中去。
超类中的某个字段只被部分子类用到,将这个字段移到需要它的那些子类中去。
类中的某些特性只被某些实例用到。
新建一个子类,将上面所说的那一部分特性移到子类中。
两个类有相似的特性,为这两个类建立一个超类,将相同特性移至超类。
「动机」
重复代码时系统中最糟糕的东西之一。如果你在不同地方做同一件事情,一旦需要修改那些动作,你就得平白做更多的修改。
重复代码的某种形式就是:两个类以相同的方式做类似的事情,或者以不同的方式做类似的事情。对象提供了一种简化这种情况的机制,那就是继承。但是,在建立这些具有共通性的类之前,你往往无法发现这样的共通性,因此经常会在具有共通性的类出现之后,再开始建立其间的继承关系。
另一种选择就是提炼类,这两种方法之间的选择其实就是继承和委托之间的选择。如果两个类可以共享行为,也可以共享接口。
若干个客户使用类接口中的同一子集,或者两个类的接口有部分相同。
将相同的子集提炼到一个独立接口中。
超类和子类之间无太大区别,将它们合为一体。
「动机」
继承是避免重复行为的一个强大工具,无论何时,只要你看见两个子类之中有类似的函数,就可以把它们提升到超类。但是如果这些函数并不完全相同该怎么办?
我们仍然有必要尽量避免重复,但又必须保持这些函数之间的实质差异。
常见的一种情况是:
两个函数以相同顺序执行大致相近的操作,但是各操作不完全相同。这种情况下我们可以将执行操作的序列移至超类,并借助多态保证各操作仍得以保持差异性。这样的函数被称为模板函数。
在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数,让继承取代委托。
某个继承体系同时继承两项责任,建立两个继承体系,并通过委托关系让其中一个可以调用另一个。
继承是个好东西,它可以明显减少子类中的代码量,函数的重要性可能并不和它的大小成正比,在继承体系中尤然。
将数据记录变成对象,将大块的行为分成小块,并将行为移入相关对象之中。
某些GUI类中包含了领域逻辑,将领域逻辑分离出来,为它们建立独立的领域类。
「动机」
提到面向对象,就不能不提MVC模式。MVC模式最核心的价值在于:它将用户界面代码和领域逻辑代码分离了。展现层只包含处理用户界面的逻辑;领域类不含任何与程序外观界面相关的代码,只含有业务逻辑相关代码,这好像就是现在所说的前后端分离。
你有某个类做了太多工作,其中一部分工作是以大量条件表达式完成的,此时,应该建立继承体系,以一个子类表示一种特殊情况。
以上技术如此出彩,可它们仅仅只是个开始,这是为什么?答案很简单,因为你还不知道何时应该使用它们、何时不应该使用它们;何时开始,何时停止。使重构能够成功的,不是前面各自独立的技术,而是这种节奏。
那么我们该如何重构呢?
你面前的代码也许看起来混乱极了,不要着急,一点一点慢慢地解决这些问题。当你想要添加新功能时,用上几分钟时间把代码整理一下。如果首先添加一些测试能使你对整理工作更有信心,那就去做,它们会回报你的努力。如果在添加一些新代码之前进行重构,那么添加新代码的风险将大大降低。重构可以使你更好理解代码的作用和工作方式,这使得新功能的添加更容易。而且重构之后代码的质量也会大大提高,下次你再有机会处理他们的时候,肯定会对目前所做的重构感到非常的满意。
上一篇:《重构 改善既有代码的设计 2》重新组织函数、数据
下一篇:【编写高质量代码:改善Java程序的151个建议】