结对编程——四则运算
1759226 彭志远
1759215 王溯
一.前言
在软件工程综合实践课上,我们两两组队完成一道编程题:帮助小学老师每周给同学出300道四则运算练习题,自选一种编程方式实现,1、两个运算符,100以内的数字,不需要写答案,估计编写时间 2、需要写答案,并且保证答案在0-100之间,并且估计时间。3、增加要求(题目避免重复,可扩展性,可定制,具体定制),并且估计时间。
二.需求分析
关于本次合作编程的需求部分,我们经过商讨之后初步确立了如下需求:
1. 关于算式——本次程序中算式应该满足如下要求:运算符号在“加、减、乘、除“中随机产生;a、b两个运算数分别为一位或二位整数,即整形变量(int);与此同时要计算出答案,最后得出的结果应该不大于100。
2. 关于输出——本程序在输出时应该满足如下要求:每行应该输出3个算式,且中间要保持一定的间隔。
三.系统环境
开发环境:JDK10.0
编辑器(IDE):Intellij IDEA
语言:Java
硬件环境:处理器:Inter(R)Core(TM)i5-7300HQ CPU @ 2.50GHz
系统环境:Windows10 64位操作系统,基于x64的处理器
四.编写过程
首先根据分析过的软件需求以及Java面向对象的编程思想,为了使合作编程进行地更加顺利,我们在编写代码之前先做了一些准备工作——画UML类图。我们让王溯同学来解释一下关于我们为什么要画UML类图:UML类图的确定相当于确定了整个程序的基本框架,并且同时确定了各个模块之间交互时使用的数据接口以及交互方式。在本程序的模式设计中,我们使用了大量的关联模式,以便于数据在各个模块之间的传递。
关于软件编程的分工,我负责的是Symbol、Form以及Figure类的编写,王溯同学负责的是Calculate类、Print类以及Main函数的编写。
以下是我们最开始暂定的UML类图:
在本张图中可以看出,我们在这里使用了5个类,分别是Symbol类、Figure类、Form类、Calculate类以及Print类。
在实际编程的过程中我们发现,这张UML类图中有一些问题。比如说:在输出算式以及结果的过程中,我们需要通过调用Print类里面的成员变量来将结果输出。但是我们可以发现,Print类中只有一个calculate可以调用,但是这个变量是Calculate类的一个对象。那么我们倒回到Calculate类中,我们会发现,在这个类中,只有一个answer可以调用输出,所以整个对象还需继续往前推。毫无疑问,这就十分容易产生代码的冗余。另外一个就是对于Calculate类来说,要想计算整个式子的结果,那么必须要分别得到两个操作数a、b以及它们之间的符号。那么我们又需要从Calculate类分别推回Symbol类和Figure类。这就又会产生大量的代码冗余。经过我们的商量,我们决定对这个框架进行一些修改,得到如下所示:
同时,我们可以看到,在这次的框架修改中,王溯同学还在里面新增加了一个WriteInFlie类。其目的就是在于:为了更接近实际项目,我们许多时候会更希望可以得到一份文本文件,而不是在命令窗口里看到这些东西,或者是在GUI中做了一次就没法回头复习的桌面小程序。于是在这里新增了一个Java里的有关文件输出流的类,以将这些算式输出到电脑的D盘目录下的一个名叫“计算题.txt”的文本文件中。在对于文本文件的输出操作,王溯同学在这里选择的是Java中的BufferedWriter,即缓冲区写入。其原因是,利用BufferedWriter可以更好地控制文本的格式以及换行。
关于代码的风格,这也是王溯同学在实际编程的过程中十分注重的。他给的统一格式是:大括号的左括号留在前一行的末尾,右括号单独成一行。在两个大括号之间的代码每行起始的位置要比其左大括号所在行缩进2个字节。同时,为了杜绝在小括号漏掉右括号的语法错误,他还要求小括号要成对打。这不仅仅是为了整个代码的美观,更是为了在后期修改的时候节省大量的时间以及减少不必要的麻烦。在这里,我给大家找了一篇关于编码规范的重要性的文章,大家可以参考一下:
其中,在编程的过程中,我们还遇到了一个小问题。在做需求的时候,我们都是根据题目上对于软件的要求而做出的需求。但是,在实际编写的过程中,我们发现了一个被我们疏忽的严重问题——隐藏的需求。
在需求分析这门课上老师说过,一个软件的需求被分为好几种。其中有一种需求比较特殊,它在做需求分析的过程中最容易被人们所忽略。它就是“隐藏的需求”。隐藏的需求往往是不会被用户所提出来,但是在客观现实生活中它又实际存在且不可被忽略。在我们这个软件中,这个隐藏的需求就是:除数不能等于0!!!!!由于这个问题的存在,使得我们在编译的时候总是出现报错,就在Calculate类的calculate函数的“ case:'/' ”里。于是我们就只能将这一段全部进行修改,使用了一个if+while的循环,并对除数进行刷新,直到刷到一个不为0的数之后,再进行除法操作。
以下是我们附上的项目源代码
六.程序代码
Figure.java
1 import java.util.*;2 public class Figure {3 int figure;4 public void createFigure(){5 figure = (int)(Math.random()*100);6 }7 }
Symbol.java
1 import java.util.*; 2 public class Symbol { 3 char symbol[]={'+','-','*','/'}; 4 int fnum; 5 public void createSymbol(){ 6 do { 7 fnum = (int)(Math.random()*10); 8 }while(fnum>3); 9 }10 }
Form.java
1 public class Form { 2 String form; 3 int num1; 4 int num2; 5 char symbol; 6 Figure n1; 7 Figure n2; 8 Form(Figure num1,Figure num2,Symbol symbol){ 9 num1.createFigure();;10 num2.createFigure();;11 symbol.createSymbol();12 n1 = num1;13 n2 = num2;14 this.symbol = symbol.symbol[symbol.fnum];15 this.num1 = num1.figure;16 this.num2 = num2.figure;17 form=""+this.num1+this.symbol+this.num2+"=";18 }19 }
Calculate,java
1 public class Calculate { 2 Form form; 3 double answer; 4 Calculate(Form form){ 5 this.form = form; 6 switch (form.symbol) { 7 case '+': 8 answer = form.num1 + form.num2; 9 break;10 case '-':11 answer = form.num1 - form.num2;12 break;13 case '*':14 answer = form.num1 * form.num2;15 break;16 case '/':17 if (form.num2 == 0){18 while(form.n2.figure == 0) {19 form.n2.createFigure();20 }21 form.num2 = form.n2.figure;22 }23 answer = (double) form.num1 / (double) form.num2;24 break;25 }26 }27 }
Print.java
1 import java.io.IOException; 2 import java.text.DecimalFormat; 3 public class Print { 4 Calculate calculation; 5 WriteInFile file; 6 int countx; 7 int countp; 8 public void printAll(){ 9 try{10 if(calculation.answer > 100||calculation.answer < 0) ;11 else{12 if(countx == 3){13 countx = 0;14 System.out.println();15 file.bufferedWriter.newLine();16 }17 if(calculation.answer%1>0){18 DecimalFormat df = new DecimalFormat( "0.00");19 System.out.print(calculation.form.form+df.format(calculation.answer));20 file.bufferedWriter.write(calculation.form.form+df.format(calculation.answer)+" ");21 }22 else {23 System.out.print(calculation.form.form + calculation.answer);24 file.bufferedWriter.write(calculation.form.form+calculation.answer+" ");25 }26 System.out.printf(" ");27 countp++;countx++;28 }29 }30 catch (IOException e){31 System.out.println("Error:"+e.toString());32 }33 }34 Print(){35 file = new WriteInFile();36 countp = 0;37 countx = 0;38 }39 public void getCalculation(Calculate calculation){40 this.calculation = calculation;41 }42 }
WriteInFile.java
1 import java.io.*; 2 public class WriteInFile { 3 File fWrite; 4 Writer out; 5 BufferedWriter bufferedWriter; 6 WriteInFile(){ 7 try{ 8 fWrite = new File("D:\\","计算题.txt"); 9 out = new FileWriter(fWrite);10 bufferedWriter = new BufferedWriter(out);11 System.out.println("计算题.txt文档已保存至D:\\目录下");12 }13 catch (IOException e){14 System.out.println("Error:"+e.toString());15 }16 }17 }
七.程序结果
以上就是我们编写的程序的运行结果。与此同时,为了方便老师和大家查看我们的结对编程成果,我们特地将我们做的Java项目打包成了一个EXE可执行文件给大家参考。下载链接请点。
八.体会和总结:
谈下结对编程的优缺点吧,首先说优点。第一,可以发散思维,两个人一个小时能讨论出来的东西,或许一个人一个星期都没法想出来,当代码遇到瓶颈的时候,我们之间也互相鼓励,共同尝试新策略。第二、可以减少代码复审率,当一个人写出一段代码的时候,另一个人作为旁观者往往更容易发现其中的问题。第三、在技术互补的情况下,可以很大程度的提高效率,对于编程能力在我之上的王溯同学,在和他的讨论中,学到了其他我平时不注意的细节。除此以外,两个人互相监督,不容易偷懒,两个人一起工作需要互相配合,如果想偷懒去干别的,就会拖延工作进度。其次是缺点,最大的问题就是数据结构的问题,虽然在面向对象的框架下,都可以写各自的类和方法,但是核心代码对其他函数的调用还是很多的,由于互相不熟悉变量名称和效果,传值时还需要自己来写,这个很麻烦。比如王溯同学设计核心代码时,用到我写的外部函数,就得询问我,或者直接让我来写调用了。另一个问题就是效率问题,有时候由于工作的重复性以及讨论的不恰当性,会导致往往达不到两个人的效果。除此以外,在编程过程中我们两个人有时会谈论与编程无关的事情,往往这就导致了工程进度缓慢。因为结对编程可以很大程度上提高编程效率,而且两人轮流编程,不会太过疲惫,因此十分适合敏捷开发。如果未来我们从事软件开发的工作,我们会十分乐于进行结对编程,因为这会极大的改善我们的编程体验,是编程不再那么枯燥,debug之路也不会那么恐怖。
下面是王溯同学的感想:
我个人感觉,通过这次结对编程,我的确学到了不少东西,比如说什么是架构、需要考虑什么东西、需求工程怎么做等等与此同时,也让我更清楚地意识到,对于项目开发来说,一个好的前期准备是有多么的重要。前期把软件的框架搭好了,后期的代码实现真的就像一瞬间的事情一样。还有就是,这次结对编程也让我深刻地体会到了,两个人写一个程序和一个人写一个程序的不同之处。在整个项目完成的过程中,我们产生过分歧、争论,也曾收获过欢喜、快乐。我想,这应该就是what a team could do。但是,在本次团队编程中,我也有些许遗憾。主要就是我在前期花了大量的时间在框架设计上。但是,设计出来的框架依然有大量的代码冗余。所以,在以后的学习生活中,我会更加注重于程序架构的学习。我会争取在以后的团队编程中,可以更快地搭建出更漂亮、更高效的软件框架出来。
以上就是我和王溯同学对本次结对编程的感想,总的说来,我们从软件工程综合实践这门课程里得到的收获是非常大的,虽然两个人结对编程需要很多的时间,但是通过完成工程,不仅可以学到技术知识,而且更获得了许多宝贵的合作经验,更收获了深厚的友谊。为以后在工作岗位中迅速融入编程集体积累了宝贵的经验。