高級軟件工程第三次做業 趙坤&黃亦薇

0.小組成員

趙坤2017282110261
黃亦薇201728210260java

1.項目Github地址

https://github.com/zkself/homework3
PS:建議使用chrome瀏覽器python

2.預估耗時

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 20  
· Estimate · 估計這個任務須要多少時間   10   
Development 開發 500   
· Analysis · 需求分析 (包括學習新技術)   30   
· Design Spec · 生成設計文檔   30   
· Design Review · 設計複審 (和同事審覈設計文檔)   0   
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範)   10   
· Design · 具體設計   30   
· Coding · 具體編碼   400  
· Code Review · 代碼複審   60   
· Test · 測試(自我測試,修改代碼,提交修改)   60   
Reporting 報告 120   
· Test Report · 測試報告   150   
· Size Measurement · 計算工做量   10   
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃   20  
合計   640  

3.解題思路

  拿到這個問題的時候,咱們倆面臨的 第一個問題 就是,她沒接觸過GUI編程,對於GUI編程思想缺少認識,因此咱們決定頁面佈局讓她來寫。選擇GUI庫的時候咱們考慮過tkinter、pyqt、wxpython。最後選擇了wxpython由於它文檔全面而且demo較多。而後咱們考慮的 第二個問題 就是,頁面上會有不少重複的控件,做用是顯示題目、以及讓用戶輸入答案、斷定結果。至關於每道題都有3個控件,全部題目都是這樣的經過3個控件組成的。因此我就在考慮能不能動態生成控件呢?好比說5道題那麼頁面上就有1至少5個控件這樣會形成代碼不簡潔,而後就找到了python提供的locals函數,這種動態生成控件(變量)的思想是我在js中學到的,python中正好也提供這種方法。咱們面臨的 第三個問題 就是計時器,計時器我發現wxpython沒有現成的控件,因此必須得本身寫,而咱們用了一個笨辦法就是用while循環的方式計時,時間間隔靠sleep函數,雖然比較笨可是實際上來講,pyhon執行每條代碼的時間是很快很快的,基本上是us級別的因此循環一次的時間能夠忽略不計。第四個問題 就是如何存儲用戶的記錄,考慮到存儲的信息不多且不是那麼重要,若是用數據庫來存儲會增長代碼的複雜性,咱們認爲沒有這個必要,最後採起的方式是直接以txt方式存儲在用戶當前打開的目錄下便可。第五個問題 多語言,這也是中期彙報把不少同窗卡住的地方。說實話之前確實沒接觸過,最開始咱們也是想實在不行就是用if-else來弄吧,可是個人夥伴說,確定有聰明的辦法,否則那麼多大公司在開發多語言軟件的時候都是這樣if-else的?而後咱們就搜了一下資料,發現python有一個模塊是專門解決多語言(i18n)問題的,而後這個問題才解決掉。總的來講,此次開發咱們遇到的主要是這五個問題。c++

4.設計實現過程

  針對上面遇到的五個重要問題來詳細說明一下實現過程。git

4.1 頁面佈局

  首先咱們知道,一個GUI程序用戶體驗取決於佈局是否合理、美觀。而GUI編程自己不是python的強項,而且因爲python跨平臺的特性,wxpython這個庫主要的優勢就是能解決跨平臺GUI編程問題,其自己不管是開發工具仍是API設計都沒法和windows平臺下的GUI編程語言所媲美。換句話說就是原生界面很差看。它的編程邏輯是,首先在窗體上添加一個frame空間,而後再選擇layout佈局。且定位問題只能經過開發者本身去嘗試,並不能作到c#那樣在任何位置拖放控件。說實話這個佈局問題確實讓咱們頭疼了一段時間,由於layout的選擇直接決定了佈局的方式、最後咱們選擇了最靈活的layout————GirdBagSizer,這種佈局能夠隨意在一行、一列放置任意控件。wxpython的佈局邏輯是把frame分紅行和列來看的,最後咱們調出了比較簡明的佈局。github

4.2 重複控件

  這個問題就是解決代碼的簡潔性。由於,試想一下咱們的頁面最重要的部件就是一個題目(Label)、提供用戶輸入(Text)、顯示判斷結果(Label)由這三種控件組成。若是頁面上一次顯示5個題目就是15個控件,若是10題目就是30個控件,且若是手寫這麼多不利於後期的版本升級,意思就是我要從顯示5個題改爲顯示10個題。因此咱們沒有選擇最笨的辦法而是借鑑了Js裏面的動態生成變量的方式,用for循環去動態生成變量(控件),這裏就不得不說腳本語言有腳本語言的好處,若是換成java/c++這種語言這個問題無解。還有這種方式,解決了變量名能夠用變量拼接,好比題目一的label就是lanbel1,題目二的label就是label2 。python提供了一個內置的方法locals來以以字典的方式存儲這種動態生成的變量,而後咱們訪問這些變量的時候直接個根據鍵值對的形式就能夠取到了。還有就是事件綁定的問題,因爲咱們設定的邏輯是用戶輸入完以後按回車直接出發textbox的回車事件,因此這些變量都是綁定的同一個事件,就是說多個事件源一個事件處理函數。chrome

4.3 計時器

  這裏又要感謝wxpython了,它裏面沒有計時的控件,因此沒辦法只能本身來寫了。咱們的邏輯很簡單,一個while循環裏面有個變量每次循環自加1,而後判斷是否達到60,若是達到60,分鐘變量加1,此變量歸0,繼續判斷,若是分鐘變量達到60則小時變量加1,分鐘、秒變量歸0。而後sleep1秒。雖然這個辦法很差,可是我測了一下,每條語句的執行時間是us級別的,因此精度基本不怎麼受影響。別的方法確定有,可是咱們沒想到。只能用這個折中的辦法來代替了。數據庫

4.4 用戶記錄存取

  前面分析了咱們爲何用存取txt這種方式來解決這個問題。這裏我講一下咱們的邏輯,首先進入程序後判斷本目錄是否有record.txt這個文件,若是有則讀取裏面的數據顯示在頁面上,若是沒有則新建一個record.txt。當用戶選擇關閉程序的時候把本次的統計結果保存到文件中。這樣若是用戶不當心刪掉了文件也不會出現bug問題,無非就是記錄丟失了。編程

4.5 多語言

  利用Python提供的gettext模塊能夠解決這個問題。首先咱們把須要顯示不一樣語言的文字用_()這種形式作上記號,而後在python目錄下的TOOls文件夾下找到i18n文件夾,cmd運行裏面的pygettext.py文件會生成一個message.pot文件,用 非記事本 編輯器打開,使用非記事本的緣由是win中文版系統默認編碼是gb2312,記事本也是,而且改不了。若是你用記事本打開那麼無論你最後保存成什麼編碼都是gb2312。我用的sublime text打開並編輯的。編輯的內容就是以msgid 和msgstr的這種形式來把你須要翻譯的字符串,手動翻譯成對應的語言,例如 msgid "hello world" ,msgstr "你好世界"。而後保存成.po文件,接着運行此目錄下另一個msgfmt.py文件用剛纔保存的文件再生成一個.mo文件。而後把這兩個文件移動到項目目錄下便可。這樣程序運行的時候就能按照你手動翻譯的結果把_()標記的字符串轉換成對應語言的翻譯了。c#

4.6 程序操做流程圖

5.代碼說明

頁面佈局以及控件循環生成windows

wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=wx.DefaultPosition, size=wx.Size(
    600, 500), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL)

self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)

gbSizer1 = wx.GridBagSizer(10, 10)
gbSizer1.SetFlexibleDirection(wx.BOTH)
gbSizer1.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)

# 生成控件
self.btnStart = wx.Button(self, wx.ID_ANY, _(u"開始答題"))
gbSizer1.Add(self.btnStart, span=(1, 1), pos=(1, 6))

self.labTime = wx.StaticText(self, wx.ID_ANY, _(u"時間:"))
gbSizer1.Add(self.labTime, span=(1, 1), pos=(
    2, 0), flag=wx.EXPAND, border=3)

self.labTime1 = wx.StaticText(self, wx.ID_ANY, u"0:0:0")
gbSizer1.Add(self.labTime1, span=(1, 1),
             pos=(2, 1), flag=wx.EXPAND, border=3)
self.labRatio = wx.StaticText(self, wx.ID_ANY, u"")
gbSizer1.Add(self.labRatio, span=(1, 1),
             pos=(2, 3), flag=wx.EXPAND, border=3)
for i in range(1, 6):#循環生成控件
    self.creatvar['self.labQues' +
                  str(i)] = wx.StaticText(self, wx.ID_ANY, u"")
    gbSizer1.Add(self.creatvar['self.labQues' + str(i)], span=(1, 5),
                 pos=(i + 2, 0), flag=wx.ALIGN_CENTER_VERTICAL, border=3)
    self.creatvar['self.texAns' +
                  str(i)] = wx.TextCtrl(self, i - 1, wx.EmptyString)
    gbSizer1.Add(self.creatvar['self.texAns' + str(i)],
                 pos=(i + 2, 6), flag=wx.ALIGN_CENTER_VERTICAL)
    self.creatvar['self.texAns' + str(i)].Disable()
    self.creatvar['self.labCor' +
                  str(i)] = wx.StaticText(self, i + 4, u"")
    gbSizer1.Add(self.creatvar['self.labCor' + str(i)],
                 pos=(i + 2, 7), flag=wx.ALIGN_CENTER_VERTICAL)

self.btnNext = wx.Button(self, wx.ID_ANY, _(u"再來五題"))
gbSizer1.Add(self.btnNext, span=(1, 1), pos=(9, 3))

self.btnPause = wx.Button(self, wx.ID_ANY, _(u"暫停"))
gbSizer1.Add(self.btnPause, span=(1, 1), pos=(9, 6))

self.btnEnd = wx.Button(self, wx.ID_ANY, _(u"結束答題"))
gbSizer1.Add(self.btnEnd, span=(1, 1), pos=(9, 8))
#################################################################
self.SetSizer(gbSizer1)
self.Layout()

事件綁定

self.btnStart.Bind(wx.EVT_BUTTON, self.btnStartOnButtonClick)
for i in range(1, 6):#動態控件綁定事件
    self.creatvar['self.texAns' +
                  str(i)].Bind(wx.EVT_TEXT_ENTER, self.texAnsOnTextEnter)
self.btnNext.Bind(wx.EVT_BUTTON, self.btnNextOnButtonClick)
self.btnPause.Bind(wx.EVT_BUTTON, self.btnPauseOnButtonClick)
self.btnEnd.Bind(wx.EVT_BUTTON, self.btnEndOnButtonClick)

事件處理函數

def btnStartOnButtonClick(self, event):
    for i in range(1, 6):
        self.creatvar['self.texAns' + str(i)].Enable()
        tmp = initFix()
        self.creatvar['self.labQues' +
                      str(i)].SetLabel("".join(printFix(tmp)))
        PostfixExp = changeToPostfix(tmp)
        self.res.append(PostfixExp)
    self.eve.set()
    t = threading.Thread(target=self.stopwatch, name='timer')
    t.start()
    self.btnStart.Disable()

def texAnsOnTextEnter(self, event):
    tex = event.GetEventObject()
    value = tex.GetValue()
    index = tex.GetId()
    cor = CalculatePostfix(self.res[index])
    if value == str(cor):
        self.creatvar['self.labCor' + str(index + 1)].SetLabel(_(u'正確'))
        self.ratio[0] += 1
        self.ratio[1] += 1
        self.labRatio.SetLabel(
            _(u'正確|總數') + ':' + str(self.ratio[0]) + '|' + str(self.ratio[1]))
    else:
        self.creatvar['self.labCor' +
                      str(index + 1)].SetLabel(_(u'錯誤,正確答案是') + str(cor))
        self.ratio[1] += 1
        self.labRatio.SetLabel(
            _(u'正確|總數') + ':' + str(self.ratio[0]) + '|' + str(self.ratio[1]))
    tex.Disable()

def btnNextOnButtonClick(self, event):
    self.res = []
    for i in range(1, 6):
        tmp = initFix()
        self.creatvar['self.labQues' +
                      str(i)].SetLabel("".join(printFix(tmp)))
        PostfixExp = changeToPostfix(tmp)
        self.res.append(PostfixExp)
        self.creatvar['self.texAns' + str(i)].Enable()
        self.creatvar['self.labCor' + str(i)].SetLabel('')
        self.creatvar['self.texAns' + str(i)].SetValue('')

def btnPauseOnButtonClick(self, event):
    if self.btnPause.GetLabel() == _(u'暫停'):
        self.eve.clear()
        self.btnPause.SetLabel(_(u'開始'))
    else:
        self.eve.set()
        self.btnPause.SetLabel(_(u'暫停'))

def btnEndOnButtonClick(self, event):
    self.eve.clear()
    writeFile(regularizeData(self.ratio))
    dlg = wx.MessageDialog(None, _(u"您的記錄已保存"), _(u"感謝使用"),
                           wx.OK | wx.ICON_QUESTION)
    if dlg.ShowModal() == wx.ID_OK:
        self.Close(True)
    dlg.Destroy()

計時器函數

def stopwatch(self):  # 計時器
    second = 0
    minute = 0
    hour = 0
    while 1:
        if not self.eve.isSet():#判斷eve變量是否爲假
            continue
        second += 1
        time.sleep(1)
        if second == 60:
            minute += 1
            second = 0
            if minute == 60:
                hour += 1
                minute = 0
                second = 0
        ntime = "%d:%d:%d" % (hour, minute, second)
        self.labTime1.SetLabel(ntime)

用戶記錄讀寫

# 初始化記錄
content = readFile()
if content:
    self.labRatio.SetLabel(_(u'正確|總數') + ': ' + content)
else:
    self.labRatio.SetLabel(_(u'當前無記錄'))
#寫用戶記錄
def btnEndOnButtonClick(self, event):
    self.eve.clear()
    writeFile(regularizeData(self.ratio))
    dlg = wx.MessageDialog(None, _(u"您的記錄已保存"), _(u"感謝使用"),
                           wx.OK | wx.ICON_QUESTION)
    if dlg.ShowModal() == wx.ID_OK:
        self.Close(True)
    dlg.Destroy()
def regularizeData(ratio):#把數據變成想要的格式
    recoard = str(ratio[0]) + '|' + str(ratio[1])
    return recoard


def readFile():#讀文件,若是沒有文件則生成新的
    cupath = os.getcwd()
    fpath = cupath + '/recoard.txt'
    if os.path.exists(fpath):
        f = open(fpath)
        content = f.read()
        f.close()

        return content
    else:
        f = open(fpath, 'w')
        f.close()
        return 0


def writeFile(content):#寫文件
    cupath = os.getcwd()
    fpath = cupath + '/recoard.txt'
    f = open(fpath, 'w')
    f.write(content)
    f.close()

多語言

gettext.install('lang', './locale/', unicode=False)
choices = ['簡體中文', '繁體中文', 'English']
dialog = wx.SingleChoiceDialog(None, "語言選擇", "請選擇語言", choices)
if dialog.ShowModal() == wx.ID_OK:
    langu = dialog.GetStringSelection()
    if langu == u'English':
        gettext.translation('lang', './locale/',
                            languages=['en_US']).install(True)
    if langu == u'繁體中文':
        gettext.translation('lang', './locale/',
                            languages=['zh_HK']).install(True)

6.運行結果

6.1 系統界面

首先選擇語言

而後進入系統界面(簡體中文)

繁體中文

英文

6.2 作題、計時、保存記錄

作題

暫定作題

更換題目

結束答題,存儲記錄

7.合做狀況

7.1趙坤

  完善了上次做業的隨機運算符個數問題、解決了動態生成重複控件功能、編寫了基礎的文件讀寫模塊、解決了多語言問題、編寫了測試用例
  我在任務過程當中比較急躁多虧了黃亦薇能幫我及時整理思路分析問題。女生作事每每比較仔細,有時候代碼裏的問題她能看出來而我卻須要屢次測試才能找到Bug在哪。

7.2黃亦薇

  構建了基本的界面佈局、編寫了計時器功能、編寫了顯示題目處理用戶輸入並判斷結果功能、使用coverage功能測試代碼分支覆蓋率
  個人代碼能力不強,趙坤也交給我了不少編碼技巧,多語言問題原本他都要放棄了,而後我鼓勵他一塊兒繼續找找資料而後才解決的了這個問題。

8.項目小結

PSP2.1 Personal Software Process Stages 預估耗時(分鐘) 實際耗時(分鐘)
Planning 計劃 20  10
· Estimate · 估計這個任務須要多少時間   10  10 
Development 開發 500   600
· Analysis · 需求分析 (包括學習新技術)   30  30 
· Design Spec · 生成設計文檔   30   30
· Design Review · 設計複審 (和同事審覈設計文檔)   0 
· Coding Standard · 代碼規範 (爲目前的開發制定合適的規範)   10  10 
· Design · 具體設計   30  40 
· Coding · 具體編碼   400  500
· Code Review · 代碼複審   60  70 
· Test · 測試(自我測試,修改代碼,提交修改)   60  60 
Reporting 報告 120  150 
· Test Report · 測試報告   150   180
· Size Measurement · 計算工做量   10   20
· Postmortem & Process Improvement Plan · 過後總結, 並提出過程改進計劃   20 30 
合計   640 770 

8.1趙坤

  我以爲此次我主要掌握了多語言問題的處理方法,以及測試用例到底怎麼編寫。兩我的結對編程最大的好處就是能夠交換思路,有時候一我的容易鑽牛角尖,而且找bug的時候別人看你的代碼和本身看是不同的,旁觀者清當局者迷。可是咱們仍是沒解決括號的問題……由於咱們當時不想大改程序結構,由於後面的GUI也是挺須要時間來搞定的。

8.2黃亦薇

  此次是趙坤一直在督促我快點寫,如今看來要不是他的督促時間根本不夠。他對於時間把控方面仍是須要我學習的。此次我學到了好多,GUI編程思想、多線程問題等。能夠說大的方向都是他把控的個人工做主要是給他提出建議以及幫他分擔任務。感謝趙坤對個人幫助,我會繼續努力的。

9.結對照片


相關文章
相關標籤/搜索