重构-改善既有代码的设计:函数重构-重新组织函数的九种方法(四)

         函数过长或者逻辑太混乱,重新组织和整理函数的代码,使之更合理进行封装。

目录

一、函数逻辑相关

1. Extract Method 提炼函数:由复杂的函数提炼出独立的函数或者说大函数分解成由小函数组成

2. Inline Method 内联函数直接使用函数体代替函数调用

3. Replace Method with Method object 函数对象取代函数大函数变成类

二、函数局部变量重构

4.Inline Temp 内联临时变量表达式代替临时变量

5.Replace Temp with Query 以查询函数代替临时变量独立函数代替表达式

6.Introduce Explaining Variable 引入解释性变量复杂表达式分解为临时解释性变量

7. Split Temporary Variable 分解临时变量临时变量不应该被赋值超过一次

三、函数参数重构和算法重构

8.Remove Assigments to Parameters 移除对参数的赋值不要对参数赋值

9.Substitute Algorithm 替换算法函数本体替换为另一个算法

一、函数逻辑相关


1. Extract Method 提炼函数


提炼函数:(由复杂的函数提炼出独立的函数或者说大函数分解成由小函数组成)。反向重构是内联函数。

你有一段代码可以被组织在一起并独立出来。将这段代码放进一个独立函数,并让函数名称解释该函数的用途

void printOwing() { 
	//print banner
	System.out.println(“*********”);
	System.out.println(“Banner”);
	System.out.println(“*********”);
	//print details
	System.out.println ("name: " + _name); 
        System.out.println ("amount " + getOutstanding()); 
} 

void printOwing()  {
    printBanner(); 
    printDetails(getOutstanding()); 
} 

Void printBanner()  {
    //print banner
    System.out.println(“*********”);
    System.out.println(“Banner”);
    System.out.println(“*********”);
}
void printDetails (double outstanding)   { 
    System.out.println ("name: " + _name); 
    System.out.println ("amount " + outstanding); 
} 
void printDetails (double outstanding)   { 
    System.out.println ("name: " + _name); 
    System.out.println ("amount " + outstanding); 
} 
         过长的函数或者一段需要注释才能让人理解用途的代码 ,就应该将这段代码放进一个独立函数中。 简短而命名良好的函数的好处:
1)如果每个函数的粒度都很小,那么函数被复用的机会就更大;
2)这会使高层函数读起来就想一系列注释,组织逻辑更清晰。
3)如果函数都是细粒度,那么函数的覆写也会更容易些。
一个函数多长才算合适?长度不是问题,关键在于函数名称和函数本体之间的语义距离。如果提炼可以强化代码的清晰度,那就去做,就算函数名称必提炼出来的代码还长也无所谓。

2. Inline Method 内联函数


内联函数:(直接使用函数体代替函数调用 ) 一个函数调用的本体与名称同样清楚易懂。在函数调用点插入函数体,然后移除该函数

int getRating() {
	return moreThanfiveLateDeliverise() ? 2 : 1;
}
bool moreThanfiveLateDeliverise() {
	return _numberOfLateLiveries > 5;
}

               

int getRating(){
     return _numberOfLateLiveries > 5 ? 2 : 1;
}

     有时候你会遇到某些函数,其内部代码和函数名称同样清晰易读。也可能你重构了该函数,使得其内容和其名称变得同样清晰。果真如此,你应该去掉这个函数,直接使用其中的代码。间接性可能带来帮助,但非必要的间接性总是让人不舒服。
     另一种需要使用Inline Method (内联函数)的情况是:你手上有一群不甚合理的函数。你可以将它们都内联到一个大型函数中,再从中提炼出合理的小函数。实施Replace Method with Method Object (以函数对象取代函数)之前这么做,往往可以获得不错的效果。你可以把所要的函数的所有调用对象的函数内容都内联到函数对象中。比起既要移动一个函数,又要移动它所调用的其他所有函数,将整个大型函数作为整体来移动比较简单。
    如果别人使用了太多间接层,使得系统中所有函数都似乎只是对另一个函数的简单委托,造成在这些委托动作之间晕头转向,那么就使用 Inline Method (内联函数)。
     当然,间接层有其价值,但不是所有间接层都有价值。试着使用内联手法,可以找出那些有用的间接层,同时将那些无用的间接层去除。


函数对象代替函数:(大函数变成类)你有一个大型函数,其中对局部变量的使用使你无法采用 Extract Method (提炼函数)。将这个大型函数放进一个单独对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数。

class Order...
double price() { 
    double primaryBasePrice;
    double secondaryBasePrice; 
    double tertiaryBasePrice; 
    // long computation; ... 
} 
                                                 
 

    或者可以采用static method

        局部变量的存在会增加函数分解的难度。如果一个函数之中局部变量泛滥,那么想分解这个函数是非常困难的。Replace Temp with Query (以查询取代临时变量)可以帮助你减轻这一负担,但有时候你会发现根本无法拆解一个需要拆解的函数。这种情况下,应该使用函数对象。

二、函数局部变量重构


4.Inline Temp 内联临时变量


内联临时变量:(表达式代替临时变量)你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。 将所有对该变量的引用动作,替换为对它赋值的那个表达式自身      

内联临时变量的原因:

1、作为“以查询取代临时变量”的一部分使用

2、临时变量妨碍了其他的重构手法

double basePrice = anOrder.BasePrice();
return basePrice(>1000);
                                                           
                     
return (anOrder.BasePrice() >1000);

        Inline Temp(内联临时变量)多半是作为Replace Temp with Query(以查询取代临时变量)的一部分使用的,所以真正的动机出现在后者那里。唯一单独使用Inline Temp(内联临时变量)情况是:你发现某个临时变量被赋予某个函数调用的返回值。
       一般来说,这样的临时变量不会有任何危害,可以放心把它留在那。但如果这个临时变量妨碍了其他的重构手法,例如 Extract Method (提炼函数),就应该将它内联化。

5.Replace Temp with Query 以查询函数代替临时变量


以查询代替临时变量:(独立函数代替表达式)你的程序以一个临时变量保存某一个表达式的运算效果。将这个表达式提炼到一个独立函数中。将这个临时变量的所有引用点替换为对新函数的调用。此后,新函数就可以被其他函数调用。

查询函数的作用

1.解释性(对应用途的函数名称就是良好的注释)

2.可读性(减小父函数的体积,在查询函数中可将逻辑表达式分块)

3.方便重构(当临时变量影响到了重构步骤,用查询函数替代)
 

double basePrice = _quantity*_itemPrice;
if (basePrice > 1000) {
	return basePrice * 0.95;
else 
	return basePrice * 0.98;

                                                             

if (basePrice() > 1000)
	return basePrice() * 0.95; 
else 
	return basePrice() * 0.98; 
……
double basePrice() { 
	return _quantity * _itemPrice; 
} 
function void init() {
  boolean flag = month >=6 && month <= 9
  if(flag) {
    console.log('夏天') //处理逻辑
  }
}
//repalce temp with query, 改进后的init函数
function void init() {
  if(isSummer()) {
    console.log('夏天') //处理逻辑
  }
}
 
function boolean isSummer() {
  boolean flag = month >=6 && month <= 9
  return flag
}

    临时变量的问题在于:它们是暂时的,而且只能在所属函数内使用。由于临时变量只是在所属函数内可见,所以它们会驱使你写出更长的函数,因为只有这样你才能访问到需要的临时变量。如果把临时变量替换为一个查询,那么同一个类中的所有函数都可以获得这份信息。这将带给你极大帮助,使你能够为这个类编写更清晰地代码。
    以查询代替临时变量往往是你运用Extract Method(提炼函数)之前必不可少的一个步骤。局部变量会使代码难以被提炼,所以你应该尽可能把它们替换为查询式。
    这个重构手法较为简单的情况是:临时变量只被赋值一次,或者赋值给临时变量的表达式不受其他条件影响。其他情况比较棘手,但也可能发生。你可能需要先运用Split Temporary Variable(分解临时变量)或Separate Query form Modifier(将查询函数和修改函数分离) 使情况变得简单一些,然后再替换临时变量。如果你想替换的临时变量是用来收集结果的)例如循环中的累加值),就需要将某些程序逻辑(例如循环)复制到查询函数去。

6.Introduce Explaining Variable 引入解释性变量


引入解释性变量:(复杂表达式分解为临时解释性变量)你有一个复杂的表达式。将该复杂表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。

if (Platform.ToUpperCass().indexOf("MAC") > -1 && (Browser.ToUpperCass().indexOf("Ie") > -1) && WasInitalized() ) {
	//do something
 }

                                                
const bool imMacOs = Platform.ToUpperCass().indexOf("MAC") > -1;
const bool isIeBrowser = Browser.ToUpperCass().indexOf("Ie") > -1;
const bool wasInitalized = WasInitalized();
if (imMacOs && isIeBrowser && wasInitalized)
{
	//do something
}
    表达式有可能非常复杂而难以阅读。这种情况下,临时变量可以帮助你将表达式分解为比较容易管理的形式。
    在条件逻辑中, Introduce Explaining Variable(引入解释性变量) 是一个很常见的手法,但是最好尽量使用  Extract Method(提炼函数)  来解释一段代码的意义。毕竟临时变量只在他所处的那个函数才有意义,局限性较大。函数则可以在对象的这个生命中都有用,并且可被其他对象使用。但有时候,当局部变量使 Extract Method(提炼函数) 难以进行时,就可以使用 Introduce Explaining Variable (引入解释性变量) .

7. Split Temporary Variable 分解临时变量


分解临时变量:(临时变量不应该被赋值超过一次)你的程序有某个临时变量被赋值超过一次,它既不是循环变量,也不被用于收集计算结果。针对每次赋值,创造一个独立、对应的临时变量

double temp = 2 + (_height + _width);
Console.WriteLine(temp);
temp = _height * _width;
Console.WriteLine(temp);

const double perimeter = 2 + (_height + _width);
Console.WriteLine(perimeter);
const double area = _height * _width;
Console.WriteLine(area);

        临时变量有各种不同用途,其中某些用途会很自然的导致临时变量被多次赋值。“循环变量”和“结果收集变量”就是典型的例子:循环变量会随循环的每次运行而改变;   
结果收集变量负责将“通过这个函数的运算”而构成的某个值收集起来。
        除了这2种情况,还有很多临时变量保存一段冗长代码的运算结果,以便稍后使用。这种临时变量应该只被赋值一次。如果它们被赋值超过一次,
就意味着它们在函数中承担了一个以上的职责。如果临时变量承担多个责任,它就应该被替换为多个临时变量,每个变量只承担一个责任。同一个临时变量承担2件不同的事情,会令代码阅读者糊涂。

三、函数参数重构和算法重构


8.Remove Assigments to Parameters 移除对参数的赋值


移除对参数的赋值:(不要对参数赋值)代码对一个 参数赋值。以一个临时变量取代该参数的位置。 

public int discount(int inputVal, int quantity, int yearToDate) {
  if (inputVal > 50) inputVal -= 2;
  if (quantity > 100) inputVal -= 1;
  if (yearToDate > 10000) inputVal -= 4;
  return inputVal;
}
                                                
public int discount(int inputVal, int quantity, int yearToDate) { 
  int result = inputVal;
  if (inputVal > 50) result -= 2; 
  if (quantity > 100) result -= 1; 
  if (yearToDate > 10000) result -= 4; 
  return result; 
}

如果参数是Object,容易误赋值。采用final来防止误用参数:

要清楚“对参数赋值”这个说法的意思。如果你把一个名为foo的对象作为参数传给某个函数,那么“对参数赋值”意味着改变foo,使它引用另外一个对象。如果你在“被传入对象”身上进行什么操作,那没什么问题。这里只针对“foo被改而指向另一个对象”这种情况来讨论。

void aMethod(Object  foo) {
      foo.modifyInSomeWay();
      foo = anotherObject;
}

这样的做法降低了代码的清晰度,而且混用了按值传递和按引用传递这2种参数传递方式。
在按值传递的情况下,对参数的任何修改,都不会对调用端造成任何影响。那些用过按引用传递方式的人可能会在这一点上犯糊涂。
另一个让人糊涂的地方时函数本体内。如果你只以参数表示“被传递进来的东西”。那么代码会清晰地多,因为这种用法在所有语言都表现出相同语义。
 

四、

9.Substitute Algorithm 替换算法

替换算法:(函数本体替换为另一个算法) 你想要把某个算法替换为另一个更清晰地算法。 将函数本体替换为另一个算法。  
String foundPerson(String[] people){ 
    for (int i = 0; i < people.length; i++)  { 
        if (people[i].equals ("Don")){
             return "Don"; 
         }
         if (people[i].equals ("John")) {
	       return "John"; 
         } 
         if (people[i].equals ("Kent")){ 
                return "Kent"; 
         } 
     }
     return ""; 
} 
                                                 
 
String foundPerson(String[] people){ 
    List candidates 
             = Arrays.asList(new String[] {"Don", 	"John", "Kent"}); 
    for (int i=0; i<people.length; i++) 
        if (candidates.contains(people[i])) 
             return people[i]; return ""; 
} 
      解决问题有好几种方法。算法也是如此。如果你发现做一件事可以有更清晰地方式,就应该以较清晰地方式取代复杂的方式。“重构”可以把一些复杂东西分解为较简单的小块,但有时你就必须删除整个算法,代之以简单的算法。随着对问题有了更多理解,你往往会发现,在原先的做法之外,有更简单的解决方案,此时就需要改变原来的算法。如果你开始使用程序库,而其中提供的某些功能/特性与你自己的代码重复,那么你也需要改变原先的算法。
      有时候你会想要修改原先的算法,让它去做一件与原先略有差异的事。这时候你也可以先把原先的算法替换为一个较易修改的算法,这样后续的修改会轻松许多。使用这项重构之前,请先确定自己尽可能分解了原先函数。替换一个巨大而复杂的算法是很困难的。只有先将它分解为较简单的小型函数,然后你才能很有把握的进行算法替换工作。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2章 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3章 代码的坏味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的) 85 3.19 Incomplete Library Class(不完美的库) 86 3.20 Data Class(纯稚的数据) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4章 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5章 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6章 重新组织函数 109 6.1 Extract Method(提炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7章 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(提炼) 149 7.4 Inline Class(将内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8章 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据取代记录) 217 8.13 Replace Type Code with Class(以取代型码) 218 8.14 Replace Type Code with Subclasses(以子取代型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代型码) 227 8.16 Replace Subclass with Fields(以字段取代子) 232 第9章 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10章 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11章 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(提炼) 330 11.7 Extract Superclass(提炼) 336 11.8 Extract Interface(提炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12章 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(提炼继承体系) 375 第13章 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14章 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15章 总结 409

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hguisu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值