代码重构

面试落下好多节课,现在重新捡起来。
P.S. 到了HR面,好开心OVO 2015/04/23 14:41

首先是重构的概念:

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
Refactoring (noun): a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.

重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
Refactor (verb): to restructure software by applying a series of refactorings without changing its observable behavior.

.摘自<重构 改善既有代码的设计>

也就是说重构是为了使得程序变得容易理解。当然重构得建立在不影响程序正常运行结果

每当我要进行重构的时候,第一个步骤永远相同:我得为即将修改的代码建立一组可靠的测试环境
.摘自<重构 改善既有代码的设计>

好的 现在开始将常见的重构情况(22种代码味道,这里的味道是smelly)整理成下表:

类内味道

味道名称关键词处理方法详情备注
过长函数代码行 、方法名过长提炼函数将函数切分成一个个小函数(从复用的角度)
以查询替代临时变量`比如:int sum = a + b; —>getSum(a,b);`说实在的,这个方法也只有在频繁使用类似a+b的公式计算的时候才有所作为,而且函数调用 似乎会折损点效率,毕竟压栈弹栈
引入参数对象对于参数列表中大量的参数,将这些参数封装到对象里面一般来说这个方法比较适合这些参数有一定联系的,比如username password address…
以函数对象取代函数新建一个类,将庞大的函数切分成多个函数作为新类的方法,其中局部变量作为类的字段原因:如果一个函数的局部变量泛滥的话,对于提炼函数来说,有点困难,因为每一个子函数都需要传入局部变量。而新建类其中局部变量成为成员变量,自然可以自由访问,无需显式传值
分解条件表达式提炼条件表达式,将条件表达式(较长的判断以及方法体)封装成一个个函数。原因:由于if(或switch)情况过多,条件判断复杂,方法体过长等导致的函数过长,可读性差(一般需要在每个方法体的开头都必须写上注释). 注意函数命名时,需做到一目了然
过大类功能过多,实例变量过多(违反单一职责原则)提炼子类太多实例变量或太多代码,分离出小类,与原来的类形成关联关系。
提炼接口确认客户端如何使用???这个我还是有点不解,按道理提炼出来的接口是需要实现的,也就是说原类里面还是需要有相关的函数,难道是提炼函数的别名???
复制”被监视数据”把数据和行为分别移到一个独立的领域对象,但需要保留一些重复(重叠)数据。最常用的地方就是:观察者模式。 传统的GUI集合数据处理 —> MVC Model只做数据处理,View只做界面更新,而Control连接这两个,每当界面有事件触发,发送给Model,每当Model修改数据,通知View界面更新
过长参数列如题引入参数对象见上文
以函数取代参数向已有对象发出一条请求就可以取代一个参数也就是说在参数列表引入这个参数是多余的,毕竟可以直接跟已有对象拿
保持对象完整将来自同一个对象的一堆参数收集起来它指的是,参数项就是同一个对象的一个个参数,比如Account : username,address,phone function store(username,address,phone)其实可以取代为store(account)
过多的注释画蛇添足,赘肉提炼函数将需要注释的代码块封装到函数中去注意函数的命名应该能简单明了的知道其功能,其实就是把注释写道函数命名去了
函数改名提炼过的函数还需要注释来解释其行为,说明命名不佳
引入断言将说明系统需求规格的注释,改成断言断言 Assert, 其实就是类似与判空之类的判断语句 比如: assert(name)
夸夸其谈的未来性超越一个时代的想法,最终因实际中无法实现而沉淀在历史的长河中折叠继承体系某个抽象类无多大作用,直接取消抽象类,将其子类改成类抽象层不一定越多越好,动态联编其实会对程序的效率有所折损(但是如果考虑到灵活性和扩展性,又可以忽略这些折损,其实编程也是需要你权衡利弊,没有绝对,depend on circumstances)
将类內联化将俩个类合并取消不必要的委托(关联),如果一个类跟另一个类的关系非常紧密(依恋情结?),而且另一个类跟其他类无多少交际,直接将这俩类合并。(这个也是无绝对的,万一导致过大类呢?奇怪,我们学到的方法却是自相矛盾的,类內联化–提炼子类 解决这个矛盾的方法是:中庸)
移除参数函数的某些参数未被使用(多余的)
函数改名函数名称带有多余的抽象涵义
内联函数/移除函数作用不大的函数,可以移除掉,或者是与其他函数合并内联函数–提炼函数
重复代码相似的代码结构,多处出现提炼函数同一个类的函数之间或者函数内部方法体内有相似的代码
提炼函数/函数上移/塑造模板函数情况为:两个互为兄弟的子类含有相同的方法函数上移,就是将这些函数移动到父类中去(提炼函数 跟这个一样吧。。)塑造模板函数:当函数之间的结构相似,中间填充的内容不同时,可以在父类中塑造一个函数模板,让这些兄弟类的不同实现作为其填充剂
提炼类两个毫不相关的类含有相同的代码将相同的代码提炼的一个新类中,让这俩个类调用其方法
异曲同工的类相同的事情, 不同的签名函数改名改成相同的签名?我只想知道 有何作用。。。
搬移函数将某些行为移入类,直到两者的协议一致为止。这句话的理解应该是???
提炼超类提炼超类作为模板,具体变化在子类
Switch惊悚现身switch语句通常会导致代码重复(不同情况的方法体结构可能相同,只是里面的细节不同)提炼函数,搬移函数,以多态取代条件表达式,以子类取代类型码,以State/Strategy(策略模式)取代类型码与类型码相关的函数或类

类间味道


//提炼函数:
public class Report {
    public static void report(Writer out, List machines, Robot robot) throws IOException) {
    out.write("FACTORY REPORT\n");
    Iterator line = machine.iterator();
    while (line.hasNext()) {
        Machine machine = (Machine)line.next();
        out.write("Machine " + machine.name());

        if (machine.bin() != null)
        out.write(" bin= " + machine.bin());
        out.write("\n");
    }
    out.write("\n");

    out.write("Robot");
    if (robot.location() != null) 
        out.write(" location =" + robot.location().name());
    if (robot.bin() != null)
        out.write(" bin = " + robot.bin());
    out.write("\n");
out.write ("==========\n");
  } 
}
//change to...
public static void report (PrintStream out, List machines, Robot robot) {
    reportHeader(out);
    reportMachines(out, machines);
    reportRobot(out, robot);
    reportFooter(out);
}
//以上代码摘自weiliu_china

上面将大片的代码提取成函数(这大片的代码实质上是移动到其他函数中),有何好处呢?
1,增加代码的复用性
相似的情况只需调用这些函数即可
2,便于代码维护
假设此时需要修改对Footer的report,只需修改reportFooter函数,而不是一改改多处(后面会提到霰弹式修改),便于维护

其实,良好的代码风格就是:用人不疑。"放心"的调用里面的函数而不关心里面如何实现
(因为如果干涉里面的实现,会在调用前(后)增加额外的代码,实质上是增加了自身的特殊性以及对这个函数的依赖性,如果这个函数里面的实现发生变化,有可能这边的代码也需要变化,这就是耦合。以后找到一个较好的例子再好好解释。)
比较好的例子是OSI模型。
OSI模型中 上下层之间是通过相应的接口通信的,上下层之间并不关心对方具体如何实现,他只知道对方提供的接口能实现他的功能
所以当某一层内部发生修改的时候,他的上下层不需要修改(因为我知道他会帮我做好我交付给他的事,至于他的私生活,我觉得并不影响我对他的看法)
感兴趣可以去看"迪米特法则"
//分解条件表达式:

//塑造模板方法:
两个兄弟类之间含有相同的代码,
父类构建一个模板,让兄弟类不同的实现作为填充剂。
class A:
public void getTea() {
    //...
    //warm the water by stove
    //clean the silver tea pot
    //put the green tea
    //...
}
class B:
public void getTea() {
    //...
    //warm the water by stove
    //clean the silver tea pot
    //put the black tea
    //...
}
//change to:
class Father:
protected abstract void putTea();
...
public void getTea() {
    //...
    putTea();
    //...
}

class A(B)
分别实现独有的putTea()操作。

thanks to http://www.cnblogs.com/matchcolor/tag/%E9%87%8D%E6%9E%84/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值