备注:本文的内容来自《
wxPython
实战
(
中文版)》的
5.1
节
好的程序员为什么也会写出不好的界面或界面代码?这有很多原因。甚至一个简单的用户界面可能都要求很多行来显示屏幕上的所有元素。程序员通常试图用单一的方法来实现这些,这种方法会迅速变得长且难于控制,此外界面代码是很容易受到不断改变的影响的
(除非你对管理这些改变训练有素)。由于写界面代码的过程可能是很枯燥的,所以界面程序员经常会使用设计工具来生成代码,而机器生成的代码相对于手工代码来说是很差。
原则上讲,保持
UI
代码在控制之下是不难的。关键是重构或不断改进现有代码的设计和结构。重构的目的是保持代码在以后易读和易于维护
。
下表说明了在重构时需要记住的一些原则。最重要的是要记住,某人以后可能会不得不读和理解你的代码。努力让他人的生活更容易些,毕竟那有可能是你
。
不要重复 |
你应该避免有多个相同功能的段。当这个功能需要改变时,这维护起来会 很头痛。 |
一次做一件事情 |
一个方法应该并且只做一件事情。各自的事件应该在各自的方法 中。方法应该保持短小。 |
嵌套的层数要少 |
尽量使嵌套代码不多于 也是一个好的选择。 |
避免字面意义上的字符串和数字 |
字面意义上的字符串和数字应使其出现在代码中的次数最小化。一个好的方法是,把它们从你的代码的主要部分中分离出来,并存储于一个列表或字典中。 |
这些原则在
Python
代码中特别重要。因为
Python
的缩进语法、小而简洁的方法是很容易去读的。然而,长的方法对于理解来说是更困难的,尤其是如果它们在一个屏幕上不能完全显示出来时。类似的,
Python
中的深的嵌套使得跟踪代码块的开始和结尾很棘手。然而,
Python
在避免重复方面是十分好的一种语言,特别是因为函数和方法或以作为参数传递。
一、
一个需要重构的例子
为了展示如何在实际工作中应用这些原则,我们将看一个重构的例子。下图显示了一个窗口。
它的布置比之前我们的所见过的那些要复杂一些。但是按现实中的应用程序的标准,它仍然十分简单。例
5.1
的代码的结构很差。
例
1
产生图
1
的没有重构的代码
#!/usr/bin/env python
import wx
class RefactorExample(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, ’Refactor Example’,
size=(340, 200))
panel = wx.Panel(self, -1)
panel.SetBackgroundColour(“White”)
prevButton = wx.Button(panel, -1, ”<< PREV”, pos=(80, 0))
self.Bind(wx.EVT_BUTTON, self.OnPrev, prevButton)
nextButton = wx.Button(panel, -1, ”NEXT >>”, pos=(160, 0))
self.Bind(wx.EVT_BUTTON, self.OnNext, nextButton)
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
menuBar = wx.MenuBar()
menu1 = wx.Menu()
openMenuItem = menu1.Append(-1, ”&Open”, ”Copy in status bar”)
self.Bind(wx.EVT_MENU, self.OnOpen, openMenuItem)
quitMenuItem = menu1.Append(-1, ”&Quit”, ”Quit”)
self.Bind(wx.EVT_MENU, self.OnCloseWindow, quitMenuItem)
menuBar.Append(menu1, ”&File”)
menu2 = wx.Menu()
copyItem = menu2.Append(-1, ”&Copy”, ”Copy”)
self.Bind(wx.EVT_MENU, self.OnCopy, copyItem)
cutItem = menu2.Append(-1, ”C&ut”, ”Cut”)
self.Bind(wx.EVT_MENU, self.OnCut, cutItem)
pasteItem = menu2.Append(-1, ”Paste”, ”Paste”)
self.Bind(wx.EVT_MENU, self.OnPaste, pasteItem)
menuBar.Append(menu2, ”&Edit”)
self.SetMenuBar(menuBar)
static = wx.StaticText(panel, wx.NewId(), ”First Name”,
pos=(10, 50))
static.SetBackgroundColour(“White”)
text = wx.TextCtrl(panel, wx.NewId(), ””, size=(100, -1),
pos=(80, 50))
static2 = wx.StaticText(panel, wx.NewId(), ”Last Name”,
pos=(10, 80))
static2.SetBackgroundColour(“White”)
text2 = wx.TextCtrl(panel, wx.NewId(), ””, size=(100, -1),
pos=(80, 80))
firstButton = wx.Button(panel, -1, ”FIRST”)
self.Bind(wx.EVT_BUTTON, self.OnFirst, firstButton)
menu2.AppendSeparator()
optItem = menu2.Append(-1, ”&Options...”, ”Display Options”)
self.Bind(wx.EVT_MENU, self.OnOptions, optItem)
lastButton = wx.Button(panel, -1, ”LAST”, pos=(240, 0))
self.Bind(wx.EVT_BUTTON, self.OnLast, lastButton)
# Just grouping the empty event handlers together
def OnPrev(self, event): pass
def OnNext(self, event): pass
def OnLast(self, event): pass
def OnFirst(self, event): pass
def OnOpen(self, event): pass
def OnCopy(self, event): pass
def OnCut(self, event): pass
def OnPaste(self, event): pass
def OnOptions(self, event): pass
def OnCloseWindow(self, event):
self.Destroy()
if __name__ == ’__main__’:
app = wx.PySimpleApp()
frame = RefactorExample(parent=None, id=-1)
frame.Show()
app.MainLoop()
根据重构原则,上面这段代码有一点是做到了,就是没有深的嵌套。其它都没有做到。
为了让你有一个关于如何调整的一个思想,我们将把所有的按钮代码分别放到各自的方法中。
下表归纳了我们重构原代码应解决的问题
原则 |
代码要重构的地方 |
不要重复 |
几个模式不断重复,包括“增加按钮,关联一个方法”, |
一次只做一件事 |
代码做了几件事情。除了基本的框架 |
避免字面意义上的字符串和数字 |
在构造器中每个按钮、菜单项和文本框都有一个文字字符串和坐标常量 |
二、
开始重构
例
2
中只包含了前面用于创建按键栏的代码。作为重构的第一步,我们在例
2
中把例
1
中创建按钮栏这些代码抽出来放在了它自己的方法中
例
2
按钮栏作为一个单独的方法
def createButtonBar(self):
firstButton = wx.Button(panel, -1, ”FIRST”)
self.Bind(wx.EVT_BUTTON, self.OnFirst, firstButton)
prevButton = wx.Button(panel, -1, ”<< PREV”, pos=(80, 0))
self.Bind(wx.EVT_BUTTON, , self.OnPrev, prevButton)
nextButton = wx.Button(panel, -1, ”NEXT >>”, pos=(160, 0))
self.Bind(wx.EVT_BUTTON, self.OnNext, nextButton)
lastButton = wx.Button(panel, -1, ”LAST”, pos=(240, 0))
self.Bind(wx.EVT_BUTTON, self.OnLast, lastButton)
像上面这样把代码分离出后,所有按钮添加代码之间的共性就很容易看出来了。我们可以把添加按钮的代码写成一个公用的方法来调用
,而避免了重复
。如例
3
所示:
例
3
一个公用的改进了的按钮栏方法
def createButtonBar(self, panel):
self.buildOneButton(panel, ”First”, self.OnFirst)
self.buildOneButton(panel, ”<< PREV”, self.OnPrev, (80, 0))
self.buildOneButton(panel, ”NEXT >>”, self.OnNext, (160, 0))
self.buildOneButton(panel, ”Last”, self.OnLast, (240, 0))
def buildOneButton(self, parent, label, handler, pos=(0,0)):
button = wx.Button(parent, -1, label, pos)
self.Bind(wx.EVT_BUTTON, handler, button)
return button
例
3
代替例
2
有两个好处。第一,简短的方法和有意义的方法名使得代码的可读性更清晰了
。第二,它避免了局部变量(诚然,你也可以通过使用
ID
来避免使用局部变量,但那容易导致重复的
ID
问题)
。不使用局部变量是有好处的,它减少了代码的复杂程序,并且也因为这样几乎排除了通常由剪切和粘贴部分代码而忘记了改变所有变量的名字带来的错误。(在实际的应用中,你可能需要存储按钮为实例变量以备后来访问,但是本例不需要。)另外,
buildOneButton()
方法容易放进一个工具模块中并可以在别的框架或项目中重用。
三、
进一步重构
上面的例子,已经得到了很多的改善。但是在多处仍有许多常量。其一,就是用于定位的点坐标,当另一个按钮被添加到按钮栏时可能使代码产生错误,尤其是新的按钮被放置在按钮栏的中间
。因此让我们再往前进一步,我们把这些字面意义上的数据从处理中分离出来。下例
4
展示了一个用于创建按钮的数据驱动机制。
例