重构

重构

阅读了重构_改善既有代码的设计,最喜欢的话:

重构的意义在于:你永远不必说对不起,只要把出问题的地方修补好就行了。

重构就是在不改变代码外在行为的前提下,对代码做出修改,来改进内部结构。一般情况下代码会慢慢沉沦,但是重构与之相反,甚至每一步都可以很简单,把字段从一个类移到另一个类,某些代码从一个函数拉出来变成另一个函数,或者在继承体系中把某些代码推上推下就行了。

重构的第一个案例

如果你发现你需要为程序加一个特性,但是代码结构无法让你很方便的达成目的,那就重构程序,让它的添加比较容易进行,然后再添加特性。

重构首先保证你得有可靠的测试环境,因为避免绝大多数引入 bug 的事情。好的测试是重构的根本,花时间建立一个优良的测试机制是完全值得的。

第一步

第一步是找出逻辑泥团,然后找函数中的局部变量以及参数,如果没有变化的就直接作为参数传入,如果变化的了,那就可以作为返回值返回。

重构就是以微小的步伐修改程序。

第二步

改名,好的代码应该可以表达自己的功能,变量名称是代码清晰的关键。

第三步

看代码有没有放错地方,功能要放在他应该在的地方。

第四步

去除临时变量,就是类似于我们现在用到的 selector,就是 computed state,这个不应该每次都计算一次,而是应该提取出来,每次调用的时候调用方法来拿取,这样更少的临时变量。

第五步

不要在另一个对象的属性基础上运用 switch 语句,如果要使用,也要在对象自己的数据上使用。

重构原则

重构是在不改变软件可观察行为的前提下,提高可理解性,降低修改成本。重构都是软件的小改动。
重构是使用一系列重构手法,不改白软件可观察行为的前提下,调整结构。

与之形成对比的性能优化,性能优化通常不会改变组件的行为,除了执行速度,性能优化往往使得代码较难理解。

重构的目的

没有重构,程序的设计会逐渐变质,重构就是在整理代码,改进设计的一个重要方向就是消除重复代码。

使程序更容易理解,很多时候阅读者是自己,用重构来理解不熟悉的代码。早起的重构可能就是擦掉窗户上污垢。然后就会进入更高的理解层次上。

重构可以帮忙找 bug,因为可以深入理解,然后把新的理解反馈回去。

重构可以提高编程速度,可以提高设计质量。

重构法则

三次法则:第一次尽管做,第二次虽然不爽还是可以做,第三次就应该重构了。

这句话的感触太深了,经常会遇到一个代码写到第三次忍不了的情况。

  • 最常见的是添加新特性的时候重构,来让自己更快的理解。
  • 修补错误的时候觉得代码不够清晰从而进行重构
  • 复审代码重构

系统当下的行为只是整个故事的一部分,如果为了今天的任务不择手段,不能完成明天的任务,那最终还会回失败。

更小的发布接口

何时不该重构:我们可以将大块软件重构为封装良好的小型组件,然后逐一对组件做出重构还是重建的决定。

我们如果用了重构的话,可以不要求最开始的就是最优的设计。

很多时候我们并不一定需要提前设计很多的灵活性,我们只需要考虑把当前的简单方案重构成灵活的方案的代价大不大,如果不大,直接实现目前的简单方案就好了。

哪怕我们完全了解系统,也先实际度量一下它的性能,臆测绝大多数都是错的。

程序一大半时间都耗费在一小半代码上,通过监控程序运行,来找到性能热点锁在的一小段代码。

代码的坏味道

  • 重复代码:如果在一个点以上看到相同的程序结构,那就得想办法把它们合二为一,程序会更好。
  • 过长的函数:程序越小,他的生命周期越长,很简单,找到函数适合集中在一起的地方,提炼出来形成新函数。大量的参数和临时变量,我们可以将它们用方法获取来拿到,这样参数就会更少。
  • 过大的类:应该将类内彼此相关的变量,把他们归到一起,提出新类来
  • 过长的参数列:函数所需要的所有东西都通过参数传,这可以理解,不这样做就是全局函数了,但是我们应该减少参数,用对象来传输
  • 发散式变化:因为不同的原因在不同的方向上发生变化,我们就像想办法去提取了,单个类只能有一个变化的原因
  • 霰弹式修改:就是说一个修改在很多不同的类都需要改动的话,我们就要把他们抽到一个类里面
  • 依恋情节:将数据和对数据的操作放到一起,如果我们某个函数为了计算某个值,拿取了其他的类的很多数据,那我们这个函数其实就应该出现在那个类中
  • 数据泥团:我们有时看到相同的参数,两个类中相同的字段,我们可以把他们提炼到独立对象中,参数列表就可以缩短,简化函数调用
  • 基本类型偏执:就是我们很多时候宁愿使用基本类型,其实对象的可扩展性好得多
  • switch 语句:遇到 switch 语句我们可以使用多态性或者换成很多的函数
  • 平行继承体系:如果你为某个类增加一个子类,也必须为另一个类增加一个子类,我们解决方法是让一个继承体系的实例引用另一个继承体系的实例
  • 冗赘类:每创建的一个类,都得有人维护他,如果重构过程中这个类不再具备价值了,就应该删掉
  • 夸夸其谈未来性:就是说没用的东西趁早删掉
  • 令人迷惑的暂时字段:一些不是所有情况都需要的字段会让人迷惑,我们需要把他们提炼出来
  • 过度耦合的消息链:当出现过长的消息链的,看看能不能缩短,比如看看这段代码在哪里使用了
  • 中间人:委托这件事情可能会被过分使用
  • 狎昵关系:如果两个类过于亲密,花费太多精力研究彼此的 private 部分
  • 异曲同工的类: 如果两个函数做同样的事情,却名字不同,就得去统一或者移动函数
  • 不完美的类库:复用的意义容易被高估,很多类其实够用就好
  • 纯粹的数据类:
  • 被拒绝的遗赠:
  • 过多的注释:有的时候注释太多的情况其实是因为代码的可读性太差

构筑测试体系

修复错误通常是比较快的,但是找出错误都是噩梦一场。

确保所有的测试都完全自动化,让它们检查自己的测试结果。

实际上,写测试代码最有用的时机是在开始编程之前,写好测试代码能让你的注意力集中于接口而非实现。

重构列表

先介绍了下他即将给出的示例。

寻找引用点:很多重构要求你找到所有引用点,借助编译器咯或者文本查找。

重构的基本技巧:小步前进,频繁测试。

很多重构手法都是单进程软件这一个大前提。还挺适合 js 的。

重新组织函数

重构的很大一部分都是在对函数进行整理,更恰当的包装代码,比如提取方法,但是也有很多情况你觉得提取是没有价值的,就将函数本体返回回去。

  • 提炼函数:当看到一个过长的函数或者需要注释才能让人理解的代码,就可以放到独立函数中。函数的粒度够小的话,函数复用的机会更大,高层函数看起来就像是注释了~函数如果都是细粒度,函数覆写也会很舒服。不要让修改临时参数,我们可以把修改的值返回回来
  • 内联函数:就是内部代码和函数名称同样清晰,我们就应该去掉这个函数,直接使用代码,很多时候这一步是为了找到无用的间接层
  • 内联临时变量:就是如果临时变量只被赋值了一次,并且他妨碍了其他的重构手法,就得替换成内联的变量
  • 以查询取代临时变量:就是说消掉临时变量,可以更好地提炼代码
  • 引入解释性变量:就是复杂的表达式我们可以用临时变量来解释
  • 分解临时变量:就是如果一个临时变量被赋值了两次,有了两种含义,我们就最好再加个临时变量,否则看代码的人会打你的
  • 移除对于参数的赋值:就是说对于参数的赋值会引起误解,我们最好用一个新的值来做
  • 以函数对象取代函数:就是当大量的局部函数泛滥成灾,我们没法轻易的搞的时候,把整个函数声明为一个类,用一个类来做这件事,我们就可以随意分解,不必传参数了
  • 替换算法:其实就是说准备好 case,如果跑 case 的结果一样,就可以替换了

对象之间搬移特性

我们很多时候决定责任放在哪里很多时候都不能做好,我们可以来搬离逻辑以及搬离函数来重构类。我们可以通过抽离 class 的方法来将一些责任融入另一个类,或者在一些类没用的时候,把他直接融入另一个类。

  • 搬移函数:就是发现函数使用另一个对象的次数比自己所驻对象的次数要多。
  • 搬移字段:如果一个字段,在所驻类以外另一个类使用了,那就应该搬移了
  • 提炼类:一个类应该有清楚的抽象,如果类的功能越来越复杂,我们就应该考虑哪部分应该抽离出去,作为单独的类。
  • 将类内联化:某个类没有做太多事情,于是将他的特性移到另一个类,然后移除原类
  • 隐藏“委托关系”:就是如果有中间的委托对象,我们先拿到委托对象,再调委托对象的方法,就很蠢,应该避免知道委托对象,
  • 移除中间人:封装受托对象,每次加新特性,就得新加个简单委托函数,有的时候服务类完全变成了中间人
  • 引入外加函数:当你使用的服务类并没有提供某个能力,而你却在很多地方都要使用到这个能力的时候,就建立一个函数,不调用客户类的属性,只通过参数传递,就是说他可以随时被移出去
  • 引入本地扩展:就是说如果上面的函数就 1,2 个,那我们直接建立外加函数就行了,单数如果多的话,我们需要把这些函数组织到一起,放到恰当的地方,就像是我们 js 项目中的 util 一样

重构的意义在于:你永远不必说对不起,只要把出问题的地方修补好就行了。

重新组织数据

当一个数组的行为方式很像数据结构,我们可以把 array 用对象来变现,然后再为之提供方法,才能真正体现好处。

  • 自封装字段:很多时候我们直接访问某个字段,但是渐渐腐化,提供了 get,set 函数之后在子类继承的时候价值比较大
  • 以对象取代数据值:开发的初期,我们往往以简单的数据项表示简单情况,然后就会发现,我们需要把数据值变成对象,所以项目初期可能就不该在一些地方用简单类型
  • 将值对象改为引用对象:从一个类衍生很多彼此相等的实例,我们可能需要将他们替换为一个对象
  • 将引用对象改为值对象:有的时候如果引用对象变得难以使用,我们可能需要这条回头路,因为不用再考虑数据的同步了,很多时候不可变的话我们改为值对象比较合适
  • 以对象取代数组:数组应该只用于以某种顺序容纳一组对象,如果你给他的顺序赋予了含义就太蠢了,正常人也不会这么做咧
  • 复制被监视数据:
  • 使单向关联变成双向关联:两个类都需要对方的特性,但是只有一条单向连接,有的时候我们就得建立双向引用

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 981909093@qq.com

文章标题:重构

文章字数:3.6k

本文作者:泽鹿

发布时间:2019-08-28, 16:45:23

最后更新:2019-08-28, 16:45:23

原始链接:http://panyifei.github.io/2019/08/28/读书笔记/重构/重构/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏