重构是对软件内部结构的一种调整,目的是在不改变软件之可察性前提下,提高其可理解性,降低其修改成本。关于重构的至理明言如下:
-
任何一个傻瓜都能写出计算器可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员;
-
事不过三,三则重构;
-
当你接获bug提报,请先撰写一个单元测试来揭发这个bug;
-
当你感觉需要撰写注释,请先尝试重构,试着让所有的注释变得多余;
-
当你发现自己需要为程序增加一个特性,而代码结构使你无法方便的这样做,就先重构那个程序;
-
重构之前,必须建立一套可靠的测试机制;
-
写软件就像种树,优秀的程序员挖成小坑后随及填好,继续挖下一个,只会产生一系列小坑,不会有大坑,
菜鸟则不会意识到所挖的坑正在变大,还是不停的挖,直到自己掉进大坑,爬不出来,陷入无尽的痛苦深渊;
-
开发时间越长,越能体会垃圾代码的痛苦,却不知道如何改进;
-
Kent Beck:我不是一个伟大的程序员,我只是个有着一些优秀习惯的好程序员而已;
变量(Variable)
不要定义一个临时变量多次重复使用,临时变量定义仍然应该可以自解释,从变量名称能够很好的理解变量的含义和作用。在定义一个临时变量后需要有一段业务逻
辑才能够完成对临时变量的赋值的时候,可以考虑将这段逻辑抽取到一个独立的方法。
double
getPrice() {
int basePrice = _quantity * _itemPrice;
double discountFactor;
if (basePrice > 1000) discountFactor = 0.95;
else discountFactor = 0.98;
return basePrice * discountFactor;
}
重构为:
double getPrice() {
return basePrice() * discountFactor();
}
private int basePrice() {
return _quantity * _itemPrice;
}
private double discountFactor() {
if (basePrice() > 1000) return
0.95;
else return 0.98;
}
当遇到复杂的表达式的时候,需要引入解释变量,因为复杂的表达式很难进行自解释。
if ( (platform.toUpperCase().indexOf("MAC") > -1)
&&
(browser.toUpperCase().indexOf("IE") > -1)
&&
wasInitialized() && resize
> 0 )
{
// do something
}
重构为:
final boolean
isMacOs
= platform.toUpperCase().indexOf("MAC") >
-1;
final boolean isIEBrowser =
browser.toUpperCase().indexOf("IE")
> -1;
final boolean
wasResized = resize >
0;
if (isMacOs
&& isIEBrowser
&& wasInitialized()
&& wasResized) {
// do something
}
减少对全局变量的使用,第一个是全局变量的生命周期很难控制,资源本身无法得到很快的释放,其二是过多使用全局变量导致在调用方法的时候很难完全清楚方法
说需要的入口数据信息,其三,多处都可以对全局变量赋值,我们很难立刻定位到当前全局变量的值来源自哪里?
分解方法(Extract Method)
一个较大的方法往往也会分为多个小的段落,step1,step2,step3,在每一个步骤都会考虑添加注释说明。而这些相对较为独立的步骤就可以分解
为不同的方法,在分解后方法名可以自解释方法的功能而不再需要额外的注释。在一个类里面如果方法里面有一段代码在多个方法中重复出现,需要抽取该类的公用
方法。在多个不同的类中有一段代码重复出现,需要考虑将公用代码放到公用类中形成公用方法。
方法名需要很好的自解释方法的功能,方法的返回尽量单一,方法的入口参数太多的时候应该考虑使用集合,结构或数据对象进行参数的传递。参数的传递可能出传
递的是引用,但不要去修改入口参数的值。
不要因为一个方法里面只有一行,两行很短而不考虑去分解,分解的时候更多的是考虑代码的自解释性。代码本身不是解释的技术实现机制,而是解释的业务规则和
需求。如果代码不是解释的业务规则和需求,那么其它人员就很难快速理解。
引入方法对象来取代方法,当发现一个方法只用到该类里面的几个关键属性,方法和类里面其它的方法交互很少,输出单一。由于该方法和这几个属性内聚性很强而
和该类其它部分松耦合,因此可以考虑将方法和这部分属性移出形成一个单独的方法对象。
移动方法,类的职责要单一,一个类的方法更多用到了别的类的属性,这个方法可能更适合定义在那个类中。
class Account...
private AccountType
_type;
private int
_daysOverdrawn;
double overdraftCharge()
{
if (_type.isPremium()) {
double result = 10;
if (_daysOverdrawn > 7) result += (_daysOverdrawn -
7) * 0.85;
return result;
}
else return _daysOverdrawn * 1.75;
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result +=
overdraftCharge();
return result;
}
重构为:
class
Account...
private AccountType _type;
private int _daysOverdrawn;
double overdraftCharge() {
return _type.overdraftCharge(_daysOverdrawn);
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0)
result += _type.overdraftCharge(_daysOverdrawn);
return result;
}
class
AccountType...
double overdraftCharge(Account account) {
if (isPremium()) {
double result = 10;
if (account.getDaysOverdrawn() > 7)
result += (account.getDaysOverdrawn() - 7) * 0.85;
return result;
}
else return account.getDaysOverdrawn() * 1.75;
}
分解类(Extract Class)
类的职责的划分不容易在初次设计时就准确把握,所以在编码时重构是必要的。职责定位不清!——典型特征是拥有太多的成员变量;而在这里面最重要的就是职责
要单一,属性和方法是否合适的类中。如果不是就需要考虑分解或合并,扩展类的功能,或者抽象相应的接口。面向对象的设计原则如下:
1.单一职责原则 (SRP) - 就一个类而言,应该仅有一个引起它变化的原因
类的职责要单一,类里面的方法只做类的职责范围里面的事情。MVC即是一种粗粒度的职责话费,模型类重点是提供数据,控制类重点是处理业务逻辑,而V视图
类则是关注数据获取后的呈现。
数据和数据操作可以考虑分解,如形成专门的DTO数据传输对象类。界面类和界面数据提供类也可以考虑分离,如形成专门的Facade层专门负责数据的准备
和形成。界面层不应该有太多的数据处理操作。
当发现一个大的类里面的属性和方法存在明细的分组特性的时候,而且分组直接松散耦合,需要考虑分解为多个类。
引入方法对象来取代方法,当发现一个方法只用到该类里面的几个关键属性,方法和类里面其它的方法交互很少,输出单一。由于该方法和这几个属性内聚性很强而
和该类其它部分松耦合,因此可以考虑将方法和这部分属性移出形成一个单独的方法对象。
胖接口也是违反职责单一,胖接口会导致所有实现接口的类都Override所有的接口方法,而有些接口方法往往是子类并不需要的。因此对于胖接口仍然要从
职责的角度对接口进行拆分。
2.开放——封闭原则 (OCP)- 对扩展开放,对修改封闭
当发生变化时,只需要添加新的代码,而不必改动已经正常运行的代码:软件人的梦想!而要达到这个目的,关键是要能够较为准确的预测业务变化会导致的可能会
发送变化的模块或代码。
3.Liskov替换原则 (LSP)
子类型必须能够替换掉他们的基类型。正是子类型的可替换性,才使得使用基类类型的软件无须修改就可以扩展。案例参考正方形驳论。矩形的合理假设:长、宽可
以独立变化;而正方形的合理假设:长、宽始终相等。因此正方形并不能从矩形继承。
4.依赖倒置原则 (DIP) - 高层模块不应该依赖于低层模块;抽象不应该依赖于细节。
依赖倒置原则的重点是高层模块类不要去依赖底层模块的类,而应该去依赖接口,特别是当我们预见到底层模块的类本身可能会扩展和变化的时候。这样在变化的时
候最大的好处就是高层类和接口不用变化。
类是否考虑抽象为接口,一方面是根据LSP原则进行重构,一方面是需要观察我们建立的类,是否有多个类本身存在相同的行为或方法,如果存在则需要考虑抽象
接口。
分享到:
相关推荐
编程,游戏开发必读书--重构;编程,游戏开发必读书--重构
【敏捷软件开发第三讲——重构】是广州大学华软软件学院软件工程系的一堂课程,主讲教师谭翔纬在课件中详细介绍了重构的概念、原则、技巧以及它在软件开发中的重要性。 **重构介绍** 重构是软件开发中的一个重要...
改善既有的代码-重构,改善既有的代码-重构
重构是对软件内部结构的一种调整,目的是在不改变软件之可察性前提下,提高其可理解性,降低其修改成本。关于重构的至理明言如下:任何一个傻瓜都能写出计算器可以理解的代码,唯有写出人类容易理解的代码,才是优秀...
在IT行业中,软件重用和系统重构是两个关键的概念,它们对于提高开发效率、降低维护成本以及提升软件质量具有至关重要的作用。让我们深入探讨这两个主题。 **软件重用(Software Reuse)** 软件重用是将已开发的、...
《软件架构设计-基于重构》是一本专注于软件开发中架构改进和优化的著作。它主要探讨如何通过重构技术来提升现有系统的架构质量,确保软件的可维护性、扩展性和性能。重构是软件开发过程中的一个重要环节,它并不...
EXCEL报表柱形图-46-重构数据柱形图.xlsx
深信服_信服云盾--重构入云业务安全边界_产品彩页.pdf
这一过程旨在提高代码的可读性、可维护性和整体质量,是软件开发中不可或缺的重要环节。 重构的核心理念在于通过一系列小步骤来逐步优化代码,而不是一次性进行大规模的改动。这些小步骤包括提取方法、移动函数、...
基于以太坊的通用积分系统案例-重构
Refactoring-重构_改善既有代码的设计
重构和设计模式都是软件开发中非常重要的概念,它们不仅有助于提高代码的质量,还可以提高程序员的编程技能和思维能力。通过深入理解和掌握重构和设计模式,程序员可以编写出更加健壮、灵活和易于维护的代码,从而...
总之,重构是软件开发过程中的重要环节,它旨在通过改进代码结构来提升代码质量,解决各种代码坏味道,以适应需求的变化和软件的持续演进。理解和掌握重构的原则和技术,对于任何软件开发者来说都是至关重要的。
584 千锋Python教程:47-重构IP代理中间件1.mp4
585 千锋Python教程:48-重构IP代理中间件2.mp4
基于分解-重构混合模型的期货价格预测及末端效应控制研究
它有助于提升软件质量,降低维护成本,同时提高代码可读性和可测试性。在EasyMonitor的重构过程中,我们可能会关注以下几个关键点: 1. **模块化设计**:将复杂的系统拆分成小而独立的模块,每个模块负责特定的功能...
软件设计是演进过程,而重构是设计演进的基本方法。重构是指不改变软件行为的前提下,修改程序内部结构。重构说简单,做不简单。首先,需要知道代码的好坏,即代码异味,设计原则等。其次,需要以自动测试作为保障。
在装置方面,"重构媒体的装置"可能指的是硬件设备,如编码器、解码器,或者是嵌入在各种设备中的软件模块,如智能手机、电视、电脑等的多媒体处理器。这些装置需要具备足够的计算能力和算法支持,以执行媒体的重构...