Рубрики
Без рубрики

WXPYPHON: рефакторинг вашей программы

Получите практические, реальные навыки Python на наших ресурсах и пути

Автор оригинала: Mike Driscoll.

В мире много плохого кода. Моя цель в этой статье заключается в том, чтобы помочь программистам WXPYPHON учиться, как облегчить их приложения для обслуживания и изменения. Следует отметить, что то, что в этой статье не обязательно так называемый «лучший» способ рефакторов программы; Вместо этого следующее является представлением того, что я узнал из своего собственного опыта, с небольшим количеством помощи от книги Робина Данна, WxPython в действии и сообщество WXPYPHON.

Для иллюстрации я создал графический скелет популярной программы калькулятора, которую компьютерные науки любят весной, на не подозревающие новые студенты. Код создает только пользовательский интерфейс. Это на самом деле не будет делать какие-либо расчеты. Тем не менее, я нашел какой-то код на Этот сайт Это должно быть легко адаптировать для этой программы. Я оставлю это как упражнение для читателя. Давайте посмотрим на грубый, неограниченный кусок кода:

import wx

class PyCalc(wx.App):
    def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)
        
    def OnInit(self):
        # create frame here
        self.frame = wx.Frame(None, wx.ID_ANY, title="Calculator")
        panel = wx.Panel(self.frame, wx.ID_ANY)
        self.displayTxt = wx.TextCtrl(panel, wx.ID_ANY, "0", 
                                      size=(155,-1),
                                      style=wx.TE_RIGHT|wx.TE_READONLY)
        size=(35, 35)
        zeroBtn = wx.Button(panel, wx.ID_ANY, "0", size=size)
        oneBtn = wx.Button(panel, wx.ID_ANY, "1", size=size)
        twoBtn = wx.Button(panel, wx.ID_ANY, "2", size=size)
        threeBtn = wx.Button(panel, wx.ID_ANY, "3", size=size)
        fourBtn = wx.Button(panel, wx.ID_ANY, "4", size=size)
        fiveBtn = wx.Button(panel, wx.ID_ANY, "5", size=size)
        sixBtn = wx.Button(panel, wx.ID_ANY, "6", size=size)
        sevenBtn = wx.Button(panel, wx.ID_ANY, "7", size=size)
        eightBtn = wx.Button(panel, wx.ID_ANY, "8", size=size)
        nineBtn = wx.Button(panel, wx.ID_ANY, "9", size=size)
        zeroBtn.Bind(wx.EVT_BUTTON, self.method1)
        oneBtn.Bind(wx.EVT_BUTTON, self.method2)
        twoBtn.Bind(wx.EVT_BUTTON, self.method3)
        threeBtn.Bind(wx.EVT_BUTTON, self.method4)
        fourBtn.Bind(wx.EVT_BUTTON, self.method5)
        fiveBtn.Bind(wx.EVT_BUTTON, self.method6)
        sixBtn.Bind(wx.EVT_BUTTON, self.method7)
        sevenBtn.Bind(wx.EVT_BUTTON, self.method8)
        eightBtn.Bind(wx.EVT_BUTTON, self.method9)
        nineBtn.Bind(wx.EVT_BUTTON, self.method10)
        divBtn = wx.Button(panel, wx.ID_ANY, "/", size=size)
        multiBtn = wx.Button(panel, wx.ID_ANY, "*", size=size)
        subBtn = wx.Button(panel, wx.ID_ANY, "-", size=size)
        addBtn = wx.Button(panel, wx.ID_ANY, "+", size=(35,100))
        equalsBtn = wx.Button(panel, wx.ID_ANY, "Enter", size=(35,100))
        divBtn.Bind(wx.EVT_BUTTON, self.method11)
        multiBtn.Bind(wx.EVT_BUTTON, self.method12)
        addBtn.Bind(wx.EVT_BUTTON, self.method13)
        subBtn.Bind(wx.EVT_BUTTON, self.method14)
        equalsBtn.Bind(wx.EVT_BUTTON, self.method15)
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
        vBtnSizer = wx.BoxSizer(wx.VERTICAL)
        numSizer  = wx.GridBagSizer(hgap=5, vgap=5)
        numSizer.Add(divBtn, pos=(0,0), flag=wx.CENTER)
        numSizer.Add(multiBtn, pos=(0,1), flag=wx.CENTER)
        numSizer.Add(subBtn, pos=(0,2), flag=wx.CENTER)
        numSizer.Add(sevenBtn, pos=(1,0), flag=wx.CENTER)
        numSizer.Add(eightBtn, pos=(1,1), flag=wx.CENTER)
        numSizer.Add(nineBtn, pos=(1,2), flag=wx.CENTER)
        numSizer.Add(fourBtn, pos=(2,0), flag=wx.CENTER)
        numSizer.Add(fiveBtn, pos=(2,1), flag=wx.CENTER)
        numSizer.Add(sixBtn, pos=(2,2), flag=wx.CENTER)
        numSizer.Add(oneBtn, pos=(3,0), flag=wx.CENTER)
        numSizer.Add(twoBtn, pos=(3,1), flag=wx.CENTER)
        numSizer.Add(threeBtn, pos=(3,2), flag=wx.CENTER)
        numSizer.Add(zeroBtn, pos=(4,1), flag=wx.CENTER)        
        vBtnSizer.Add(addBtn, 0)
        vBtnSizer.Add(equalsBtn, 0)
        masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
        masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
        mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
        mainSizer.Add(masterBtnSizer)
        panel.SetSizer(mainSizer)
        mainSizer.Fit(self.frame)
        self.frame.Show()
        return True
    
    def method1(self, event):
        pass
    
    def method2(self, event):
        pass
    
    def method3(self, event):
        pass
    
    def method4(self, event):
        pass
    
    def method5(self, event):
        pass
    
    def method6(self, event):
        pass
    
    def method7(self, event):
        pass
    
    def method8(self, event):
        pass
    
    def method9(self, event):
        pass
    
    def method10(self, event):
        pass
    
    def method13(self, event):
        pass
    
    def method14(self, event):
        pass
    
    def method12(self, event):
        pass
    
    def method11(self, event):
        pass
    
    def method15(self, event):
        pass
    
        
def main():
    app = PyCalc()
    app.MainLoop()
    
if __name__ == "__main__":
    main()

Я основанный на этом коде на какой-то очень неприятный код VBA, который мне пришлось сохранить за последние несколько лет. Это тот код, который вы можете столкнуться с программами, которые автоматически генерируют код для программиста, такой как Visual Studio или Macro Builder в Microsoft Office. Обратите внимание, что функции просто пронумерованы вместо того, чтобы быть описательным, и что многие из них выглядят одинаково. Когда вы видите два или более строк кода, которые выглядят одинаково или, похоже, имеют ту же цель, они обычно имеют право на рефакторинг. Термин для этого явления – «копия и вставка» или Код спагетти (Не быть путать с другими кодами, связанными с макароном, связанным с кодом эвфемизмами). Да, копия и вставленный код злой! Когда вам нужно сделать изменения, вам нужно найти каждый экземпляр скопированного кода и слишком изменить его.

На этой записке давайте начнем рефакторинг этого беспорядка! Я думаю, что разделяя кадр, приложение и панельные объекты облегчают дело, так что это то, что мы сделаем первым. Глядя на родителей виджета, мы видим, что текстовый контроль и все кнопки используют панель для их родителя, поэтому давайте поставим все это в один класс. Я также поставлю фактическое создание и макет виджета в одну функцию, которая может быть вызвана из класса панели __init__ (см. Ниже).

class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
        self.parent = parent
        self.formula = []
        self.currentVal = "0"
        self.previousVal = "0"
        self.operator = None
        self.operatorFlag = False
        self.createAndlayoutWidgets()
        
    def createAndlayoutWidgets(self):
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
        vBtnSizer = wx.BoxSizer(wx.VERTICAL)
        numSizer  = wx.GridBagSizer(hgap=5, vgap=5)
        
        self.displayTxt = wx.TextCtrl(self, wx.ID_ANY, "0", 
                                      size=(155,-1),
                                      style=wx.TE_RIGHT|wx.TE_READONLY)
                                          
        # number buttons
        size=(45, 45)
        zeroBtn = wx.Button(self, wx.ID_ANY, "0", size=size)
        oneBtn = wx.Button(self, wx.ID_ANY, "1", size=size)
        twoBtn = wx.Button(self, wx.ID_ANY, "2", size=size)
        threeBtn = wx.Button(self, wx.ID_ANY, "3", size=size)
        fourBtn = wx.Button(self, wx.ID_ANY, "4", size=size)
        fiveBtn = wx.Button(self, wx.ID_ANY, "5", size=size)
        sixBtn = wx.Button(self, wx.ID_ANY, "6", size=size)
        sevenBtn = wx.Button(self, wx.ID_ANY, "7", size=size)
        eightBtn = wx.Button(self, wx.ID_ANY, "8", size=size)
        nineBtn = wx.Button(self, wx.ID_ANY, "9", size=size)
        
        numBtnLst = [zeroBtn, oneBtn, twoBtn, threeBtn, fourBtn, fiveBtn,
                     sixBtn, sevenBtn, eightBtn, nineBtn]
        for button in numBtnLst:
            button.Bind(wx.EVT_BUTTON, self.onButton)
        
        # operator buttons
        divBtn = wx.Button(self, wx.ID_ANY, "/", size=size)
        multiBtn = wx.Button(self, wx.ID_ANY, "*", size=size)
        subBtn = wx.Button(self, wx.ID_ANY, "-", size=size)
        addBtn = wx.Button(self, wx.ID_ANY, "+", size=(45,100))
        equalsBtn = wx.Button(self, wx.ID_ANY, "Enter", size=(45,100))
        equalsBtn.Bind(wx.EVT_BUTTON, self.onCalculate)
        
        opBtnLst = [divBtn, multiBtn, subBtn, addBtn]
        for button in opBtnLst:
            button.Bind(wx.EVT_BUTTON, self.onOperation)
        
        numSizer.Add(divBtn, pos=(0,0), flag=wx.CENTER)
        numSizer.Add(multiBtn, pos=(0,1), flag=wx.CENTER)
        numSizer.Add(subBtn, pos=(0,2), flag=wx.CENTER)
        numSizer.Add(sevenBtn, pos=(1,0), flag=wx.CENTER)
        numSizer.Add(eightBtn, pos=(1,1), flag=wx.CENTER)
        numSizer.Add(nineBtn, pos=(1,2), flag=wx.CENTER)
        numSizer.Add(fourBtn, pos=(2,0), flag=wx.CENTER)
        numSizer.Add(fiveBtn, pos=(2,1), flag=wx.CENTER)
        numSizer.Add(sixBtn, pos=(2,2), flag=wx.CENTER)
        numSizer.Add(oneBtn, pos=(3,0), flag=wx.CENTER)
        numSizer.Add(twoBtn, pos=(3,1), flag=wx.CENTER)
        numSizer.Add(threeBtn, pos=(3,2), flag=wx.CENTER)
        numSizer.Add(zeroBtn, pos=(4,1), flag=wx.CENTER)
        
        vBtnSizer.Add(addBtn, 0)
        vBtnSizer.Add(equalsBtn, 0)
        
        masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
        masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
        mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
        mainSizer.Add(masterBtnSizer)
        self.SetSizer(mainSizer)
        mainSizer.Fit(self.parent)

Вы заметите, что добавление функции и некоторого белого пространства сделали эту часть кода выглядеть намного приятнее. Это также позволяет нам взять этот код и поместить его в отдельный файл, если мы хотели, чтобы мы могли импортировать. Это может помочь продвинуть весь MVC способ делать вещи. Если вы обращали внимание, вы увидите, что я связываю цифровые кнопки в один обработчик и кнопки оператора к другому обработчику. Вот методы, которые я использую:

def onOperation(self, event):
    """
    Add an operator to the equation
    """
    print "onOperation handler fired"

def onButton(self, event):
    """
    Keeps the display up to date
    """
    # Get the button object
    buttonObj = event.GetEventObject()
    # Get the label of the button object
    buttonLbl = buttonObj.GetLabel()
    
def onCalculate(self):
    """
    Calculate the total
    """
    print 'in onCalculate'

Я застрял какой-нибудь код в onbutton Обработчик событий, чтобы вы могли видеть, как получить ручку на кнопочный объект, который его назвал. В противном случае этот метод на самом деле ничего не делает. Другие методы – это просто заглушки. Теперь давайте посмотрим на объектный код рамы и приложения:

class PyCalcFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, 
                          title="Calculator")
        panel = MainPanel(self)
                        
class PyCalc(wx.App):
    def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)
        
    def OnInit(self):
        # create frame here
        frame = PyCalcFrame()
        frame.Show()
        return True
    
def main():
    app = PyCalc()
    app.MainLoop()
    
if __name__ == "__main__":
    main()

Как видите, они очень короткие и до точки. Если вы спешите, то этот маленький рефакторинг может быть достаточно для вас. Однако я думаю, что мы можем сделать лучше. Прокрутите резервную версию и возьмите второй взгляд на этот повторный класс панели. Посмотрите, как есть в десятке строки создания кнопок, которые в основном такие же? Там также есть много линий «Sizer.add». Это будет нашей следующей целью!

На списке рассылки WXPYPHON это прошлой весной (2009), на этой теме было большая дискуссия. Я видел много интересных решений, но тот, который был заблужден, больше всего, было создание своего рода метода здания виджета. Это то, что я покажу вам, как это сделать. Вот моя ограниченная версия:

def onWidgetSetup(self, widget, event, handler, sizer, pos=None, flags=[]):
    """
    Accepts a widget, the widget's default event and its handler,
    the sizer for the widget, the position of the widget inside 
    the sizer (if applicable) and the sizer flags (if applicable)
    """
    widget.Bind(event, handler)        
    if not pos:
        sizer.Add(widget, 0, wx.ALL, 5)
    elif pos and flags:
        sizer.Add(widget, pos=pos, flag=wx.CENTER) 
    else:
        sizer.Add(widget, pos=pos)
    
    return widget

Это довольно простой кусочек кода, который принимает виджет, связывает его к событию и добавляет полученную комбинацию для Sizer. Этот скрипт может быть расширен для выполнения дополнительных привязки, гнездовые сизуры, установите шрифты и многое другое. Использовать свое воображение. Теперь мы можем взглянуть на то, как это изменило код класса панели:

class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
        self.parent = parent
        self.formula = []
        self.currentVal = "0"
        self.previousVal = "0"
        self.operator = None
        self.operatorFlag = False
        self.createAndlayoutWidgets()
        
    def createAndlayoutWidgets(self):
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
        vBtnSizer = wx.BoxSizer(wx.VERTICAL)
        numSizer  = wx.GridBagSizer(hgap=5, vgap=5)
        
        self.displayTxt = wx.TextCtrl(self, wx.ID_ANY, "0", 
                                      size=(155,-1),
                                      style=wx.TE_RIGHT|wx.TE_READONLY)
        # number buttons
        size=(45, 45)
        zeroBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "0", size=size),
                                     wx.EVT_BUTTON, self.onButton, numSizer,
                                     pos=(4,1))
        oneBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "1", size=size),
                                    wx.EVT_BUTTON, self.onButton, numSizer,
                                    pos=(3,0))
        twoBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "2", size=size),
                                    wx.EVT_BUTTON, self.onButton, numSizer,
                                    pos=(3,1))
        threeBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "3", size=size),
                                      wx.EVT_BUTTON, self.onButton, numSizer,
                                      pos=(3,2))
        fourBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "4", size=size),
                                     wx.EVT_BUTTON, self.onButton, numSizer,
                                     pos=(2,0))
        fiveBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "5", size=size),
                                     wx.EVT_BUTTON, self.onButton, numSizer,
                                     pos=(2,1))
        sixBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "6", size=size),
                                    wx.EVT_BUTTON, self.onButton, numSizer,
                                    pos=(2,2))
        sevenBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "7", size=size),
                                      wx.EVT_BUTTON, self.onButton, numSizer,
                                      pos=(1,0))
        eightBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "8", size=size),
                                      wx.EVT_BUTTON, self.onButton, numSizer,
                                      pos=(1,1))
        nineBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "9", size=size),
                                     wx.EVT_BUTTON, self.onButton, numSizer,
                                     pos=(1,2))
        # operator buttons
        divBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "/", size=size),
                                    wx.EVT_BUTTON, self.onOperation, numSizer,
                                    pos=(0,0), flags=wx.CENTER)
        multiBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "*", size=size),
                                      wx.EVT_BUTTON, self.onOperation, numSizer,
                                      pos=(0,1), flags=wx.CENTER)
        subBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "-", size=size),
                                    wx.EVT_BUTTON, self.onOperation, numSizer,
                                    pos=(0,2), flags=wx.CENTER)
        addBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "+", size=(45,100)),
                                    wx.EVT_BUTTON, self.onOperation, vBtnSizer)
        equalsBtn = self.onWidgetSetup(wx.Button(self, wx.ID_ANY, "Enter", size=(45,100)),
                                       wx.EVT_BUTTON, self.onCalculate, vBtnSizer)
        masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
        masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
        mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
        mainSizer.Add(masterBtnSizer)
        self.SetSizer(mainSizer)
        mainSizer.Fit(self.parent)
        
    def onWidgetSetup(self, widget, event, handler, sizer, pos=None, flags=[]):
        """
        Accepts a widget, the widget's default event and its handler,
        the sizer for the widget, the position of the widget inside 
        the sizer (if applicable) and the sizer flags (if applicable)
        """
        widget.Bind(event, handler)        
        if not pos:
            sizer.Add(widget, 0, wx.ALL, 5)
        elif pos and flags:
            sizer.Add(widget, pos=pos, flag=wx.CENTER) 
        else:
            sizer.Add(widget, pos=pos)
        
        return widget

Лучшая часть об этом коде состоит в том, что она помещает все материалы создания кнопок в метод, поэтому нам не нужно писать «Wx.Button ()» снова и снова. Эта итерация также удаляла большинство звонков «Sizer.add». Конечно, на его месте вместо этого у нас есть много звонков методов «Self.onwidgetSetetup ()». Похоже, это все еще может быть повторно, но как!? Для моего следующего трюка я просматривал рефакторинг в книге Робина Данна и пришел к выводу, что его идеи стоят попробовать. (Он Создатель Wxpython, в конце концов.)

В его книге у него есть метод Button Builder, который выглядит похоже на мой виджет Builder, хотя его намного проще. У него также есть метод, который просто возвращает данные кнопки. Я взял эти идеи и адаптировал их для этой программы, как вы можете увидеть в моем конечном коде главы:

class MainPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
        self.parent = parent
        self.formula = []
        self.currentVal = "0"
        self.previousVal = "0"
        self.operator = None
        self.operatorFlag = False
        self.createDisplay()
        
    def createDisplay(self):
        """
        Create the calculator display
        """
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        masterBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
        vBtnSizer = wx.BoxSizer(wx.VERTICAL)
        numSizer  = wx.GridBagSizer(hgap=5, vgap=5)
        
        self.displayTxt = wx.TextCtrl(self, wx.ID_ANY, "0", 
                                      size=(155,-1),
                                      style=wx.TE_RIGHT|wx.TE_READONLY)
        
        for eachLabel, eachSize, eachHandler, eachPos in self.buttonData():
            button = self.buildButton(eachLabel, eachSize, eachHandler)
            if eachPos:
                numSizer.Add(button, pos=eachPos, flag=wx.CENTER) 
            else:
                vBtnSizer.Add(button)
                
        masterBtnSizer.Add(numSizer, 0, wx.ALL, 5)
        masterBtnSizer.Add(vBtnSizer, 0, wx.ALL, 5)
        mainSizer.Add(self.displayTxt, 0, wx.ALL, 5)
        mainSizer.Add(masterBtnSizer)
        self.SetSizer(mainSizer)
        mainSizer.Fit(self.parent)
        
    def buttonData(self):
        size=(45, 45)
        return (("0", size, self.onButton, (4,1)), 
                ("1", size, self.onButton, (3,0)),
                ("2", size, self.onButton, (3,1)), 
                ("3", size, self.onButton, (3,2)),
                ("4", size, self.onButton, (2,0)), 
                ("5", size, self.onButton, (2,1)),
                ("6", size, self.onButton, (2,2)), 
                ("7", size, self.onButton, (1,0)),
                ("8", size, self.onButton, (1,1)), 
                ("9", size, self.onButton, (1,2)),
                ("/", size, self.onOperation, (0,0)), 
                ("*", size, self.onOperation, (0,1)),
                ("-", size, self.onOperation, (0,2)),
                ("+", (45,100), self.onOperation, None),
                ("Enter", (45,100), self.onCalculate, None))
    
    def buildButton(self, label, size, handler):
        """
        Builds a button and binds it to an event handler.
        Returns the button object
        """
        button = wx.Button(self, wx.ID_ANY, label, size=size)
        self.Bind(wx.EVT_BUTTON, handler, button)
        return button

На данный момент мы должны вернуться назад и посмотреть, что это набрало нам. Оригинальный код составлял 132 строк, первый рефакторист ударил подсчет строки до 128, второй увеличил счет до 144, и этот последний отвел нас обратно до 120. Циничный может сказать, что все, что мы сохранены, – это 12 линий код. Я не согласен. Что мы в конечном итоге (будь то больше кода, чем оригинал), – это гораздо упрощенная поддержка базы кода. Это может быть изменено и хранится намного проще, чем оригинал.

Я надеюсь, что этот пост помогли вам посмотреть, как рефакторинг вашего кода на классы и методы могут сделать ваши программы более читабельными, проще для обслуживания – и поделиться с другими участниками, без позора!

Загрузки

  • calc.zip
  • Calc.tar.tar.

Дальнейшее чтение