《重构 改善既有代码的设计 1》重构原则

2023-11-05

前言

重构 既有代码的设计,一本经典神书,两年前入手,一年前看了一半,感觉一般般,今天起,再次拜读,希望会有不一样的收获!

/**
* @startTime 2020-12-16 23:22
* @endTime 2020-12-16 23:59
* @startPage 1 
* @endPage 55
* @efficiency 56/1 = 56页/天
* @needDays 412/56 = 7天
* @overDay 2020-12-16 + 7天 = 2020-12-22 
*/

第1章 重构,第一个案例

所谓重构,是在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。重构是一种千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入错误的几率。本质上说,重构就是在代码写好之后改进它的设计。

任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。

第2章 重构原则

1、何谓重构

对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

重构的目的是使软件更容易被理解和修改。

越难看出代码所代表的设计意图,就越难保护其中的设计,经常性的重构可以帮助代码保持自己该有的形态。完成同一件事,设计不良的程序往往需要更多的代码,这常常是因为代码在不同的地方使用完全相同的语句做同样的事情。因此改进设计的一个重要方向就是消除重复代码,这个动作的重要性在于方便未来的修改。代码量减少并不会使系统运行更快,因为这对程序运行轨迹几乎没有任何明显影响。然而代码量减少将使未来可能的程序修改动作容易得多。

/**
* @startTime 2020-12-17 22:00
* @endTime 2020-12-17 22:50
* @startPage 56 
* @endPage 64
* @efficiency 64/2 = 32页/天
* @needDays 412/32 = 13天
* @overDay 2020-12-16 + 13天 = 2020-12-29 
*/

2、重构使软件更容易理解

让review的人更容易理解,让下一个接手你代码的人,更容易理解。也许下一个接手你代码的人就是你自己。有的时候,一段代码拿过来,是不是你自己写的,你都确定不了,对吧?

  • 重构的好处
  • 重构帮助找到bug
  • 重构提高编程速度

3、重构的时机

  • 事不过三,三则重构
  • 添加功能时重构
  • 修改错误时重构
  • 复审代码时重构

4、为什么重构有用

  1. 难以阅读的程序,难以修改;
  2. 逻辑重复的程序,难以修改;
  3. 添加新行为时需要修改已有代码的程序,难以修改;
  4. 带复杂条件逻辑的程序,难以修改;

因此,我们希望程序:

  1. 容易阅读;
  2. 所有逻辑都只在唯一地点指定;
  3. 新的改动不会危及现有行为;
  4. 尽可能简单表达条件逻辑;

间接层和重构

间接层的价值:

  1. 允许逻辑共享;
  2. 分开解释意图和实现;
  3. 隔离变化;
  4. 封装条件逻辑;

5、重构的难题

(1)数据库

数据迁移

(2)修改接口

不要过早发布接口,请修改你的代码所有权政策,使重构更顺畅。

(3)难以通过重构手法完成的设计改动

(4)何时不该重构 

/**
* @startTime 2020-12-20 11:50
* @endTime 2020-12-20 15:30
* @startPage 65 
* @endPage 102
* @efficiency 102/5 = 20.4页/天
* @needDays 412/20.4 = 20天
* @overDay 2020-12-16 + 20天 =  2020-01-04
*/

6、重构和设计

很多人都把设计看做软件开发的关键环节,而把编程看做只是机械式的低级劳动。他们认为设计就像画工程图而编码就像施工。

哪怕你完全了解系统,也请实际度量它的性能,不要臆测。臆测会让你学到一些东西,但十有八九你是错的。

7、重构和性能

首先写出可调的软件,然后调整它以获得足够的速度。

三种编写快速软件的方法:

(1)时间预算法

通常用于性能要求极高的实时系统。

分解设计时要做好预算,给每个组件预先分配一定的资源,包括时间和执行轨迹。每个组件决不能超出自己的预算,就算拥有组件之间的调度预配时间的机制也不行。

这种方法高度重视性能,对于心率调节器一类的系统是必须的,因为这样的系统中迟来数据就是错误的。但对一些管理系统来说,如此追求性能就有点过分了。

(2)持续关注法

这种方法要求任何程序员在任何时间做任何事情时,都要设法保证系统的高性能。

这种方法看起来很常见,感觉上很有吸引力,但通常不会起太大作用。

(3)性能优化阶段

编写程序时,不用将过多的精力关注在性能上,在代码开发完毕之后,会有一个性能优化阶段,一旦进入该阶段,就按照某个特定程序来调整程序性能。

在性能优化阶段,首先应该用一个度量工具来监控程序的运行,让它告诉你程序中哪些地方大量消耗时间和空间。这样就可以找出性能热点所在的一小段代码。然后应该集中关注这些热点代码,并使用持续关注法中的优化手段来优化它们。每走一步都需要编译、测试、再次度量。

8、重构起源何处

第三章 代码的坏味道

1、重复代码

同一个类中两个函数含有相同的表达式时,就要提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。

2、过长函数

间接层所带来的全部利益,解释能力、共享能力、选择能力。

应该更积极的分解函数,每当感觉需要注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名,我们甚至可以对一组甚至一行代码做这件事。哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫的这么做。关键不在于函数的长度,而在于函数“做什么”和“如何做”之间的语义距离。

(1)提炼函数

(2)以查询取代临时变量

(3)引入参数对象或保持对象完整

(4)以函数对象取代函数

将这个特函数放进一个单独对象中,如此一来局部变量就变成了了对象内的字段,然后你可以在同一个对象中将这个大型函数分解成多个小型函数。

本书在不断强调小型函数的优美动人。

(5)条件表达式和循环常常也是提炼的信号。

分解条件表达式;

将循环和其内的代码提炼到一根独立函数中。

3、过大的类

如果想利用单个类做太多事情,其内就会出现太多实例变量。一旦如此,重复代码也就接踵而至了。

可以利用提炼类将几个变量一起提炼至新类中。

4、过长参数列

5、发散式变化

6、散弹式修改

如果遇到某种变化,都必须在很多不同的类中做一些小修改的时候,你就可以将这些一起变化的代码提炼到一个新的类中。

7、依恋情节

函数对某个类的兴趣高多对自己所处类的兴趣,这时就需要转移函数的位置了。

8、数据泥团

一个好的评判方法是:删除众多数据中的一项时,其它数据有没有因而失去意义?如果它们不再有意义,这就是一个明确的信号,你就应该产生一个新对象。

减少字段和参数的个数,当然可以去除一些坏味道,但更重要的是:一旦拥有新对象,就有机会让程序散发出一种芳香。得到新对象后,你就可以着手寻找依恋情节,这可以帮你指出能够移至新类中的种种程序行为。不必太久,所有的类都将在它们小小社会中充分发挥价值。

9、基本类型偏执

对象技术的新手通常不愿意在小任务上运用小对象,像结合数值和币种的money类、电话号码、邮政编码等的特殊字符串,看似简单,此时可以运用以对象取代数据值,将原本单独存在的数据值替换为对象,从而走出传统的洞窟,进入炙手可热的对象世界。

10、switch惊悚现身

面向对象程序的一个最明显特征就是:少用switch语句。

从本质上说,switch语句的问题在于重复。你常会发现同样的switch语句散布于不同地点。如果要为它添加一个新的case子句,就必须找到所有switch语句并修改它们。 面向对象中的多态就可以带来优雅的解决方法。

11、平行继承体系

如果你发现一个类增加一个子类,必须也为另一个类相应的增加一个子类。如果你发现某个继承体系的类名称前缀和另一个继承体系名称前缀完全相同,便是闻到了这种坏味道。

消除这种重复性的一般策略是:让一个继承体系的实例引用另一个继承体系的实例。如果再接再厉运用转移方法和转移字段,就可以将引用端的继承体系消匿于无形。

12、冗余类

折叠继承体系;

将类内联化;

13、夸夸其谈未来性

14、令人迷惑的暂时字段

15、多度耦合的消息链

16、中间人

17、暧昧关系

18、异曲同工的类

19、不完美的类库

引入外加函数,即对复杂参数的封装;

引入本地扩展,即继承类库并添加你需要的方法;

20、纯稚的数据类

21、被拒绝的馈赠

以委托替代继承。

22、过多的注释

当你看到一堆注释的时候,往往是因为你的代码很糟糕,需要用注释去解释它。所以过多的注释就预示着你该对代码进行重构了。

当你感觉需要撰写注释时,请先尝试重构,试着让所有注释都变得多余。

第四章 构筑测试体系

1、自测试代码的价值

实际上编写测试代码的最有用时机是在开始编程之前。当你需要添加特性的时候,先写相应测试代码。

编写测试代码其实就是在问自己,添加这个功能需要做些什么。编写测试代码还能使你把注意力集中于接口而非实现。

2、JUnit测试框架

3、添加更多测试

  • 编写未臻完善的测试并实际运行,好过对完美测试的无尽等待;
  • 考虑可能出错的边界条件,把测试火力集中在那儿;
  • 当事情被认为应该会出错时,别忘了检查是否抛出了预期的异常;
  • 不要因为测试无法捕捉所有bug就不写测试,因为测试的确可以捕捉到大多数bug;

 

上一篇:【编写高质量代码:改善Java程序的151个建议】

下一篇:《重构 改善既有代码的设计 2》重新组织函数、数据

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《重构 改善既有代码的设计 1》重构原则 的相关文章

  • java try catch 程序流程什么时候中断?

    你好 我对 Java 中的异常处理不太熟悉 所以 正如主题在基本 try catch 块中所述 当我在 Try 块中捕获异常时 程序流程何时中断 try some code that raises an Exception catch Ex
  • JNA - EnumProcessModules() 未返回所有 DLL?

    我试图从游戏中读取坐标 当我在通过 OpenProcess 接收的 HANDLE 上使用 ReadProcessMemory 以及我在 CheatEngine 中找到的内存时 效果非常好 例如 如果我知道正在运行的进程中的浮点值是0x5AB
  • 如何为java注释处理器编写自动化单元测试?

    我正在尝试使用 java 注释处理器 我可以使用 JavaCompiler 编写集成测试 事实上我现在正在使用 hickory 我可以运行编译过程并分析输出 问题 即使我的注释处理器中没有任何代码 单个测试也会运行大约半秒 对于以 TDD
  • 从字符串生成密钥?

    我需要从字符串生成一个密钥 以便我始终可以从同一字符串创建相同的密钥 具体来说是一个Key对象 这样我就可以用它来创建Cipher进而创建SealedObject 这在 Java 中可行吗 我应该考虑什么类 方法组合才能做到这一点 对于 A
  • Android 游戏偶尔出现延迟

    我正在用 Java 制作一个简单的 Android 游戏 我注意到每 20 40 秒就会出现一些烦人的延迟 首先 我认为它们是由垃圾收集器引起的 但当我检查 LogCat 时 我发现游戏滞后时没有垃圾收集 每当游戏开始滞后时 我都会标记日志
  • net.sf.jasperreports.engine.JRRuntimeException:java.io.IOException:无法读取字体数据

    我正在尝试通过 JasperReport 创建 PDF 报告 但读取字体数据时出现问题 我有 jasperreports extension properties 和 ClassPath 中的相关 TTF 文件 这是错误 java io I
  • spring mvc 跟踪引用页面

    在基于注释的弹簧控制器中 如果用户正在url com first page并点击一个链接或提交一份表格指出url com second page 如何制作second page知道url of first page所以这样second pa
  • 如何在 JdbcTemplate 中创建 mySQL 存储过程

    背景 为了解决 MySql 中某些语句只允许在存储过程中出现的问题 我尝试在 JdbcTemplate 提交的 sql 中创建 运行然后删除存储过程 一个简单的例子是 这恰好是在 Spring Boot 中 Service public c
  • 反应式 Spring Webflux REST 控制器内部重定向

    我正在为 spring 反应项目创建简单的控制器服务器 在设置重定向到另一个位置时 我在调用时发现错误http localhost 8080 There was an unexpected error type Internal Serve
  • selenium webdriver 中的多个程序执行不起作用

    Selenium WebDriver 中的多个程序执行不起作用 我编写了 1 个 testNG xml 文件和 2 个 java 类 我尝试从 xml 文件运行这两个 java 类 但这不起作用 XML代码
  • JFrame Glasspane 也优于 JDialog,但不应该

    我有一个带有 Glasspane 的 JFrame 未装饰 该框架打开一个 JDialog 也未装饰 也有一个 glassPane 并隐藏自身 setVisible false Glasspanes 通过 setGlassPane 设置 对
  • bufferedinputstream 中标记读取限制有什么用

    我是Java流的新手 我想读取特定的文件内容 然后需要从头开始读取 我创建了一个 BufferedInputStream 但我对 BufferedInputStream mark int markLimit 的文档感到困惑 文档说 publ
  • Java字符串查找和替换的最佳方法?

    我正在寻找 Java 中字符串查找和替换的最佳方法 这是一句话 我的名字叫米兰 人们都知道我叫米兰瓦西奇 我想用 Milan Vasic 替换 Milan 弦 但在我已经有 Milan Vasic 的地方 情况不应该是这样 搜索 替换后的结
  • Elasticsearch - EdgeNgram + 突出显示 + term_vector = 不好的突出显示

    当我使用带有edgengram min 3 max 7 front term vector with positions offsets的分析器时 文档包含文本 CouchDB 当我搜索 couc 时 我的亮点是 cpu 而不是 couc
  • Java 验证日期为 yyyyMMddHHmmss

    我想在java中验证给定的日期格式为yyyyMMddHHmmss 状况 应符合格式 yyyyMMddHHmmss 它应该验证当前日期 它应该验证与当前小时有 3 小时或 3 小时差异的小时数 如果满足所有三个条件 Java 方法应返回 tr
  • 在 REST Web 服务中接受逗号分隔值

    我正在尝试接收 REST URI 中以逗号分隔值形式的字符串列表 示例 http localhost 8080 com vogella jersey first rest todo test 1 abc test 其中 abc 和 test
  • ASTParser:解析绑定后查找声明节点

    我创建了一个启用了绑定的 AST 当我稍后解析绑定时 我得到了一个有效的 ITypeBinding 但是 当我想要获取绑定的声明 Node 时 它 总是返回 null 除非 ITypeBinding 在 sourceFile 中声明 这是我
  • 如何使 JScrollPane 与嵌套 JPanel 一起正常工作?

    我正在使用 NetBeans 在 Java 中构建 Swing 应用程序 但我遇到布局问题 我的主框架包含一个JScrollPane其中包含一个JPanel called contentPanel其中又包含一个JPanel called l
  • Struts2中的变量声明

    Struts2中如何声明变量并为该变量赋值 使用设置标签
  • 使用正则表达式匹配阿拉伯文文本

    我试图使用正则表达式仅匹配阿拉伯语文本 但出现异常 这是我的代码 txt matches P Arabic 这是例外情况 线程 main 中的异常 java util regex PatternSyntaxException 索引 9 附近

随机推荐

  • 使用vsomeip遇到的一些问题

    1 接口设计 在编写fdepl文件时 要先写attribute 在写method 再写broadcast 不能像fidl文件 穿插着写 否则编译不过 2 在运行程序时 有时候会遇到无法连接的问题 需要把 tmp vsomeip 0 这一系列
  • 宏定义中有浮点数_算法笔记

    2 9 2浮点数的比较 由于计算机当中采用有限位的二进制编码 因此浮点数在计算机当中的存储并不总是精确地 例如在大量的计算以后 一个浮点类型的数3 14在计算机当中可能存储成3 1400000000001 也有可能存储成3 13999999
  • 动态NFT的构建、部署和出售

    原文地址 NFT是只有在区块链领域里才存在的工具 有着广泛的应用和机遇 ERC721代币标准可以构建收藏品 独立代币 票据 游戏等多种应用 对于那些想要参与构建的开发者来说 一个动态和随机的NFT是一个很好的开始 但我们现在可以用它做什么
  • 【机器学习实战】11、利用SVD简化数据

    文章目录 14 1 1 隐形语义索引 14 1 2 推荐系统 14 2 矩阵分解 SVD矩阵分解 14 3 利用python实现SVD 14 4 1 相似度计算 14 4 2 基于物品的相似度还是基于用户的相似度 14 4 3 推荐引擎的评
  • 【数据挖掘】数据清洗

    什么是数据清洗 数据清洗是指发现并纠正数据文件中可识别的错误的最后一道程序 包括检查数据一致性 处理无效值和缺失值等 与问卷审核不同 录入后的数据清理一般是由计算机而不是人工完成 数据清洗的步骤 缺失值的处理 无效值的处理 统一规格 纠正错
  • 断点续传与差分升级

    断点续传的原理 基于STM32单片机的差分升级 增量升级 算法 OTA 差分升级 云端一体化差分升级 AliOS Things物联网升级 利器 详解STM32在线IAP升级 单片机差分升级算法 STM32 M0 M3 M4等芯片都适用 Al
  • 【DICOM医学影像1】数据格式存储于显示,基本知识科普指南

    DICOM Digital Imaging and Communications in Medicine 数据格式 是医学影像存储中的标准格式 无论是X光 CT 还是MRI等等影像 采集的原理不同 但是存储的格式一般都是统一的 本文就对DI
  • 杂记——4.书写spring时出现的Error creating bean with name ‘user0‘ defined in file错误

    目录 1 问题描述 2 情况描述 3 解决方法 4 问题原因 1 问题描述 当我们运行一个spring程序时 出现下图的错误 重点语句 Error creating bean with name user011 defined in fil
  • 腾讯在线教育互动课堂——Demo调试过程记录

    官方文档地址 https cloud tencent com document product 680 17888 Demo调试 不像集成使用 不需要完全按照文档一步步处理 基本的代码 集成在下载下来的demo项目上都已经写好了 以下记录以
  • 550种Blender风格化笔刷素材

    550种Blender风格化笔刷素材 550 Blender刷风格化版 包括4K阿尔法 大小解压后 3G 信息 一个伟大的自定义风格化的刷子使用Blender收集 Alphas包含在其他软件中使用 ArtStation MEGAPACK 5
  • 小程序中 rich-text 显示富文本

    在使用 rich text 来显示fuwq富文本时需要注意后台返回的数据是一个网页转义字符 直接使用rich text的话是无法正常解析的 那么需要使用下面的一个方法进行反转义即可 小程序里的转义方法 escape2Html functio
  • More Effective C++

    链接 https pan baidu com s 1oIns7Z7CWD6zAz17IFImWw 提取码 4stq Scott Meyers大师Effective三部曲 Effective C More Effective C Effect
  • C/C++编程笔记:C++中的指针与引用,又在什么时候使用?

    C和C 支持与大多数其他编程语言不同的指针 其他语言包括C Java Python Ruby Perl和PHP 从表面上看 引用和指针非常相似 都用于使一个变量提供对另一变量的访问 两者都提供了许多相同的功能 因此通常不清楚这些不同机制之间
  • 8086/8088的寻址方式

    根据操作数所在位置将寻址方式分为 立即寻址 寄存器寻址 存储器寻址 I O端口寻址 立即寻址 操作数位于指令区 代码段 如 Mov dx 2100H Mov AX A 源操作数不能超过目的操作数的表数范围 必须符合数据类型相匹配的原则 立即
  • 多数CEO预计受疫情影响未来半年收入将下降;上海国际酒店投资加盟展将延期

    全球抗击新冠疫情 关于COVID 19商业影响的新YPO行政总裁全球调查发布 由130个国家超过29000位首席执行官组成的全球领导力社区YPO进行了一项全会员调查 以了解COVID 19的商业影响 了解首席执行官由于这一新的商业现实而采取
  • CSDN博客修改不了头像的最新解决方法

    自己的博客不能改头像 清理缓存 换IE浏览器 都不行 以前有类似经历 可以在手机APP上修改头像 然后自动同步了 下载 CSDN APP 左上角 个人中心 点击 头像 修改就好了 我修改后没立即出来 延迟可能
  • C + + 使用小括号/大括号直接赋值,又叫列表初始化。简介

    C 使用小括号 大括号直接赋值 又叫列表初始化 简介 C 可以使用 小括号 大括号 直接赋值 并且 兼容了 C风格 的等号 赋值 C 使用小括号 大括号直接赋值 又叫列表初始化 简介 C 中 我们可以使用小括号直接赋值的方式 将多个值赋给一
  • Vue中使用动画

    在Vue中使用动画效果 1 使用transition标签包裹需要动画显示的内容 1 1 默认名的方式
  • java并发编程笔记(五)--共享模型之无锁

    1 无锁解决线程安全问题 就是使用CAS 利用乐观锁的不断确认 来保证线程安全 乐观锁时原子系列类的方法 使用的时候需要创建原子系列对象 创建原子整数对象 AtomicInteger balance new AtomicInteger 举个
  • 《重构 改善既有代码的设计 1》重构原则

    前言 重构 既有代码的设计 一本经典神书 两年前入手 一年前看了一半 感觉一般般 今天起 再次拜读 希望会有不一样的收获 startTime 2020 12 16 23 22 endTime 2020 12 16 23 59 startPa