读书「重构」

设计一个「类」的时候有什么原则

  • 终极目标:提升代码的可理解性。
  • 一个类应该是一个清楚的抽象,处理一些明确的责任。
  • 封装,每个对象应该尽可能少了解系统的其他部分。这样一旦发生变化,需要了解这一变化的对象就会比较少。
  • 修改函数和查询函数分开设计,在查询函数里不要做任何修改,同时最好用 get 和 set 命名。

重构的基本原则?

  1. 添加新功能,和重构不可同时进行,重构时不加新功能,加新功能时不重构。
  2. 重构时不应该添加任何测试。
  3. 小步前进,频繁测试。
  4. 一个函数应该且只应该完成一件事情。

对一个对象/函数来说,哪些地方很可能需要重构?

  1. 拥有太多实例变量
  2. 有超过百行的函数
  3. 函数的参数超过三个
  4. 牵一发动全身,如果经常一个改动需要在许多不同的类内做出小修改。
  5. 如果有些数据项经常会成群地出现在一起,很可能需要拥有他们自己的对象。判断方法是删掉这些数据项中的一项,如果其他的数据立刻变得没有意义,那么说明他们应该在一起。

重构的方法有哪些?

  1. 分解函数
    • 当看到一个过长的函数或者一段需要注释才能让人理解用途的代码,把这段代码放到一个独立函数里面去
    • 函数命名应该以「做什么」而不是「怎么做」来命名。
  2. 用对象取代函数
    • 如果有一个大型函数,其中对局部变量的使用使你无法使用Extract Method,考虑把这个函数放进一个单独的对象中。然后再分解函数。
  3. 移动类字段
    • 如果一个 A 类里的一个字段与 B 对象有更多的交流:经常被后者调用,考虑把 A 中的这个字段放到 B 里。
  4. 移动类函数
    • 与移动类字段相似:如果一个 A 类里的一个函数与 B 对象有更多的交流:或者调用后者,或者被后者调用,考虑把 A 中的这个函数放到 B 里。但优先考虑移动类字段。
  5. 分解类
    • 如果类的责任过于复杂,考虑分解这个类,抽象出一个新类。
    • 一个方法是思考如果把这个函数或者变量去掉之后,是不是其他的函数或变量就没有意义了?
    • 另一个方式是某些特性需要用一种方式子类化,而另一些特性需要用另一种方式子类化,那就意味着这个类需要分解。
  6. 隐藏委托(Delegate)
    • 如果某个 Client 必须通过对象 A 的某个字段获得另一个对象 B,然后调用 B 的函数,那么 Client 就必须知道这一层委托关系,如果这一层委托关系发生变化,就不得不通知 Client,可以在把在 A 中放置一个简单的委托函数,将委托关系隐藏起来,去掉这一层依赖。
    • 当 Client 调用 B 的所有函数都被 A 承包了,Client 也就不需要知道 B 对象了。
    • 隐藏委托也是一把双刃剑,会使委托类 A 非常臃肿,这时候直接调用 B 反而可能更好,掌握合适的隐藏委托程度。
  7. 外加函数
    • 尽量不要重载原始类函数,如果需要增加新功能,可以继承原始类,增加新函数。
  8. 公开的字段自封装
    • 用 get 和 set 获取值。Python 并没有 Public 关键词,因此 Python类的所有属性都是可以直接访问的,应用这个设计理念,最好把 Python 类的所有字段统一在开头加上_以示私有变量,公开的变量用 @property 装饰器包装。
    • 如果字段是一个集合(list,set, dict等),谨慎对待 set 函数,需要的话,用 add,remove 会比 set 要更好。
  9. 简化条件表达式
    • 尽管一个函数最好只有一个出口,但为了这一个出口的设计而加上多重 if else 判断并不一定划算。
    • 多重 if else 用来处理异常或者说处理完了已经对函数后半部分没有兴趣的话,可以直接 return 或者抛出异常。
    • switch case 的情况下,使用「多态」设计模式,将条件表达式的每一个分支放进一个子类内的函数实现,然后将原始函数声明为抽象函数。
  10. 使用断言
    • 某些代码只有在某个条件为真的时候才能正常运行,此时与其用注释,不如使用断言来标明。
    • 不要滥用断言,只用它来检查「一定必须为真」的条件。
    • 如果断言的约束条件不满足的情况下,程序依然可以正常运行,考虑拿掉断言。

参考

Martin Fowler 重构——改善既有代码的设计

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

Java与C/C加加的区别

Java 是由 C++发展而来的,保留了 C++的大部分内容,其编程方式类似于 C++。但 Java 的句法更清晰、规模更小、更易学。Sun 公司对多种程序设计...

7146
来自专栏AlgorithmDog的专栏

为了 1% 情形,牺牲 99% 情形下的性能:蜗牛般的 Python 深拷贝

最近使用 Python 一个项目,发现 Python 的深拷贝 copy.deepcopy 实在是太慢了。 ? 相关背景 在 Pytho...

3225
来自专栏JavaQ

烂代码吐槽汇 | 奇葩命名

代码首先是给人看的,其次才是给机器看的。 烂代码特征:可读性差、逻辑混乱、性能低下。 1.奇葩项目(模块)名 项目(模块)名称使用汉语拼音、英汉双拼、超长的字母...

3325
来自专栏take time, save time

你所能用到的数据结构之番外篇---逆袭的面向对象(一)

     对于番外篇,我深刻能明白在大多数人眼里就和电视剧的广告一样,说实话,我也不喜欢这种感觉,因为这样会让人觉得是在欺骗消费者啊~~~阿西巴~~~但是我实在...

3438
来自专栏猿人谷

[你必须知道的.NET] 第四回:后来居上:class和struct

本文将介绍以下内容: • 面向对象基本概念 • 类和结构体简介 • 引用类型和值类型区别 1. 引言 提起class和struct,我们首先的感觉是语法几乎相...

18610
来自专栏magicsoar

确保你想要修改的char*是可以修改的

void change(char *source) { source[0] = 'D'; cout<<source<<endl; 考虑一下,你有...

1915
来自专栏C语言及其他语言

【编程经验】printf专题:你可能不知道的printf用法【文末有福利】

你可能不知道的printf用法 ? 不少小伙伴反映C语言只会用printf函数,但一些参数、返回值什么的还不了解,所以今天总结一二,给大家总结和梳理一下...

2818
来自专栏HappenLee的技术杂谈

C++雾中风景9:emplace_back与可变长模板

emplace_back方法最大的改进就在与可以利用类本身的构造函数直接在内存之中构建对象,而不需要调用类的拷贝构造函数与移动构造函数。

762
来自专栏java一日一条

Java面试参考指南(一)

Java是一种基于面向对象概念的编程语言,使用高度抽象化来解决现实世界的问题。 面向对象的方法将现实世界中的对象进行概念化,以便于在应用之间进行重用。例如...

1523
来自专栏大闲人柴毛毛

剑指 offer代码解析——面试题32统计1到n中1出现的次数

本题的分析过程均在代码注释中: /** * 题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。 * @author 大闲人柴毛毛 * ...

3566

扫码关注云+社区