小学四则运算之结对项目
一、Coding.Net项目地址:
https://git.coding.net/lvgx/majx.git
二、PSP展示
PSP |
任务内容 |
计划共完成需要的时间(min) |
实际完成需要的时间(min) |
Planning |
计划 |
2*60 |
3*60 |
Estimate |
估计这个任务需要多少时间,并规划大致工作步骤 |
2*60 |
3*60 |
Development |
开发 |
22*60 |
27*60 |
Analysis |
需求分析 (包括学习新技术) |
5*60 |
7*60 |
Design Spec |
生成设计文档 |
60 |
60 |
Design Review |
设计复审 (和同事审核设计文档) |
30 |
30 |
Coding Standard |
代码规范 (为目前的开发制定合适的规范) |
60 |
60 |
Design |
具体设计 |
5*60 |
6*60 |
Coding |
具体编码 |
8*60 |
10*60 |
Code Review |
代码复审 |
30 |
30 |
Test |
测试(自我测试,修改代码,提交修改) |
60 |
60 |
Reporting |
报告 |
2*60 |
2*60 |
Test Report |
测试报告 |
60 |
60 |
Size Measurement |
计算工作量 |
30 |
30 |
Postmortem & Process Improvement Plan |
事后总结, 并提出过程改进计划 |
30 |
30 |
三、接口设计
接口提供了一份记载着可供公众访问的方法的契约,它定义了两个对象间可以具有的关系,只要接口不变,这个关系的双方都是可以替换的。但不是有了接口就万事大吉了,应该避免公开未定义于接口中的方法,否则其它对象可能对那些并不属于接口的方法产生依赖,而这是不安全的。因为这些方法随时都可能发生改变或被删除,从而对到整个系统失灵。一个理想的软件系统应该为所有类定义接口。这些类只向外界提供它们实现的接口中规定的方法,任何别的方法都留作自用,其所有属性都是私用的,外界只有通过接口中定义的存取操作与之打交道。
经过我查阅相关资料后,我了解到:
1. Information Hiding(信息隐藏):指在设计和确定模块时,使得一个模块内包含的特定信息(过程或数据),对于不需要这些信息的其他模块来说,是不可访问的。它主要包括两个方面:①隐藏复杂度:这样你就不用再去应付它,除非你要特别关注的时候;②隐藏变化源:这样当变化发生时,其影响就能被限制在局部范围内。复杂度的根源包括复杂的数据类型、文件结构、布尔判断以及晦涩的算法等等。
从用具名常量代替字面常量,到创建数据类型,再到类的设计、子程序的设计以及子系统的设计等等,信息隐藏在设计的所有层次上都有很大作用,尤其有助于设计类的公开接口。
2. Interface Design(接口设计):由于接口是提供给其他模块或者系统使用的一种约定或者规范,因此接口必须要保证足够的稳定性和易用性,这是设计接口的基本要求。设计接口要采用面向对象的思想,提供类接口或者COM接口。扩展接口可以采用版本特性,不同版本的接口实现可以允许有差异,但是提供版本查询功能;或者用序号表示新增的接口,如SetCookie、SetCookie1、SetCookie2。
接口设计的要求有:
①稳定性:接口必须相对稳定,否则将导致接口的使用者和提供者为了适应新接口而不断修改接口的实现,可能重复进行无用功,严重时影响整个软件开发进度。首先,接口的语义必须明确。包括接口调用方法、接口名称、参数的类型和名称。抽象的接口名称或者参数名称容易使人困惑或者理解错误。其次,采用版本定义来区分接口的差异。比如提供接口版本查询功能,接口实现着提供接口版本的查询功能。
②易用性:接口是提供给第三方使用的,较难使用的接口会导致接口使用者的抱怨。
③规范性:主要是接口设计的代码规范,这是最基本的要求。可移植性:对于需要在多平台实现的接口需要考虑接口本身的可移植性,因此最少使用对于系统依赖的类型作为接口的参数类型或者返回值类型。
④鲁棒性:接口需要有适度的鲁棒性,主要是指能够在多种情况下接口都能实现统一的效果,不会随着调用者传入的参数的变化而导致接口的输出出现违背接口语义的情况出现。
⑤安全性:接口定义时需要严格限制参数的读写权限,如果只能是只读的参数一定要设置成const,以免出现非法使用。
⑥兼容性:这是接口扩充的原则,必须保证同一个接口实现后向兼容前一版本的使用。扩充的同类接口也能兼容老接口的实现。
3. Loose Coupling(松耦合):松耦合就是降低系统部件和部件之间的耦合性,也就是降低部件的依赖性,使得部件之间相对独立,这样对于日后系统的维护及扩展都是很有好处的。
只要一个方法操作是类而非接口,那么你就只能使用这个类及其子类。如果你想要将这个方法应用到不在此继承结构中的某个类,那么你就达不到目的。接口可以很大程度放宽这种限制,因此使用接口而非继承使得我们可以编写可复用性更好的代码。
在结对编程中,对接口设计时,我们使用了ActionListener(事件监听器)接口,它是用于接收操作事件的侦听器接口,这个接口定义了所有动作监听器共有的动作,用它来处理按钮上的各种动作事件。同时,这个ActionListener(事件监听器)接口包含了处理事件的actionPerformed方法,它通过覆盖这些方法来响应事件。
凡是对处理操作事件感兴趣的类都可以可以实现此接口,而使用该类创建的对象可使用组件的addActionListener 方法向该组件注册。在发生操作事件时,调用该对象的actionPerformed 方法。当特定于组件的动作(比如被按下)发生时,由组件(比如 Button)生成此高级别事件。事件被传递给每一个ActionListener 对象,这些对象是使用组件的addActionListener 方法注册的,用以接收这类事件。
当GUI 建立后,我们将面板添加到窗体并显示结果。当用户点击按钮时,程序调用actionPerformed方法,通过if语句来判断是哪一个按钮被点击,然后在对话框中显示相应的内容。
利用一个监听器来处理事件的缺点是,当程序比较复杂时,需要一大串的if 语句来实现,程序代码较难阅读与维护。当然,如果处理的事件较少,这种方式比较简单。
当然了,我们在设计这个接口时,确实也利用了上面所说的方法,比如说Information Hiding(信息隐藏),同时,我们设计的接口也满足了一些相应的基本要求,比如说稳定性,规范性,易用性等等。
四、计算模块接口的设计与实现过程
在这次结对项目中,我们的代码共设计了9个类,12个函数,它们之间的相互调用关系是:
主函数Command与图形用户界面gui是主类,共同调用了四个类:suanfa、suanfa1、suanfa2、suan;
suanfa、suanfa1、suanfa2、suan类共同调用了panduan、suan1类;
suan类还调用了test类。
下图形象的展示了它们之间的相互调用关系:
五、计算模块接口部分的性能改进
下图是性能测试的到的截图:
六、计算模块部分单元测试展示
在这次结对项目中,我们的代码中一共有九个类,通过对作业要求中所提供的链接进行了单元测试的学习之后,我们打算采用统一的独立的测试方法来观测它们各个类的代码覆盖率。
以下是单元测试的部分代码:
public class test { private static int i=10; private static int j1=1; private static int j2=50; private static int p=4; private static int n=0; private static String s="11+(43-(35-(14+26)))"; public static void main(String[] args) { System.out.println(suanfa.suanfa(i, j1, j2, n, p)); //题目函数一 System.out.println(suanfa.suanfa(i, j1, j2, n, p)); System.out.println(suanfa.suanfa(i, j1, j2, n, p)); gui.main(null); // 中文界面 }
下图是单元测试得到的测试覆盖率截图:
七、计算模块部分异常处理说明
在我们的项目中,我们共设计了四种异常处理,分别是出题数量异常处理,数值左边界异常处理,数值右边界异常处理和运算符数量异常处理。
1. 出题数量异常处理
当用户输入的出题数量的数值不在[1,10000]范围内时,提醒用户当前输入的数值不合法,要求用户重新输入。
代码展示:
try { n1 = Integer.parseInt(n.getText()); if (n1 <= 0 || n1 > 10000) { n.setText("不可以哦,要在1至10000之间"); return; } flag0 = 1; } catch (Exception a) { n.setText("格式不正确,请重新填写!"); }
对应场景:
2. 数值左边界异常处理
当用户输入的出题数值左边界不在[1,100]范围内时,提醒用户当前输入的数值不合法,要求用户重新输入。
代码展示:
try { m11 = Integer.parseInt(m1.getText()); if (m11 <= 0 || m11 > 100) { m1.setText("不可以哦,要在1至100之间"); return; } flag1 = 1; } catch (Exception a) { m1.setText("格式不合法,请重新填写!"); }
对应场景:
3. 数值右边界异常处理
当用户输入的出题数值右边界不在[50,1000]范围内时,提醒用户当前输入的数值不合法,要求用户重新输入。
代码展示:
try { m22 = Integer.parseInt(m2.getText()); if (m22 < 50 || m22 > 1000) { m2.setText("不可以哦,要在50至1000之间"); return; } flag2 = 1; } catch (Exception a) { m2.setText("格式不合法,请重新填写!"); }
对应场景:
4. 运算符数量异常处理
当用户输入的运算符数量不在[1,10]范围内时,提醒用户当前输入的数值不合法,要求用户重新输入。
代码展示:
try { o1 = Integer.parseInt(o.getText()); if (o1 <= 0 || o1 > 10) { o.setText("不可以哦,要在1至10之间"); return; } flag3 = 1; } catch (Exception a) { o.setText("格式不合法,请重新填写!"); }
对应场景:
八、界面模块的详细设计过程
在这次结对项目中,我们定义了一个GUI类,使用了图形用户界面GUI来设计了四个界面模块,分别是出题、做题、题目统计与上传文件这四个界面模块,而且这四个界面模块之间是相互嵌套、相互继承的,通过事件监听器接口,可以实现从一个GUI界面到另一个GUI界面的相互转换。
以下是设计GUI界面的部分代码:
public class gui extends JFrame { private JButton chuti = new JButton("出题"); private JButton shangchuan = new JButton("上传文件"); private JCheckBox chengchu = new JCheckBox("乘除"); private JCheckBox kuohao = new JCheckBox("括号"); private JTextField i = new JTextField(8); private JTextField j1 = new JTextField(8); private JTextField j2 = new JTextField(8); private JTextField p = new JTextField(8); private JLabel label = new JLabel(); private static JLabel label0 = new JLabel(); private static JButton next = new JButton("下一题"); private static JButton jbtijiao = new JButton("提交"); private int i1, j11, j22, p1, n = 0, b = 0; String line; int frrl0 = 0; int frrl1 = 0; int frrl2 = 0; int frrl3 = 0; int frrl4 = 0; static int frrl6 = 0; ArrayList<String> list = new ArrayList<String>(); static ArrayList<String> list0 = new ArrayList<String>(); static int rightAnswer = 0; public static void main(String[] args) { gui frame = new gui(); frame.setTitle("出题"); frame.setLocationRelativeTo(null); // Center the frame frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(600, 300); frame.setVisible(true); }
public gui() { JPanel p1 = new JPanel(); p1.setLayout(new GridLayout(4, 2, 5, 5)); p1.add(new JLabel("出题数[1,10000]:")); p1.add(i); p1.add(new JLabel("数值左边界[1,100]:")); p1.add(j1); p1.add(new JLabel("数值右边界[50,1000]:")); p1.add(j2); p1.add(new JLabel("运算符数[1,10]:")); p1.add(p); JPanel jpButtons = new JPanel(); jpButtons.add(chuti); jpButtons.add(shangchuan); chuti.setMnemonic('V'); chuti.setToolTipText("出题"); shangchuan.setHorizontalAlignment(SwingConstants.CENTER); setLayout(new BorderLayout()); add(jpButtons, BorderLayout.NORTH); chengchu.setMnemonic('C'); kuohao.setMnemonic('B'); JPanel jpCheckBoxes = new JPanel(); jpCheckBoxes.setLayout(new GridLayout(3, 1)); jpCheckBoxes.add(chengchu); jpCheckBoxes.add(kuohao); add(jpCheckBoxes, BorderLayout.WEST); add(p1, BorderLayout.CENTER); chengchu.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { n = 1; } }); kuohao.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { b = 1; } }); chuti.addActionListener(new ButtonListener());// 出题监听器 shangchuan.addActionListener(new MouseListener()); // 文件上传功能 }
以下是实现的GUI界面的部分截图:
1.出题:
2.做题:
3.题目统计:
4.上传文件:
九、界面模块与计算模块的对接
我们在这次结对项目中,通过设计了GUI类,使用了图形用户界面来进行UI模块的设计,至于这些各种不同UI模块之间的对接,则是通过事件监听器接口来实现的。
我们在事件源的类中使用addActionListener注册,成为一个Actionlistener接口,它的功能是:当发生事件后,可以监听到这个事件,同时就会执行Actionlistener接口它唯一的方法actionperformed来完成监听后需要执行的操作.这就相当于把监听给予一个类,让它自己具有监听的功能。当特定于组件的动作(比如被按下)发生时,由组件(比如 Button)生成此高级别事件。事件被传递给每一个ActionListener 对象,这些对象是使用组件的addActionListener 方法注册的,用以接收这类事件。
通过构造器建立用户GUI,定义一个面板Jpanle,,增加两个按钮,然后利用 JButton.addActionListerner将两个按钮加入到一个活动监听器SimpleLister中,最后,两个按钮添加到面板。当GUI 建立后,我们将面板添加到窗体并显示结果。当用户点击按钮时,程序调用actionPerformed方法,通过if语句来判断是哪一个按钮被点击,然后在对话框中显示相应的内容。
以下是设置监听器的部分代码:
chuti.addActionListener(new ButtonListener());// 出题监听器 shangchuan.addActionListener(new MouseListener()); // 文件上传功能 } private class MouseListener implements ActionListener { public void actionPerformed(ActionEvent e) { eventOnImport(new JButton()); } } private class ButtonListener implements ActionListener { public void actionPerformed(ActionEvent e) {
下图是我们实现的UI模块的截图:
1.出题:
2.做题:
3.题目统计:
4.上传文件:
十、结对过程描述
在这次结对项目中,杨有存(2016012061)与马佳欣(2016012009)共同结对完成了本次作业,我们私下在寝室里共同讨论了这次项目的设计过程,并对代码的编写与测试都提出了各自的建设性意见。
下图为我们在讨论研究时的图片:
十一、结对编程的优缺点与结对成员优缺点
(1)结对编程的优点:
1. 在开发层次,结对编程能提供更好的设计质量和代码质量,两人合作解决问题的能力更强。两人合作,还有相互激励的作用,工程师看到别人的思路和技能,得到实时的讲解,受到激励,从而努力提高自己的水平,提出更多创意。
2. 对开发人员自身来说,结对工作能带来更多的信心,高质量的产出能带来更高的满足感。
3. 在企业管理层次上,结对能更有效的交流,相互学习和传递经验,分享知识,能更好地应对人员流动。
(2)结对编程的缺点:
1.对于有不同习惯的编程人员,可以在起工作会产生麻烦,甚至矛盾。 有时候,程序员们会对一个问题各执己见(代码风格可能会是引发技术人员口水战的地方),争吵不休,反而产生重大内耗。
2.两个人在一起工作可能会出现工作精力不能集中的情况,程序员可能会交谈一些与工作无关的事情,反而分散注意力,导致效率比单人更为低下。
3.面对新手,有经验的老手可能会觉得非常的烦躁,他们更喜欢单兵作战,找个人来站在他背后看着他可能会让他感到非常的不爽,最终导致编程时受到情绪影响,反而出现反作用,导致团队的不和谐。
4.新手在面对有经验的老手时会显得非常的紧张和不安,甚至出现害怕焦虑的的精神状态,从而总是出现低级错误,而老手站在他们后面不停地指责他们导致他们更加紧张,出现恶性循环。最终导致项目进展效率低下,并且团队貌合神离。
(3)结对成员杨有存的优缺点:
优点:1.勤奋好学,积极上进,对于不懂的知识点能够认真学习;
2.踏实认真,耐心专注,对工作任务认真负责;
3.平易近人,性格随和,能够认真听取队友的意见。
缺点:动手实践能力不是很好,而且对于项目所需的技术也不是很熟练。
(4)结对成员马佳欣的优缺点:
优点:1.算法能力很强,对于项目所需的技术很擅长;
2.任劳任怨,踏实勤奋,对于这次结对项目认真负责;
3.阳光开朗,积极乐观,能够给予队友鼓励与支持;
缺点:对于java语言不是很擅长。
十二、PSP表格记录(见上文第二条目)