• 《重构》--读书笔记


    第一章 重构,第一个案例

    该章主要举一个例子说明如何重构以及重构是什么
     
    重构的第一步,为即将修改的代码建立测试环境
    重构时依赖测试来体现是否有bug
     
    函数如果过长尝试把它分解,代码块越小,代码功能越容易管理,代码的处理与移动就越轻松。
    降低代码的重复量
     
    重构每次修改的幅度都很小,所以任何错误都很容易被发现
     
    变量名/函数名是代码清晰的关键,好的代码应该清楚地表达出自己的功能
    任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。
     
    临时变量不需要要及时去除
     
    用多态代替switch语句
     
    活用设计模式
     

    第二章 重构原则

    该章主要从理论说明重构原则和重构的好处
     
    重构:使用一系列手法,在不改变软件可观察行为的前提下调整其结构
    重构的目的是使软件更容易理解和修改
    添加新功能与重构需要分开进行
    重构改变软件设计,使软件更容易理解,有助于找到bug,提升编程速度
    关于何时重构可以遵从三次法则:第一次只管做,第二次做同样的事就要注意,到第三次再做同样的事就需要重构。事不过三,三则重构。
    最常见的重构时机就是想给软件添加新功能的时候。尤其是当代码的设计无法使自己添加新功能的时候。“如果用某种方式设计,添加特性会简单许多”。
    第二个重构时机就是修复bug的时候,让代码更具有可读性,帮助找到bug,当不能快速找到bug时就是需要重构的信号。
    第三个重构的时机就是代码复审的时候。(尤其是别人看自己代码的时候,会提出一些结构的改进,亲测有效)
    系统当下的行为,只是整个软件的的一部分,如果没有认清这一点,你无法长期从事编程工作。如果你只为求完成今天的任务而不择手段,导致不可能在明天完成明天的任务,那么最终还是会失败。但是,你知道今天需要什么,却不一定知道明天需要什么。(程序需要便于添加与修改,为未来考虑)
    需要重构的代码:1.难以阅读,2.逻辑重复,3.添加新行为时需要修改已有代码,4.带复杂条件逻辑
    希望代码:1.更容易阅读,2.所有逻辑都只在唯一地点指定,3.新的改动不会危及现有的行为,4.尽可能简单的表达条件逻辑
     
    间接层的好处:1.允许逻辑共享(函数多次被调用),2.分开解释意图和实现(每个函数的名字来解释他的意图),3.隔离变化(修改它不影响别的代码),4.封装条件逻辑(降低代码重复,增加清晰度)
     
    修改接口尽量让旧接口继续工作,让旧接口调用新接口,这样就不用修改别处。修改函数也是这同样
     
    将大块功能重构为封装良好的小型组件,就可以逐一对组件做出重建或重构。
    如果项目接近最后期限,就不应该重构,因为已经没时间了。这表示其实你早该进行重构。
     
    事先做好设计可以节省返工的成本。设计是开发的关键环节,编程只是机械式的低级劳动。软件的可塑性更强。除此之外,你也必须理解如何修改设计。
     
    关于性能,程序的大半时间都耗费在一小半代码身上,如果一视同仁的修改所有代码,90%的优化工作都是白费的,因为被优化的代码大多很少被执行,如果缺乏对程序的清楚认识,那么用来优化性能的时间都被浪费了。
     
    三种编写快速软件的方法:
    1.时间预算法,通常只用于性能要求极高的实时系统。分解你的设计时就要做好预算,给每个组件预先分配一定的资源----包括时间和执行轨迹。每个组件不能超出自己的预算。这种方法高度重视性能。(心律调节器等项目)
    2.持续关注法,要求任何程序员要时刻保持系统的高性能。缺点是为了提高性能使得系统难以维护。
    3.性能提升法,利用上述90%统计数据,编写构造良好的程序,不对性能投以特别的关注,直至进入性能优化阶段(通常在开发后期),到该阶段再按照某个特定的程序来调整程序性能。
     
    在性能优化阶段,应该用一个度量工具来监控程序运行(例如Unity profiler,visual studio profiler,intel vtune amplifier XE等等),会找到程序中哪部分大量消耗时间和空间,可以找出性能热点所在的一小段代码。应该集中关注这些性能热点,用持续关注法来优化它们。注意力集中在性能热点上,减少工作量。继续这个"发现热点,去除热点"的过程。
     
    优秀的程序员都会花一些时间来清理自己的代码。
     

    第三章 代码坏味道

    该章主要从几种情况来举例说明使用哪种重构方法
     
    需要培养自己的判断力,学会判断一个类内有多少实例变量算大,一个函数有多少代码算长。
     
    1.重复代码
    将重复代码合而为一。Extract Method提炼重复代码,都调用这一个方法。
     
    2.过长函数
    间接层的好处:解释能力、共享能力、选择能力-------都是小型函数支持的
    需要更积极地分解函数,并以其用途(而非实现方法)命名
    如果函数有大量参数和临时变量,运用Replace TempQuery方法来消除(看到这里是蒙逼的,后面会有方法解释吧)。
    条件表达式和循环也是需要提炼的信号
     
    3.过大的类
    如果类内数个变量名(意义)有着相同的前缀或者字尾,就可以把他们提炼到某个组件内,或者这个组件也可以作为子类。
    如果这个大类是个GUI类,就可以把数据和行为部分分开(MVC,详见之前写的关于Pure MVC框架)。可能需要两边各保留一些重复数据,并保持两边同步。
     
    4.过长的参数列
    可以叫另一个对象给你。
     
    5.发散式变化
    我们希望软件能更容易被修改,一旦被修改,要做到只在该处修改。
    如果一个类经常被修改,这就是发散式变化。
    可以将对象分为两部分,需要修改的为一部分,不需要修改的为另一部分。Extract Class
     
    6.散弹式修改
    如果要修改某处,需要在很多地方修改,这就是散弹式修改。
    可以将所有需要修改的代码放进同一个类/函数,如果无处可放就Inline Class做一个内部类。
     
    7.依恋情结
    OOP类:将数据和对数据的操作行为包装在一起。
    依恋情结就是,函数对某个类的操作高于对自己类的操作。就要把这个函数放到那个类里Move Method
    如果一个函数需要对几个类进行操作,可以把函数放到操作最多的类中,或者把函数拆分成几个小函数放到不同的类中。
    最根本的原则是:将总是一起变化的东西放在一起。
    策略模式和访问者模式破坏了这个原则,它们使你得以轻松修改函数行为,因为它们将少量需要被覆写的行为隔离开来---也多了“多一层间接性”的代价
     
    8.数据泥团
    在很多地方看到相同的数据,就需要将他们提炼到一个对象中调用。
    减少字段和参数的个数,当然可以去除一些坏味道。
     
    9.基本类型偏执
    结构类型数据允许你将数据组织成有意义的形式(对象,类,结构体,数据库的表)
    基本类型则是构成结构类型的积木块。
    对象的一个价值在于:它们模糊(甚至打破)了基本数据和体积较大的类之间的界限。可以写一些内部类小型类等。
    有的人不愿意在小任务上运用小对象(像是vector ,rect等等,虽然在Unity中这个是结构体,不过结构体也一样),可以把数据替换为对象,甚至大可以把这些对象作为数据。
     
    10.switch惊悚现身
    switch语句的问题在于重复。可以用oop的多态来解决
    将switch语句提炼到一个独立的函数中Extract Method,在Move Method搬移到需要多态性的那个类里,再决定是否使用Replace Type Code With Subclasses 或者 Replace Type Code With State/Strategy,完成了继承结构后,就可以运用 Replace Conditional with Polymorphism了。(这块没看明白,这些方法在后面再看一遍)
    扩充:消除switch的方法,可以用反射,或者Dictionary<key,Method>的方法来处理
    利用多态取代switch其实是一种面向对象的编程思想
     
    11.平行继承体系
    当为一个类增加一个子类时,也必须要为另一个类增加一个子类。
    让一个继承体系的实例引用另一个继承体系的实例。
     
    12.冗赘类
    对于几乎没用的组件,应该Inline Class作为内部类。
     
    13.夸夸其谈未来性
    过于为未来着想,加了许多不必要的抽象和钩子。
    如果某个抽象类其实没有太大作用,就用 collapse Hierarchy。不必要的委托就用Inline Class内部类来除掉。去除函数中不必要的参数,并修正函数名。
    如果有这样的函数或类,就把其测试用例也一并删掉。
     
    14.令人迷惑的暂时字段
    为特定情况而设的变量(不常用,且不易懂)归到同一个新类中
     
    15.过度耦合消息链
    当某个变量向一个对象请求另一个对象,再请求另一个对象等等,这就是消息链,一长串getThis()。这导致代码紧密耦合,一旦对象间关系发生变化,就需要大量修改。
    可以将这一系列对象变成中间件,将他们提炼到一个新函数中。
     
    16.中间件
    封装与委托
    如果一个类有超过一半的函数都委托给中间件,这就是过度运用,应该去掉这个中间件。
     
    17.狎昵关系
    两个类过于操作对方的private部分。
    可以把互相用到的部分还给对方,或者把他们的共同部分提炼到一个新的类。或者Hide Delegate(这个也要到后面才能知道是什么)
     
    18.异曲同工的类
    两个函数功能相同名字不同。只保留一个并且重新命名。
     
    19.不完美库类
    修改库的函数,就用旧函数调用新函数。
     
    20.纯稚的数据类
    类似容器的类(ICollection  .Net容器),要用Encapsulate Collection把他们封装起来(要看后面)
     
    21.拒绝的遗赠
    子类如果不想继承所有父类
    说明继承体系设计错误。
     
    22过多的注释
    注释过长,说明代码很糟糕,让人看不懂。
    或者提炼出新的函数进行功能说明
     
    --待续
    by wolf96 2017/7/30
     
    第四章 构筑测试体系
    该章讲述单元测试的重要性与方法
    (由于我是.Net开发人员,没看太多作者关于JUnit的细节)
     
    程序员编码的时间占非常小的部分,另一些时间花在设计上,最多的时间是用来调试。
    类应该包含它们自己的测试代码。
    每个类都应该有个测试函数,以它来测试这个类。
    做一个统一的整体测试,一切没问题在屏幕上输出个OK就好。确保所有的测试都完全自动化,让他们检查自己的结果。
    写好一点功能,尽快去测试,以免忘记。
     
    运用组合模式的测试套件,每个测试用例(类)都集成这个测试框架的TestClass类。
    对于测试,善用委托与反射,可以把待测函数已名字或参数传给测试类或者测试函数。
     
    每天至少执行每个测试一次。
    重构过程中可以只运行正在整理的部分代码的测试。
    单元测试提高程序员的生产率。测试的目的是为了找到现在或未来可能出现的错误。
     
     
    第五章 重构列表
    该章讲述如何构建重构列表
    重构的记录格式:
         1.名称:创建一个重构词汇表
         2.概要:简单介绍一下此重构手法使用场景
         3.动机:需要该重构的原因
         4.做法:重构的步骤
         5.范例:简单的例子
    直接用IDE替换出错机会很高,需要检查。
    许多重构手法都涉及向系统引入设计模式。
    单进程与分布式重构方法不同。
     
    第六章 重新组织函数
    该章讲述对函数的重构
    1。提炼函数
    问题:有一段代码可以提炼出来
    方法:将这对代码放入一个新的函数,函数名称解释用途
    好处:可以使代码更清晰,并可以复用。
    2.内联函数
    问题:多余的函数,代码不会被复用,行数也少,代码本身也清晰易懂
    方法:将函数代码放入调用它的地方,之后删除函数
    好处:去掉无用的间接层
    3.引入解释性变量
    问题:有一个复杂的表达式
    方法:分成几部分放入几个临时变量,以变量名解释用途
     
    4.分解临时变量
    问题:有临时变量被赋值超过一次
    方法:每次赋值都建立新的临时变量
     
     
    第七章 在对象之间的搬移性
    该章讲述如何在类之间搬移的重构方法
    1.搬移函数
    问题:类中的某个函数与该类外另一个类有更多交流
    方法:在另一个类建立一个与此相同的新函数。将旧函数变为委托函数或者完全删除。
    2.搬移字段
    问题:类中的某个字段被该类外另一个类更的使用
    方法:在另一个类新建一个字段,使该类改用这个新字段
    3.提炼类
    问题:某个类做了两个类做的事
    方法:建立一个新类,将相关字段与逻辑搬移到新类
    随着责任增加,某个类会更加复杂,变得混乱。有着大量的函数和数据,不容易理解。就要考虑哪些可以分离出去,到一个新类中。
    4.将类内联化
    问题:这个类没什么用
    方法:将特性搬到另一个类中,然后删除它。
    7.引入本地扩展
    问题:需要为服务类提供额外函数,但无法修改这个类
    方法:建立新类作为代理,让它成为服务类的子类或包装(其实就是代理模式)
     
     
    --待续
    by wolf96 2017/8/13
     
     
     
     
     
     
  • 相关阅读:
    Socket编程中的强制关闭与优雅关闭及相关socket选项
    怎样通过MSG_WAITALL设置阻塞时间,IO模式精细讲解: MSG_DONTWAIT 、 MSG_WAITALL
    RTSP、HTTP、HTTPS、SDP四种协议详解
    RTMP、RTSP、HTTP视频协议详解(附:直播流地址、播放软件)
    Idea连接服务器docker并部署代码到docker实现一键启动
    @Autowired注解和静态方法
    关于工具类静态方法调用@Autowired注入的service类问题
    @PostConstruct
    spring-boot-starter-mail技术总结
    使用SpringBoot发送mail邮件
  • 原文地址:https://www.cnblogs.com/zhanlang96/p/7259192.html
Copyright © 2020-2023  润新知