Refactor!——Visual Basic 2005的重构
概述
不知从何时起,“重构”这个词走进了我们的编程生活。通过重构,程序员可以在确保程序功能不变的情况下消除代码中的“坏味道”和隐患,使代码更加可读并且易于扩展。重构的应用越来越频繁,依照传统的方法一次一小步地进行重构,并利用单元测试来确保重构结果正确性的方法也显得越来越繁琐。于是与IDE结合的自动重构工具就流行起来了。这些自动重构工具可以分析代码,然后找出重构操作影响到的每一处代码,一并做出更改。大大提高了重构操作的效率。我们很高兴地在Visual Studio 2005中看到了内置的重构工具,但是它只支持C#语言。尽管Visual Basic程序员的要求类似重构工具的呼声很高,但最后微软还是推迟了给Visual Basic增加重构功能的计划。不过微软请第三方插件公司专门为Visual Basic 2005开发了免费的重构插件——Developer Express Inc的Refactor! For Visual Basic 2005。下面我们就在Refactor!的帮助下进行一次重构过程的演示,测试一下它强大的功能。先看一段代码:
Public Function CalculatePolynomial(ByVal fileName As String, ByVal input As Single()) As Single() Dim coefficient(10) As Single Dim output(10) As Single Dim fileContent As String Dim reader As StringReader If Not fileName Is Nothing Then Dim i As Integer = 0 If File.Exists(fileName) Then fileContent = My.Computer.FileSystem.ReadAllText(fileName) reader = New StringReader(fileContent) Do While reader.Peek > 0 coefficient(i) = CSng(reader.ReadLine) i += 1 Loop reader.Close() End If End If Dim temp As Double If Not ((Not input IsNot Nothing) OrElse input.Length <= 0) Then temp = Math.PI * 2 For i As Integer = 0 To 10 output(i) = input(i) + temp Next temp = Environment.TickCount / Date.Now.Hour For i As Integer = 0 To 10 For j As Integer = 0 To 10 output(i + j) = input(i) * coefficient(j) / temp Next Next Else Return Nothing End If Return output End Function
这段代码有许多的“坏味道”。首先,这个方法体太长,把很多不同的工作都放在一个函数中处理。将来即使要加一个非常类似的功能,也无法重用其中的代码。其次,这段代码的可读性不佳,表达式非常复杂。这时候我们也许想到的将这段代码分解为多个小方法,使它的结构更加清晰。Refactor!提供了一个名为“提炼方法(Extract Method)”的重构功能可以做到这一点。但是,代码存在过多的变量,如果要提炼的方法包含过多的变量,则提取出来就会包含过多的参数。而如果我们将变量的声明一起提取就可以避免这个问题,这时就要用到将变量声明移至首次引用附近(Move Declaration Near Reference)重构。选中fileContent的定义,从智能标记中选择“Move Declaration Near Reference”,从图 1可以看到,变量的定义将被移动到首次使用前方。
图 1:将变量声明移至首次引用附近
用同样的方法可以将变量reader和i分别移动到首次引用之前。这样,多余的变量就被清除了,现在我们可以将读取文本文件的代码部分提炼出来,选中最外层If到End If语句之间的部分,智能标记就会自动弹出来,这时选中“Extract Method”,会出现一个红色的箭头和操作提示,让你选择新方法的位置,如图 2所示:
图 2:提炼方法
按下回车后输入新方法的名称ReadCoefficient,提炼方法就完成了,非常容易,视觉效果也非常酷。观察新的方法,这里有一个复杂的If嵌套。前面的条件式中,文件名等于Nothing和文件不存在的条件并不常见,绝大情况参数是合法的。那我们就不必让这样的If嵌套扰乱我们的视线,用以卫语句取代嵌套条件式(Replace Nested Conditional with Guard Clause)重构可以将嵌套的条件式转化为清晰的卫语句(判断失败立刻离开函数,像守卫一样的If语句)。将光标放在If语句上,从智能标记中选中“Replace Nested Conditional with Guard Clause”,复杂的If嵌套立刻清晰多了。我们可以用同样方法将第二个If语句也整理成卫语句,如图 3所示:
图 3:以卫语句取代嵌套条件式
接下来我们回到第一个方法中,这里还有需要重构的地方。我们看到变量temp是一个临时变量,但却被赋了两次不同的值,这不利于我们后面的重构,使用将临时变量剖解(Split Temporary Variable)功能即可将该变量按照不同的用途进行拆分。在第二次赋值的语句上使用智能标记,清晰的视觉效果马上会给你很深的印象,如图 4:
图 4:将临时变量剖解
分离后的临时变量temp只是一个简单的表达式,我们完全可以将其消除,使用将临时变量内联化(Inline Temp)可以帮助你快速完成这项任务。只要在将光标放在临时变量temp的定义上,就会有智能标记提示你完成此操作,如图 5所示:
图 5:将临时变量内联化
我们发现,分离出的第二个变量也仅仅被赋值过一次,但却是一个可能在其它函数中用到的表达式,我们可以考虑将其提高改变成一个函数。先使用将变量声明移至首次引用附近功能和将变量初始化合并到变量声明中(Move Initialization to Declaration)功能将temp2的定义移动到初始化语句附近,并与初始化语句合并。现在,我们可以使用以查询取代临时变量(Replace Temp with Query)功能将其转化为一个只读属性,如图 6所示:
图 6:以查询取代临时变量
你会发现这项重构功能的操作方式与“提炼方法”十分相似,只要用上下键选择位置然后按回车就行了。
注意看最后这段代码,我们在多处使用“10”这个数字,但没有明确指明其有什么意义,也不能表明是否所有的数字10都是同一个意义。我们称这种数字为“魔法数”,魔法数将严重影响程序的可读性,而且不利于以后的扩展。这里10其实是多项式最高可能的次数,我们可以用引入常量(Introduce Constant)这一重构手段将魔法数转换为清晰的常数。在10出现的地方选择智能标记的“Introduce Constant”菜单,Refactor!会出现一个特殊的提示框,它引导你遍历每一个数字10出现的区域,你可以判断他们是否是同一个意义,如图 7所示:
图 7:引入常量
判断完了之后,输入一个常量的新名字MAX_DEGREE,这项重构就完成了,看看清晰的常数定义,是不是很有成就感?
我们看看下面这条If语句,非常乱,没有比较清晰的逻辑。
If Not ((Not input IsNot Nothing) OrElse input.Length <= 0) Then
这时可以用简化条件式(Simplify Conditional)重构功能将其简化。使用这项重构,可以将Not与Is、And、Or、AndAlso、OrElse和判断大小及相等的运算符组合,从而简化复杂的条件式。你可以在If语句上的智能标记中找到这项功能。当条件式简化完毕后,我们发现这条If语句的Else中有Return语句,如果它在If中多好啊,这样它就成了卫语句,Else中的内容就可以“解放”到If语句的外部来了。用反转条件式(Reverse Conditional)功能可以帮你做到。从If的智能标记中选取“Reverse Conditional”,就能将If和Else中的代码段交换,如图 8所示:
图 8:反转条件式
现在,已经没有什么可以阻挡我们继续重构了!用“提炼方法”功能将最后两个For循环提炼出来,代码变得更加整齐和精炼,重构的目标也达到了。
Private Const MAX_DEGREE As Integer = 10 Private Sub ReadCoefficient(ByVal fileName As String, ByVal coefficient() As Single) If fileName Is Nothing Then Return End If If Not File.Exists(fileName) Then Return End If Dim fileContent As String fileContent = My.Computer.FileSystem.ReadAllText(fileName) Dim reader As StringReader = New StringReader(fileContent) Do While reader.Peek > 0 Dim i As Integer = 0 coefficient(i) = CSng(reader.ReadLine) i += 1 Loop reader.Close() End Sub Private ReadOnly Property TickCountPerHour() As Double Get Return Environment.TickCount / Date.Now.Hour End Get End Property Private Sub AddDoublePi(ByVal input As Single(), ByVal output() As Single) For i As Integer = 0 To MAX_DEGREE output(i) = input(i) + Math.PI * 2 Next End Sub Private Sub PolynomialMultiply(ByVal input As Single(), ByVal coefficient() As Single, ByVal output() As Single) For i As Integer = 0 To MAX_DEGREE For j As Integer = 0 To MAX_DEGREE output(i + j) = input(i) * coefficient(j) / TickCountPerHour Next Next End Sub Public Function CalculatePolynomial(ByVal fileName As String, ByVal input As Single()) As Single() Dim coefficient(MAX_DEGREE) As Single Dim output(MAX_DEGREE) As Single ReadCoefficient(fileName, coefficient) If input Is Nothing OrElse input.Length <= 0 Then Return Nothing End If AddDoublePi(input, output) PolynomialMultiply(input, coefficient, output) Return output End Function
想必你已经喜欢上Refactor!的重构功能,它的智能标记和绚丽的视觉效果也十分引人注目。除了上述演示中所提到的重构功能之外,Refactor! For VB2005还提供了以下这些重构功能,有兴趣的读者可以下载Refactor!插件继续体验:
• |
创建方法重载(Create Overload) |
• |
创建方法契约(Create Method Contract) |
• |
封装字段(Encapsulate Field) |
• |
提炼属性(Extract Property) |
• |
引入局部变量(Introduce Local) |
• |
移除对参数的赋值(Remove Assignments to Parameters) |
• |
重命名(Rename) |
• |
安全重命名(Safe Rename) |
• |
重新排列参数(Reorder Parameters) |
• |
将初始化语句从定义中分离(Split Initialization from Declaration) |
• |
外围代码(Surrounds With) |
怎么样,比C#内置的重构还多很多吧,现在Visual Basic程序员反而要被C#程序员所羡慕了,一个免费的工具竟有如此丰富的功能。如果你想马上试用,就到这里来下载:http://msdn.microsoft.com/vbasic/downloads/2005/tools/refactor/。注意它目前只能用在Visual Studio 2005 BETA2中。
作者:施凡
引 http://www.microsoft.com/china/msdn/library/langtool/vbnet/RefactorVB2005.mspx