重构改善既有代码设计--重构手法01:Extract Method (提炼函数)
背景:
你有一段代码可以被组织在一起并独立出来。将这段代码放进一个独立函数,并让函数名称解释该函数的用途。
void PrintOwing(double amount) { PrintBanner(); //print details Console.WriteLine("name:"+_name); Console.WriteLine("amount:"+_amount); } |
void PrintOwing(double amount) { PrintBanner(); //print details PrintDetails(); } private void PrintDetails() { Console.WriteLine("name:" + _name); Console.WriteLine("amount:" + _amount); } |
动机:
Extract Method (提炼函数)是最常用的重构手法之一。当看见一个过长的函数或者一段需要注释才能让人理解用途的代码,就应该将这段代码放进一个独立函数中。
简短而命名良好的函数的好处:首先,如果每个函数的粒度都很小,那么函数被复用的机会就更大;其次,这会使高层函数读起来就想一系列注释;再次,如果函数都是细粒度,那么函数的覆写也会更容易些。
一个函数多长才算合适?长度不是问题,关键在于函数名称和函数本体之间的语义距离。如果提炼可以强化代码的清晰度,那就去做,就算函数名称必提炼出来的代码还长也无所谓。
做法:
1、创造一个新函数,根据这个函数的意图对它命名(以它“做什么“命名,而不是以它“怎样做”命名)。
即使你想要提炼的代码非常简单,例如只是一条消息或一个函数调用,只要新函数的名称能够以更好方式昭示代码意图,你也应该提炼它。但如果你想不出一个更有意义的名称,就别动。
2、将提炼出的代码从源函数复制到新建的明白函数中。
3、仔细检查提炼出的代码,看看其中是否引用了“作用域限于源函数”的变量(包括局部变量和源函数参数)。
4、检查是否有“仅用于被提炼代码段”的临时变量。如果有,在目标函数中将它们声明为临时变量。
5、检查被提炼代码段,看看是否有任何局部变量的值被它改变。如果一个临时变量值被修改了,看看是否可以将被提炼代码处理为一个查询,并将结果赋值给修改变量。如果很难这样做,或如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动提炼出来。你可能需要先使用 Split Temporary Variable (分解临时变量),然后再尝试提炼。也可以使用 Replace Temp with Query (以查询取代临时变量)将临时变量消灭掉。
6、将被提炼代码段中需要读取的局部变量,当做参数传给目标函数。
7、处理完所有局部变量后,进行编译。
8、在源函数中,将被提炼代码段替换给对目标函数的调用。
如果你将如何临时变量移到目标函数中,请检查它们原本的声明式是否在被提炼代码段的外围。如果是,现在可以删除这些声明式了。
9、编译,测试。
代码演示:
实例代码如下:
2 public void printPeople(int Age)
3 {
4 printFamily();
5 //无数代码//
6
7 //打印个人信息
8 Console.WriteLine("Name:" + myName);
9 Console.WriteLine("Age:" + Age);
10 }
重构后的代码如下:
2 public void printPeople(int Age)
3 {
4 printFamily();
5 //无数代码//
6 printMyInfo(Age);
7 }
8
9 void printMyInfo(int Age)
10 {
11 Console.WriteLine("Name:" + myName);
12 Console.WriteLine("Age:" + Age);
13 }
为什么要这样重构?当一个函数很大的时候,第一对代码的修改起来非常的不方便.
第二,会对你读代码有障碍,试想一下当你看到一个很多行代码的方法,你还有心情看下去吗?
第三,方法与方法之间的复用性会非常的好,方法的重写也会更容易些.
那么我们应该怎么做呢?
看第一个例子:
无局部变量的方法提炼.
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = 0.0;
5
6 //打印头部信息
7 Console.WriteLine("*****************");
8 Console.WriteLine("**Customer Owes**");
9 Console.WriteLine("*****************");
10
11 //计算
12 foreach(Object o in al)
13 {
14 Order each = (Order)o;
15 outstanding += each.Amount;
16 }
17
18 //打印具体信息
19 Console.WriteLine("Name:" + myName);
20 Console.WriteLine("Age:" + age);
21 }
好了我们开始先提最简单的部分.提出后的代码如下:
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = 0.0;
5
6 printBanner();
7
8 //计算
9 foreach(Object o in al)
10 {
11 Order each = (Order)o;
12 outstanding += each.Amount;
13 }
14
15 //打印具体信息
16 Console.WriteLine("Name:" + myName);
17 Console.WriteLine("Age:" + age);
18 }
19
20 void printBanner()
21 {
22 //打印头部信息
23 Console.WriteLine("*****************");
24 Console.WriteLine("**Customer Owes**");
25 Console.WriteLine("*****************");
26 }
最简单的提炼方法结束了.
下来我们看有局部变量的方法提炼.就拿上面的的代码开刀.
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = 0.0;
5
6 printBanner();
7
8 //计算
9 foreach(Object o in al)
10 {
11 Order each = (Order)o;
12 outstanding += each.Amount;
13 }
14
15 printInfo(outstanding);
16 }
17
18 void printBanner()
19 {
20 //打印头部信息
21 Console.WriteLine("*****************");
22 Console.WriteLine("**Customer Owes**");
23 Console.WriteLine("*****************");
24 }
25
26 void printInfo(double OutStanding)
27 {
28 //打印具体信息
29 Console.WriteLine("Name:" + myName);
30 Console.WriteLine("Age:" + age);
31 }
我们再来看下对局部变量再赋值方法的提炼.继续拿上面代码开刀.
2 {
3 double outstanding = GetOutStanding();
4
5 printBanner();
6
7 printInfo(outstanding);
8 }
9
10 void printBanner()
11 {
12 //打印头部信息
13 Console.WriteLine("*****************");
14 Console.WriteLine("**Customer Owes**");
15 Console.WriteLine("*****************");
16 }
17
18 void printInfo(double OutStanding)
19 {
20 //打印具体信息
21 Console.WriteLine("Name:" + myName);
22 Console.WriteLine("Age:" + age);
23 }
24
25 double GetOutStanding()
26 {
27 ArrayList al = myOrders.GetOrderList();
28 double outstanding = 0.0;
29 //计算
30 foreach(Object o in al)
31 {
32 Order each = (Order)o;
33 outstanding += each.Amount;
34 }
35 return outstanding
36 }
Extract Method方法讲解玩了.有人会问为什么要这样写?这样写的好处我没有看到啊.
那么现在有个这样的需求,我要设置outstanding的初始值,那么我们只要修改GetOutStanding方法,代码
如下:
2 {
3 ArrayList al = myOrders.GetOrderList();
4 double outstanding = previousAmount;
5 //计算
6 foreach(Object o in al)
7 {
8 Order each = (Order)o;
9 outstanding += each.Amount;
10 }
11 return outstanding
12 }
主要方法修改如下:
2 {
3 double outstanding = GetOutStanding(500.5);
4
5 printBanner();
6
7 printInfo(outstanding);
8 }
如果需求继续增加,我们修改起来是不是方便了许多?
读后感:
1.如果说没有任何局部变量,那么这个函数提炼就非常容易提炼.
2.如果说提炼的时候有局部变量,即用到了提炼函数之外的局部变量,那么如果仅仅是内部函数使用的,直接放到内部函数中;第二种,如果提炼的函数内部没有对此变量赋值的情况,仅仅是读取使用,那么直接从外面作为参数传递进来。
3.如果提炼的函数,不仅仅有局部变量,并且还要对其赋值,那么同样要看,这个局部变量是不是只是内部使用,如果只是内部使用,直接放进来,如果不是,那就说外面还要用到,那么需要经过提炼函数运算后,将值返回去.
重构改善既有代码设计--重构手法01:Extract Method (提炼函数)的更多相关文章
- 重构改善既有代码设计--重构手法16:Introduce Foreign Method (引入外加函数)&& 重构手法17:Introduce Local Extension (引入本地扩展)
重构手法16:Introduce Foreign Method (引入外加函数)你需要为提供服务的类增加一个函数,但你无法修改这个类.在客户类中建立一个函数,并以第一参数形式传入一个服务类实例. 动机 ...
- 重构改善既有代码设计--重构手法08:Replace Method with Method Object (以函数对象取代函数)
你有一个大型函数,其中对局部变量的使用,使你无法釆用 Extract Method. 将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的值域(field) 然后你可以在同一个对象中将这个大型 ...
- 重构改善既有代码设计--重构手法05:Introduce Explaining Variable (引入解释性变量)
发现:你有一个复杂的表达式. 解决:将该复杂的表达式(或其中的部分)的结果放进一个临时变量,并以此变量名称来解释表达式用途. //重构前 if((platform.toUpperCase().in ...
- 重构改善既有代码设计--重构手法04:Replace Temp with Query (以查询取代临时变量)
所谓的以查询取代临时变量:就是当你的程序以一个临时变量保存某一个表达式的运算效果.将这个表达式提炼到一个独立函数中.将这个临时变量的所有引用点替换为对新函数的调用.此后,新函数就可以被其他函数调用. ...
- 重构改善既有代码设计--重构手法02:Inline Method (内联函数)& 03: Inline Temp(内联临时变量)
Inline Method (内联函数) 一个函数调用的本体与名称同样清楚易懂.在函数调用点插入函数体,然后移除该函数. int GetRating() { return MoreThanfiveLa ...
- 重构改善既有代码设计--重构手法10:Move Method (搬移函数)
你的程序中,有个函数与其所驻类之外的另一个类进行更多的交流:调用后者,或被后者调用.在该函数最常用引用的类中建立一个有着类似行为的新函数.将旧函数编程一个单纯的委托函数,或是将旧函数完全移除. 动机: ...
- 重构改善既有代码设计--重构手法11:Move Field (搬移字段)
你的程序中,某个字段被其所驻类之外的另一个类更多的用到.在目标类建立一个新字段,修改源字段的所有用户,令它们改用新字段. 动机:在类之间移动状态和行为,是重构过程中必不可少的措施.随着系 ...
- 重构改善既有代码设计--重构手法07:Remove Assignments to Parameters (移除对参数的赋值)
代码对一个 参数赋值.以一个临时变量取代该参数的位置. int Discount(int inputVal, int quantity, int yearTodate) { if (input ...
- 重构改善既有代码设计--重构手法19:Replace Data Value with Object (以对象取代数据值)
你有一笔数据项(data item),需要额外的数据和行为. 将这笔数据项变成一个对象. class Order... private string customer; ==> class Or ...
随机推荐
- 软工1816 · Alpha冲刺(9/10)
团队信息 队名:爸爸饿了 组长博客:here 作业博客:here 组员情况 组员1(组长):王彬 过去两天完成了哪些任务 学习jQuery的AJAX部分的基础知识,对web端如何异步获取服务器信息有了 ...
- 论文爬取 & 词频统计2.0
一.Github地址 课程项目要求 队友博客 二.具体分工 031602225 林煌伟 :负责C++部分主要功能函数的编写,算法的设计以及改进优化 031602230 卢恺翔 : 爬虫 ...
- json 和 pickle
用于序列化的两个模块 json:用于字符串和python数据类型间进行转换 pickle:用于python特有的类型和python的数据类型间进行转换 json模块提供了四个功能:dumps dump ...
- iOS 关于MVC和MVVM设计模式的那些事
一.概述 在 iOS 开发中,MVC(Model View Controller)是构建iOS App的标准模式,是苹果推荐的一个用来组织代码的权威范式.Apple甚至是这么说的.在MVC下,所有的对 ...
- HDU4472_Count
题目的意思简单,给你n个点,每个点(除根节点)都有且只有一个父节点.现在问你这些节点关系组成的图有多少种情况满足同一层节点上每个点的后继节点数量相等. 很简单,对于n节点,直接枚举n-1的的所有约数情 ...
- APIO/CTSC2017游记
5.10开坑,别问我为啥今天才开始写,前几天玩得太开心了233 5.7 坐火车坐火车,坐地铁坐地铁.其实是第一次坐地铁233.解锁了在地铁上双手玩手机不扶东西站立的姿势? 全程烧流量上QQ,拜大佬约面 ...
- 【uoj#317】[NOI2017]游戏 2-SAT
题目描述 给出 $n$ 个赛车赛道和A.B.C三种赛车,除了 $d$ 个赛道可以使用所有三种赛车以外每个都只能使用给出的两种之一.另外给出 $m$ 条限制:某个赛道使用X则某另一个赛道必须使用Y.问: ...
- Python清理过期文件
改程序执行后,会清理 test/文件夹中距离现在超过一天的以 .xml 结尾的文件 # coding: utf-8 import time import os root = os.path.dirna ...
- 转载--------Python中:self和__init__的含义 + 为何要有self和__init__
背景 回复:我写的一些Python教程,需要的可以看看,中SongShouJiong的提问: Python中的self,__init__的含义是啥?为何要有self,__init这些东西? 解释之前, ...
- "XX cannot be resolved to a type "eclipse报错及解决
好久都没有写博了,还记得自己准备考研,结果你会发现——你永远不知道,你将会走上哪个路. 长远的目标是好的,但有些时候身不由己也迫不得已!做好自己的当下就是好的. 不论搞什么,总会遇到各种各样的问题,以 ...