通过面向对象三大特性:封装、继承、多态的学习,可以说我们已经掌握了面向对象的核心。接下来的学习就是如何让我们的代码更优雅、更高效、更易读、更易维护。当然了,这也是从一个普通程序员到一个高级程序员的必由之路。就看病一样,普通医生只能治标,高级医生不但看好病,还能除病根。

1.什么时重构?

重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

目的:是提高其可理解性,降低其修改成本。

通俗的说法就是,程序的功能和结果没有任何的变化。重构只是对程序内部结构进行调整,让代码更加容易理解,然后更容易维护。也就是代码的优化。

通过上述定义,可以看出,重构并不是.net的本身的特性,而是软件设计范畴。

2.重构的目的

A.改进软件的设计

在实际工作中,为了赶进度或是为了短期利益,再或者是没有完全摸清软件整体架构的情况下,对代码进行改动。而这些改动的积累很容易使软件偏离它原先的设计初衷,使软件变很很难维护或无法维护。

而重构可以帮助重新组织代码,重新清晰的体现结构和进一步改进设计。

B.提高代码的质量和可维护性

容易理解的代码很容易维护和做进一步开发。即使写这些代码的程序员本身而言,容易更解的代码也能帮助他容易的修改。

代码也是文档,首先是写给人看的,其次才是计算机。

C.帮助尽早的发现错误

重构是一个复习和反馈的过程,在另一个时段重新审视自己或别人的代码,可以更容易发现问题和加深对代码的理解.

重构是一个良好的开发习惯。

D.可以提高开发速度

重构对设计和代码的改进,都可以有效提高开发速度。

在一个有缺陷的设计和混乱的代码基础上开发,即使表面是进度较快,但本质是延后对设计缺陷的发现和对错误的修改。也就延后了开发风险,最终要在开发后期付出更多的代价。

一句话,出来混,迟早是要还的!!

3.重构的时机

重构的时候,即何时需要重构,何时不需要

A.首先,以下几种情况需要重构:

过大的类和过长的方法

过长的方法由于包含的逻辑过于复杂,错误机率将直线上升,而可读性则直线下降,类的健壮性很容易被打破。当看到一个过长的方 法时,需要想办法将其划分为多个小方法,以便于分而治之。

牵一发而需要动全身的修改

  当你发现修改一个小功能,或增加一个小功能时,就引发一次代码地震,也许是你的设计抽象度不够理想,功能代码太过分散所引起的。

类之间需要过多的通讯

  A类需要调用B类的过多方法访问B的内部数据,在关系上这两个类显得有点狎昵,可能这两个类本应该在一起,而不应该分家。

过度耦合的信息链

  如果你在代码中看到需要获取一个信息,需要一个类的方法调用另一个类的方法,层层挂接,就象输油管一样节节相连。这往往是因为衔接层太多造成的,需要查看就否有可移除的中间层,或是否可以提供更直接的调用方法。

各自为政的功能模块

  如果你发现有两个类或两个方法虽然命名不同但却拥有相似或相同的功能,你会发现往往是因为开发团队成员协调不够造成的。笔者曾经写了一个颇好用的字符串处理类,但因为没有及时通告团队其他人员,后来发现项目中居然有三个字符串处理类。革命资源是珍贵的,我们不应各立山头干革命。

不完美的设计  

  每个系统都或多或少存在不完美的设计,刚开始可能注意不到,到后来才会慢慢凸显出来,此时唯有勇于更改才是最好的出路。

缺少必要的注释

  虽然许多软件工程的书籍常提醒程序员需要防止过多注释,但这个担心好象并没有什么必要。往往程序员更感兴趣的是功能实现而非代码注释,因为前者更能带来成就感,所以代码注释 往往不是过多而是过少,过于简单。人的记忆曲线下降的坡度是陡得吓人的,当过了一段时间后再回头补注释时,很容易发生"提笔忘字,愈言且止"的情形。

曾在网上看到过微软的代码注释,其详尽程度让人叹为观止,也从中体悟到了微软成功的一个经验。

(以上关于重构的内容来自网上小伙伴的分析,还是比较全面的,摘录过来分享之)

B.还有几种情况是不适用重构的:

代码混乱,错误百出,这种情况,不是重构而是需要重写了

大型多模块软件,需要逐步重构,不是一下子完成

重构需要太长的时间,这种情况下不建议重构。

项目即将进入交付阶段,隐定性胜过其它。

3.如何进行重构

前面讲了太多的理论知识,下面来点硬货,说说重构的方法。

3.1使用VS.NET 自身的功能实现快速重构

VS.net本身关于重构的功能,可能很多人很少用到,作为一个重构的辅助功能,虽说不能完全实现重构,但是可以帮助我们快速优化代码。

3.1.1重构类型

<1>. 重命名

<2>.提取方法

<3>. 封装字段

<4>. 提取接口

<5>. 将局部变量提升为参数

<6>. 移除参数

<7>. 重新排列参数

VS.NET中提供了这么七种重构的类型。我们在代码编辑窗口中,点击鼠标右键,可以看到如下图所示:

下面,我们逐一说明

<1>重命名

我们在代码重构过程中,会有不按规范命名的情况发生或者我们想让一段代码产生一个副本。

A. 提供了一种重命名代码符号(如字段、局部变量、方法、命名空间、属性和类型)标识符的简单方法.

B. “重命名”功能除了可用来更改标识符的声明和调用以外,还可用来更改注释中和字符串中的名称.

如下图所示,选中一个名称后,输入新名称,VS.NET会提示你更改那些名字。

<2>.提取方法

A 可以通过从现有成员的代码块中提取选定的代码来创建新方法.

B. 创建的新方法中包含选定的代码,而现有成员中的选定代码被替换为对新方法的调用.

C. 代码段转换为其自己的方法,使您可以快速而准确地重新组织代码,以获得更好的重用和可靠性.

• 优点

A. 通过强调离散的可重用方法鼓励最佳的编码做法。

B. 鼓励通过较好的组织获得自记录代码。当使用描述性名称时,高级别方法可以像读取一系列注释一样进行读取。

C. 鼓励创建细化方法,以简化重载。

D. 减少代码重复.

如下图,我们选中一个方法中的代码片段,点重构中的 “提取方法”弹出下下对话框,我们重命名一个新的方法名

确定后,如下所示:

生成一个静态的方法。在一个方法实现中代码片段太长的时候,我们可以很方便的进行方法提取了。

<3>. 封装字段

A. 可以从现有字段快速创建属性,然后使用对新属性的引用无缝更新代码.

B. 当某个字段为public(C# 参考)时,其他对象可以直接访问该字段并对其进行修改,而不会被拥有该字段的对象检测到。通过使用属性(C# 编程指南)封装该字段,可以禁止对字段的直接访问。

C. 仅当将光标与字段声明置于同一行时,才可以执行“封装字段”操作。

• 实例

大部分开发者都习惯把类级的变量(字段)暴露给外界。由于每一个对象都属于面向对象编程,所以开发者应该允许通过属性或方法来存取变量。这种情况可以使用重构菜单下的"封装字段"选项来进行处理。

为此,选择你想包装在一个属性中的类级变量并且选择"封装字段"选项。这将打开一个如下图所示的对话框:

你需要输入该属性的名字并且决定是否你想从类外或类内部更新到该变量的参考。就象"重命名"对话框一样,你可以在应用之前先预览一下所作的改变。

如下图所示,假如我们要在动物这个类中,加一个属性,我们使用封装字段,

如果选择“外部”确定后,代码如下:

可以看到,为我们自动增加了一个外部属性

<4>• 提取接口

A. 使用来自现有类、结构或接口的成员创建新接口的简单方法.

B. 当几个客户端使用类、结构或接口中成员的同一子集时,或者当多个类、结构或接口具有通用的成员子集时,在接口中嵌入成员子集将很有用.

C. 仅当将光标定位于包含要提取成员的类、结构或接口中时,才可以访问此功能。当光标处于此位置时,调用“提取接口”重构操作.

如下图所示,我们在类名称点击右键 重构,选择提取接口,在弹出窗口中,输入接口名称,选择类的公有成员,则为它们创建了一个接口文件,非常实用。

<5>• 将局部变量提升为参数

A. 提供一种简单的方法,以在正确更新调用站点的同时将变量从局部使用移动至方法、索引器或构造函数参数.

B. 调用“将局部变量提升为参数”操作时,变量将被添加到成员参数列表的结尾处.

C. 对已修改成员的所有调用都将使用新参数(将替代最初赋给该变量的表达式)立即进行更新,并保留代码,以使其像变量提升之前那样正常工作.

D. 将常数值赋值给提升的变量时,此重构操作效果最好。必须声明并初始化该变量,而不能仅声明或仅赋值.

• 实例

原代码:

private static void NewMethod2()
{
string s = "";
}

选中s,转换后

private static void NewMethod2(string s)
{
}

<6>• 移除参数

A. 从方法、索引器或委托中移除参数的简单方法.

B. 在调用成员的任何位置,都会将参数移除以反映新声明.

• 实例

原代码

protected void Page_Load(EventArgs e, object sender)
{
int i = 0;
NewMethod2("1","2");
} private static void NewMethod2(string s1, string s2)
{
string s = s1 + s2;
}

移除后的代码

 protected void Page_Load(EventArgs e, object sender)
{
int i = 0;
NewMethod2();
} private static void NewMethod2()
{
string s = s1 + s2;
}

<7>• 重新排列参数

A. 对方法、索引器和委托的参数顺序进行更改的简单方法.

B. 可以通过方法声明或方法调用来重新排列参数。要将光标置于方法声明或委托声明中,而不是置于正文中。

• 实例

原代码:

private static void NewMethod2(string s1,string s2)
{
}

重新排列后

private static void NewMethod2(string s2,string s1)
{ }

4.重构实例

我们通过一个实例来看看重构带来的好处,还是我们前一节的关于动物叫的例子,有一个基类 动物(Animal)有成员属性名字(Name)

方法叫声(Shout)和叫的次数的虚方法(getShoutCount),它有N个派生类,我们先看重构前的代码如下:

  1 /// <summary>
2 /// 动物类(父类)
3 /// </summary>
4 class Animal
5 {
6 /// <summary>
7 /// 名字
8 /// 说明:类和子类可访问
9 /// </summary>
10 protected string name;
11
12
13 /// <summary>
14 /// 构造函数
15 /// </summary>
16 /// <param name="name"></param>
17 public Animal(string name)
18 {
19 this.name = name;
20 }
21
22 private int shoutNum = 3;
23 public int ShoutNum
24 {
25 get { return shoutNum; }
26 set { shoutNum = value; }
27 }
28
29 /// <summary>
30 /// 名字(虚属性)
31 /// </summary>
32 public virtual string MyName
33 {
34 get { return this.name; }
35
36 }
37
38 /// <summary>
39 /// 叫(虚方法)
40 /// </summary>
41 public virtual void Shout()
42 {
43 Console.WriteLine("我会叫!");
44 }
45
46 }
47
48 /// <summary>
49 /// 狗(子类)
50 /// </summary>
51 class Dog : Animal
52 {
53 string myName;
54 public Dog(string name)
55 : base(name)
56 {
57 myName = name;
58 }
59
60 /// <summary>
61 /// 名字(重写父类属性)
62 /// </summary>
63 public override string MyName
64 {
65 get { return "我是:狗狗,我叫:" + this.name; }
66 }
67
68 /// <summary>
69 /// 叫(重写父类方法)
70 /// </summary>
71 public override void Shout()
72 {
73 string result = "";
74 for (int i = 0; i < ShoutNum; i++)
75 result += "汪!";
76 Console.WriteLine(result);
77 }
78 }
79 /// <summary>
80 /// 猫(子类)
81 /// </summary>
82 class Cat : Animal
83 {
84 string myName;
85 public Cat(string name)
86 : base(name)
87 {
88 myName = name;
89 }
90 /// <summary>
91 /// 名字(重写父类属性)
92 /// </summary>
93 public override string MyName
94 {
95 get { return "我是:猫咪,我叫:" + this.name; }
96
97 }
98
99 /// <summary>
100 /// 叫(重写父类方法)
101 /// </summary>
102 public override void Shout()
103 {
104 string result = "";
105 for (int i = 0; i < ShoutNum; i++)
106 result += "喵!";
107 Console.WriteLine(result);
108 }
109 }
110
111 /// <summary>
112 /// 羊(子类)
113 /// </summary>
114 class Sheep : Animal
115 {
116 string myName;
117 public Sheep(string name)
118 : base(name)
119 {
120 myName = name;
121 }
122 /// <summary>
123 /// 名字(重写父类属性)
124 /// </summary>
125 public override string MyName
126 {
127 get { return "我是:羊羊,我叫:" + this.name; }
128
129 }
130
131 /// <summary>
132 /// 叫(重写父类方法)
133 /// </summary>
134 public override void Shout()
135 {
136 string result = "";
137 for (int i = 0; i < ShoutNum; i++)
138 result += "咩!";
139 Console.WriteLine(result);
140 }
141 }

我们可以看到,虽然这段代码实现了继承和多态,封装的特性,代码还是比较简洁的,但是有一点就是这个叫的方法,每个子类中都要写一次循环。假如又来了猪啊,牛啊,这些动物,是不是代码量也不少啊。我们能不能只写一次循环呢,答案是肯定的,看我们重构后的代码:

  1 /// <summary>
2 /// 动物类(父类)
3 /// </summary>
4 class Animal
5 {
6 /// <summary>
7 /// 名字
8 /// 说明:类和子类可访问
9 /// </summary>
10 protected string name;
11
12 /// <summary>
13 /// 构造函数
14 /// </summary>
15 /// <param name="name"></param>
16 public Animal(string name)
17 {
18 this.name = name;
19 }
20
21 private int shoutNum = 3;
22 public int ShoutNum
23 {
24 get { return shoutNum; }
25 set { shoutNum = value; }
26 }
27
28 /// <summary>
29 /// 名字(虚属性)
30 /// </summary>
31 public virtual string MyName
32 {
33 get { return this.name; }
34
35 }
36
37 /// <summary>
38 /// 叫声,这个方法去掉虚方法,把循环写在这里
39 /// </summary>
40 public void Shout()
41 {
42 string result = "";
43 for (int i = 0; i < ShoutNum; i++)
44 result += getShoutSound()+"!";
45
46 Console.WriteLine(MyName);
47 Console.WriteLine(result);
48 }
49 /// <summary>
50 /// 创建一个叫声的虚方法,子类重写
51 /// </summary>
52 /// <returns></returns>
53 public virtual string getShoutSound()
54 {
55 return "";
56 }
57 }
58
59 /// <summary>
60 /// 狗(子类)
61 /// </summary>
62 class Dog : Animal
63 {
64 string myName;
65 public Dog(string name): base(name)
66 {
67 myName = name;
68 }
69 /// <summary>
70 /// 名字(重写父类属性)
71 /// </summary>
72 public override string MyName
73 {
74 get { return "我是:狗狗,我叫:" + this.name; }
75 }
76 /// <summary>
77 /// 叫(重写父类方法)
78 /// </summary>
79 public override string getShoutSound()
80 {
81 return "汪!";
82 }
83 }
84 /// <summary>
85 /// 猫(子类)
86 /// </summary>
87 class Cat : Animal
88 {
89 string myName;
90 public Cat(string name): base(name)
91 {
92 myName = name;
93 }
94 /// <summary>
95 /// 名字(重写父类属性)
96 /// </summary>
97 public override string MyName
98 {
99 get { return "我是:猫咪,我叫:" + this.name; }
100 }
101 /// <summary>
102 /// 叫(重写父类方法)
103 /// </summary>
104 public override string getShoutSound()
105 {
106 return "喵!";
107 }
108 }
109
110 /// <summary>
111 /// 羊(子类)
112 /// </summary>
113 class Sheep : Animal
114 {
115 string myName;
116 public Sheep(string name): base(name)
117 {
118 myName = name;
119 }
120 /// <summary>
121 /// 名字(重写父类属性)
122 /// </summary>
123 public override string MyName
124 {
125 get { return "我是:羊羊,我叫:" + this.name; }
126 }
127 /// <summary>
128 /// 叫(重写父类方法)
129 /// </summary>
130 public override string getShoutSound()
131 {
132 return "咩!";
133 }
134 }

这样重构,是不是代码量就少很多了,结构也更加清晰了。。

调用一:

//调用
Animal sheep = new Sheep("美羊羊");
sheep.Shout();
Console.ReadLine();

结果如下:

//调用结果
//我是:羊羊,我叫:美羊羊
//咩!咩!咩!

调用二:

//调用
Animal dog= new Dog("旺财");
dog.Shout();
Console.ReadLine();

结果如下:

//调用结果
//我是:狗狗,我叫:旺财
//汪!汪!汪!

总结:重构是一门复杂的学问,本节内容只是重构的皮毛而已,有一些书籍用几千页的篇幅来介绍中重构。能否熟练使用重构,写出优雅高效的代码是区分一个程序员优秀的标准之一,重构也是学习设计模的基础,这需要我们不断的练习和思考才能做好。

要点:

A.重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

B.重构不是.NET面向对象本身的特性,而属于一种软件设计范畴。

C.重构提高了代码的可读性,可维护性;也使得代码结构更加清晰。

D.能否有效的重构代码,是一个程序员优秀与否的标准之一。也是学习设计模式和软件架构的基础。

E.重构是一门代码艺术。

.NET - 代码重构技巧的更多相关文章

  1. RefactoringGuru 代码异味和重构技巧总结

    整理自 RefactoringGuru 代码异味 --什么?代码如何"闻味道"?? --它没有鼻子...但它肯定会发臭! 代码膨胀 [代码膨胀]是代码.方法和类,它们的规模已经增加 ...

  2. CSS代码重构与优化之路

    作者:@狼狼的蓝胖子 网址:http://www.cnblogs.com/lrzw32/p/5100745.html 写CSS的同学们往往会体会到,随着项目规模的增加,项目中的CSS代码也会越来越多, ...

  3. CSS代码重构

    CSS代码重构的目的 我们写CSS代码时,不仅仅只是完成页面设计的效果,还应该让CSS代码易于管理,维护.我们对CSS代码重构主要有两个目的:1.提高代码性能2.提高代码的可维护性 提高代码性能 提高 ...

  4. 谈谈选用技术的原则,技术学习方法技巧,阅读代码的技巧及其它 MSF的一点心得

    谈谈技术原则,技术学习方法,代码阅读及其它(正文) 这篇文章是前一阵在水木BBS上和别人讨论中偶自己发言的摘编,是偶这几年开发过程完全经验式的总结.完全个人经验,供批判. 一.选用技术的原则 比较规范 ...

  5. CSS代码重构与优化

    CSS代码重构的基本方法 前面说到了CSS代码重构的目的,现在我们来说说一些如何达到这些目的的一些基本方法,这些方法都是易于理解,容易实施的一些手段,大家平时可能也不知不觉地在使用它. 提高CSS性能 ...

  6. CSS代码重构与优化之路(转)

    CSS代码重构与优化之路   阅读目录 CSS代码重构的目的 CSS代码重构的基本方法 CSS方法论 我自己总结的方法 写CSS的同学们往往会体会到,随着项目规模的增加,项目中的CSS代码也会越来越多 ...

  7. IntelliJ IDEA 复杂的重构技巧

    IntelliJ IDEA 复杂的重构技巧(二) 转载 上次我说了一些 "复杂的重构技巧" ,讲的是一些使用 IntelliJ 的简单功能实现复杂的重构需求的技巧. 看到大家的反响 ...

  8. Java常见重构技巧 - 去除不必要的!=null判断空的5种方式,很少有人知道后两种

    常见重构技巧 - 去除不必要的!= 项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢?@pdai 常见重构技巧 - 去除不必要的!= 场景一:null无意义之常规判断空 ...

  9. 常见重构技巧 - 5种方式去除多余的if else

    常见重构技巧 - 去除多余的if else 最为常见的是代码中使用很多的if/else,或者switch/case:如何重构呢?方法特别多,本文带你学习其中的技巧. 常见重构技巧 - 去除多余的if ...

随机推荐

  1. filter过滤器的使用

    从J2EE1.3开始,Servlet2.3规范中加入了对过滤器的支持.过滤器能够对目标资源的请求和响应进行截取.过滤器的工作方式分为四种,下面让我们分别来看看这四种过滤器的工作方式: 1.reques ...

  2. Android短信拦截和电话拦截

    MainActivity: package com.wyl.bctest; import android.support.v7.app.ActionBarActivity; import androi ...

  3. 浏览器与服务器间的交互(客服端 <---> 服务器)

    浏览器与服务器间的交互(客服端 <---> 服务器) 请求--->处理--->响应 对类HttpContext 内部成员的使用 例如 :Request .Response .  ...

  4. centos6.5 gsoap安装过程+ php添加soap扩展

    参考博客: CentOS编译安装gSOAP Linux C实现webservice调用 安装gsoap流程  里面提到make时可能碰到的问题 还没有用到 1.从官网下载最新的版本:http://so ...

  5. Ural 1197 - Lonesome Knight

    The statement of this problem is very simple: you are to determine how many squares of the chessboar ...

  6. UItableViewCell上的button点击无响应的办法

    由于IOS7中添加了滑动后出现编辑按钮的操作,所以使用scrollView来处理,UITableViewCellScrollView有对触摸的相应处理,导致按钮的点击效果被屏蔽了,但是点击事件还是在的 ...

  7. Mars的mp3实例

    Mars的mp3实例第一课: 关于menu: package mars.mp3player01; import mars.down.HttpDownloader; import android.app ...

  8. android页面切换效果

    两种方式: 在activity的自定义主题中定义切换方式: overridePendingTransition()方法 自定义主题: 在项目的res/values/styles.xml中添加样式 &l ...

  9. U盘安装centos 7 提示 “Warning: /dev/root does not exist, could not boot” 解决办法

    1.查询磁盘 cd /dev ls 2.查询结果 sda 是我的硬盘对应的文件名(我机子只有一块硬盘),所以sda4就是U盘对应的文件名了,可以看到是sda4.至此我们重启一下,回到第一个图片所示的界 ...

  10. perl 面向对象 use base

    1.XXX.pm 文件里面的第一行要是:package XXX: 2.要有构造函数 sub new,实现如下: sub new { my $class = shift; # Get the reque ...