结对编程项目总结
一、简介
本博文为对带UI的小初高学习软件的结对编程项目的总结
二、代码复用说明
选定队友的个人项目作为主要参考,因为对比了我们两人的代码,发现队友的代码模块化程度好,比较适合修改,于是选择了队友的代码作为结对编程界面一个近似后端的模块,我负责优化这一方面,即加上生成答案,注册,登录的用户管理以及验证码的发送与识别等功能,队友负责使用thinter来生成python的图形化界面,下面介绍几个个主要的交互以及功能操作。
项目构建介绍
1.前端图形化界面的切换
结对编程开始时做了一个简易的进化模型,后来在这些界面上加上了修改密码和忘记密码的功能界面,以及添加了密码输入错误,密码设置格式错误,发送验证码时等等许多的提示信息。


图形化界面主要由我的队友设计,本程序是使用thinter的GUI图形化界面的,首先使用Tk()函数生成一个窗口,然后定义许多个的框架Frame,通过对每个框架设置不同的标签,输入框,按钮等等来表示所有的界面,使用.pack()生成框架和.pack_forget()忘记框架,这样就实现了在一个窗口中界面的切换
在类的初始化函数中,建立所需要的全部框架
1 class GUIForeground: 2 """前端GUI主类""" 3 4 def __init__(self, my_window): 5 """初始化函数 6 Args: 7 my_window:用thinter的Tk()函数生成的窗口对象 8 """ 9 10 # 设置窗口 11 self.window = my_window 12 13 # 以下为对所有窗口框架的建立,因为界面切换时通过框架实现的,所以用界面代替框架 14 self.welcome_page = Frame(self.window) # 初始界面 15 self.register_page = Frame(self.window) # 注册界面 16 self.log_in_page = Frame(self.window) # 登录界面 17 self.forget_password_page = Frame(self.window) # 忘记密码界面 18 self.confirm_password_frame = Frame(self.window) # 弹出确认密码的框架 19 self.change_password_page = Frame(self.window) # 修改密码界面 20 self.determine_page = Frame(self.window) # 登录后选择难度界面 21 self.select_page = Frame(self.window) # 选择题量界面 22 self.make_question_page = [] # 做题界面 23 self.finally_page = Frame(self.window) # 结束(显示分数)界面
下面以登录界面为例,首先让需要输入的两个变量清空,因为登录界面的前置页面是开始界面和注册界面,所以要先清除这两者的框架,然后放入登录框架,接下来设置框架内的元素,包括Label(标签),Entry(输入框),Button(按钮),在Button元素command为设置按下该按键后跳转的函数
1 def log_in(self): 2 """登录界面 3 4 提示用户输入用户名或手机号,再输入密码,同时用户也可以返回开始界面或者进入忘记密码界面 5 6 用户名和密码不在用户库中会提示 7 """ 8 9 self.username = StringVar() 10 self.password = StringVar() 11 12 self.welcome_page.pack_forget() # 清除开始界面的框架 13 self.register_page.pack_forget() # 清除注册界面的框架 14 self.log_in_page.pack() # 放入登录界面的框架 15 16 Label(self.log_in_page, text="用户名/手机号:"). \ 17 grid(row=1, stick=W, pady=10) 18 Entry(self.log_in_page, textvariable=self.username). \ 19 grid(row=1, column=1, stick=E) 20 Label(self.log_in_page, text='密码: '). \ 21 grid(row=2, stick=W, pady=10) 22 Entry(self.log_in_page, textvariable=self.password, show='*'). \ 23 grid(row=2, column=1, stick=E) 24 Button(self.log_in_page, text='登录', command=self.check_user). \ 25 grid(row=3, stick=W, pady=10) 26 Button(self.log_in_page, text="返回开始界面", wid=12, height=1, 27 command=self.welcome). \ 28 grid(row=4, pady=10) 29 Button(self.log_in_page, text="忘记密码", wid=8, height=1, 30 command=self.forget_password). \ 31 grid(row=4, column=2, pady=10)
如上操作,依次设置所有框架的元素以及通过Button来进行多个页面的转化操作即可
2.生成答案与选项功能
生成答案和选项功能主要由我来负责,选择了队友的代码作为生成题目的主体,然后将生成的题目作为参数进入生成答案的函数,生成答案的函数返回值即为正确答案,原本想将正确答案设置为最简多项式,但和队友讨论后,发现这样设置的话,代码复杂度较高,于是我们便选择了保留小数作为最后的正确答案.
我们以题目“cos3^2+32*√32”为例
在K的生成题目函数中,将字符串数组str_one_question列表[“cos”][“3”][“^2”][“+”][“32”][“*”][“√”][“32”]联立为一个字符串,返回值为一整个题目字符串 “cos3^2+32*√32”,这里我卡壳了很久,因为在这里我默认队友函数的返回值为列表,在函数外再链接为字符串,结果队友返回值为字符串,在编写代码时也没报错,但运行有错误
1 def make_question(): 2 """ 3 """ 4 str_one_question += "=?\n" 5 str3 = "".join(str_one_question) 6 return str3
然后修改为
1 def make_question(): 2 """ 3 """ 4 return str_one_question
在生成答案函数make_answer(str_one_question)时,是这么处理的,使用五次遍历,按运算优先级依次计算所有符号,优先级:括号>三角函数>根号或平方>乘除>加减,最后返回只有一个数字的字符串
第一次遍历找到括号,把括号内的内容放入make_answer()函数中,用其返回值代替括号和括号内的内容
1 def make_answer(str_question): 2 for position_left in range(0, len(str_question)): 3 if str_question[position_left] == '(': 4 for position_right in range(position_left, len(str_question)): 5 if str_question[position_right] == ')': 6 temp_list = make_answer( 7 str_question[position_left + 1: position_right]) 8 del str_question[position_left:position_right + 1] 9 for i in range(0, len(temp_list)): 10 str_question.insert(position_left + i, temp_list[i]) 11 break 12 break
第二次遍历计算三角函数,比如遍历整个式子,如果发现了"cos"字符串,则把"cos"字符串和后面的字符串"3"整合为一个字符串数字也就是cos3即"-0.98999”
1 # 对三角函数进行处理 2 for position in range(len(str_question)): 3 if str_question[position] == 'sin' or str_question[position] == 'cos' \ 4 or str_question[position] == 'tan': 5 if str_question[position + 1].isdigit(): 6 if str_question[position] == 'sin': 7 str_question[position] = math.sin( 8 float(str_question[position + 1])) 9 10 if str_question[position] == 'cos': 11 str_question[position] = math.cos( 12 float(str_question[position + 1])) 13 14 if str_question[position] == 'tan': 15 str_question[position] = math.tan( 16 float(str_question[position + 1])) 17 18 del str_question[position + 1] 19 if position >= len(str_question) - 1: 20 break
第三次遍历计算√和^2,原理同上
1 # 对根号和平方进行处理 2 position = 0 3 while position < len(str_question): 4 if str_question[position] == '√': 5 str_question[position] = math.sqrt( 6 float(str_question[position + 1])) 7 del str_question[position + 1] 8 if str_question[position] == '^2': 9 str_question[position - 1] = math.pow( 10 float(str_question[position - 1]), 2) 11 del str_question[position] 12 position -= 1 13 14 position += 1
第四次遍历计算乘除,第五次遍历计算加减,最后只剩下一个数字字符了,这就是最终的答案。
将最终答案返回,放入生成选项的函数make_select(answer,right_num)中,其中right_num为随机数,是正确答案在ABCD四个选项中的位置,比如当right_num为2时,正确答案就放在B选项,生成的选项是正确答案的0.86~1.14倍间,生成的干扰项不能与另外三个选项相同,所有选项保留三位小数,最后返回按顺序生成的四个选项的float型列表
1 def make_select(answer, right_num): 2 """生成选项 3 4 answer为正确答案,是一个数字,right_num为该正确答案在ABCD四个选项中的位置,当right_num 5 ==1时,A为正确选项,以此类推 6 7 生成的三个干扰选项是正确答案的0.86-1.14倍,使用了一个while循环来使得干扰项不与正确答案一致 8 9 Args: 10 answer:正确答案 11 right_num:正确答案所在ABCD四个选项的位置 12 13 Return: 14 返回生成的四个选项,用列表存储 15 """ 16 17 str_one_answer = [] 18 if answer == 0: 19 for i in range(1, 5): 20 if i == right_num: 21 str_one_answer.append(0) 22 else: 23 str_one_answer.append(i) 24 else: 25 for i in range(1, 5): 26 if i == right_num: 27 str_one_answer.append(answer) 28 else: 29 false_answer = float(answer) * 0.01 * float( 30 random.randint(86, 114)) 31 false_answer = round(false_answer, 3) 32 33 # 不能产生重复的选项 34 canable = 1 35 while canable >= 1: 36 if i == 1: 37 if false_answer == answer: 38 canable += 1 39 false_answer = answer - 0.001 40 false_answer = round(false_answer, 3) 41 else: 42 for i1 in range(0, i - 1): 43 if false_answer == str_one_answer[i1] or \ 44 false_answer == answer: 45 canable += 1 46 false_answer = str_one_answer[i1] - 0.001 47 false_answer = round(false_answer, 3) 48 canable -= 1 49 str_one_answer.append(false_answer) 50 return str_one_answer
3.显示题目与选项
队友的个人项目中生成试卷的函数make_test()以及模块化,只需传入参数难度和题目数量就可以在指定的目录生成题目文档,我在此基础上增加了生成答案的模块,在生成题目的同时,也会生成答案和三个干扰选项(正确答案的标号会放在题目的后面,不会显示出来,用于检测),存放在同一目录下,试卷的文档名为时间加”.txt”,答案的文档名为时间加”_answer.txt”,两者同时生成。在前端界面提示用户选择难度和输入题目数量后,就生成题目和答案这两个txt文档,然后依次读取这两个文档,读取第一个文档的题目显示于前端界面,读取第二个文档的四个选项显示于前端界面,用户选择一个答案后,在后端将选择的答案与正确的答案进行比对,如果一致则得分增加,反之得分不变,答完所有题目后将得分转化成百分制并显示出来。
1 def make_test(self): 2 """生成试卷 3 4 先调用一次后端的的make_test,生成试卷和答案,存放在目录下 5 然后循环调用接下来的make_question,读取题目和答案 6 参数就是左边的题目(加答案)和右边的三个选项 7 循环结束后直接进入结束界面,显示分数 8 """ 9 10 try: 11 if 10 <= self.question_num.get() <= 30: 12 location = Background.make_test(self.question_level, 13 self.question_num.get()) 14 15 file_question = open(location + '.txt', 'r') 16 file_answer = open(location + 'answer.txt', 'r') 17 question = [] 18 all_select_position = [] 19 for every_question in file_question.readlines(): 20 question.append(every_question) 21 self.make_question_page.append(Frame(self.window)) 22 for every_answer in file_answer.readlines(): 23 every_answer = every_answer[1:len(every_answer) - 2] 24 all_select_position.append(every_answer.split(',')) 25 26 self.select_page.pack_forget() 27 self.make_question(0, question, all_select_position) 28 file_question.close() 29 file_answer.close() 30 else: 31 showinfo(title='错误', message='请输入题目数量为10-30') 32 except TclError: 33 showinfo(title='错误', message='请输入正确的题目数量')
以下是生成问题模块代码
1 def make_question(self, num, question, all_select_position): 2 """出题/答题界面 3 4 该界面最上方显示总题目数量 5 然后显示题号(num),题目(question[num]), 6 以及ABCD四个选项(all_select_position[num]) 7 8 Args: 9 num:题号 10 question:题目列表 11 all_select_position:选项列表 12 """ 13 14 self.right_answer = int(question[num][len(question[num]) - 2]) 15 question[num] = question[num][0:len(question[num]) - 2] 16 17 self.make_question_page[num].pack() 18 19 Label(self.make_question_page[num], 20 text="共有" + str(self.question_num.get()) + "道题目"). \ 21 grid(row=1, pady=10) 22 23 Label(self.make_question_page[num], 24 text="(优先级:括号>三角函数>平方/根号>乘除>加减)"). \ 25 grid(row=2, pady=10) 26 27 Label(self.make_question_page[num], 28 text=str(num + 1) + ". " + question[num]). \ 29 grid(row=3, pady=10) 30 31 Button(self.make_question_page[num], 32 text='A. ' + all_select_position[num][0], 33 command=lambda: self.select_abcd(1, num, question, 34 all_select_position)). \ 35 grid(row=4, pady=10) 36 37 Button(self.make_question_page[num], 38 text='B. ' + all_select_position[num][1], 39 command=lambda: self.select_abcd(2, num, question, 40 all_select_position)). \ 41 grid(row=5, pady=10) 42 43 Button(self.make_question_page[num], 44 text='C. ' + all_select_position[num][2], 45 command=lambda: self.select_abcd(3, num, question, 46 all_select_position)). \ 47 grid(row=6, pady=10) 48 49 Button(self.make_question_page[num], 50 text='D. ' + all_select_position[num][3], 51 command=lambda: self.select_abcd(4, num, question, 52 all_select_position)). \ 53 grid(row=7, pady=10)
4.登录用户,注册用户,修改用户密码,忘记用户密码:
这一部分是队友完成,因为这次结对编程在登录用户的要求上加上了使用手机号注册用户和修改密码的要求,所以,这次不能和个人项目一样直接定义许多的用户对象,因此我们使用了一个文档来保存所有的用户信息,文档位置为C:\khl_tanm\username_and_password.txt。
(1) 登录用户:在前端的登录界面上输入用户名和密码后,后端在用户信息库文档中对所有用户中进行搜索,如果找到对应的用户名和密码则让前端提示登录成功,进入下一个界面
(2) 注册用户:注册界面会提示用户输入手机号,后端程序会发送验证码,如果用户输入的验证码与发送至手机号上的验证码一致,则出现密码输入框,提示用户输入密码,输入格式正确的密码两遍后,即可注册成功,添加新的用户信息到用户信息库中
(3) 修改用户密码:修改用户密码即在用户库中搜索用户名,需要用户输入原密码和两遍新密码,原密码一致,两次输入的新密码一致且符合规范,则在用户库中修改对应账户的密码
(4) 忘记用户密码:当用户进入忘记密码界面时,会让用户输入注册用手机号,手机号验证成功后即可重新设置密码
三、学到的经验,教训与体会
这是第一次结对编程,以前都是个人编程,结对编程为我们以后的软件开发团队合作提供了一定的经验.有了基本的前后端开发的意识,两个人虽然是结对编程,但各有侧重,比如我主要偏向后端,队友偏向前端,我在后端依靠队友个人项目得生成题目函数.生成答案和题目编号并写入文档,还有手机验证码服务,然后队友从文档读取信息显示到前端,这一段我们是前后端先分离开发再组合,但用户库的管理以及验证码的发送和识别则是我们同时前后端联动完成的。两个人的项目开发,需要不断的交流,在交流上,我们既取得了一定便利,也收获了教训,在项目开始前,我和队友积极交流,搭建出了项目的框架,并敲定了具体实现,这一阶段我们虽然交流了一两个小时,但讨论完后感觉思维活跃.在具体实现时,由于没有及时交流,在一个非常容易的点上我卡了许久,检查了许久才发现,影响了效率..在后面的开发过程中,我们两个不断沟通,出现问题了就两人一起研究,开发效率大大提高了.

浙公网安备 33010602011771号