软件工程结对项目

首先,附上所有代码:https://github.com/mwl0811/Arithmetic(内含GUI部分的可执行程序和代码)
队友博客地址:https://blog.csdn.net/wen_zihan/article/details/86479724

一、预计的PSP表格

PSP2.1Personal Software Progress Stages预估耗时(分钟)实际耗时(分钟)
Planning计划6090
· Estimate· 估计这个任务需要多少时间2030
Development开发6090
· Analysis· 需求分析(包括学习新技术)180150
· Design Spec· 生成设计文档12090
·Design Review· 设计复审(和同事审核设计文档)6090
· Coding Standard· 代码规范(为目前的开发制定合适的规范)12090
· Design· 具体设计180180
· Coding· 具体编码15001820
· Code Review· 代码复审300240
· Test· 测试(自我测试,修改代码,提交更改)300300
Reporting报告6060
· Test Report· 测试报告6060
· Size Measurement· 计算工作量3020
· Postmortem & Process Improvement· 事后总结,并提出过程改进计划180120
合计30303430

二、解题思路

  1. 需求分析:
    这一部分主要是分析要求,我总结出了如下的要求:
  • 生成不重复的N道四则运算题目(含乘方)至文件(0<N<=1000)
  • 计算出题目正确结果(结果不展示),并对输入结果(包含真分数)判断对错,最后给出正确和错误题数
  • 读取文件内的数独问题,求解并将结果输出到文件(0<N<=1000000)
  • GUI部分:
    –设置倒计时功能,每道题20s内完成,没有完成计0分到下一题
    –点击“历史记录”可以查看做题的历史记录,每道题是否正确
  1. 初期思路:
    我们的程序在设计时,主要分成了两个部分,第一部分是生成题目部分,这一过程主要需要注意的是生成题目的格式(后文会仔细介绍),这一部分主要由我负责,后一过程是由队友负责的,对于生成的题目进行计算得出一个结果,并且保存。而我们避免生成重复题目的方法是所有计算式的结果不同,所以在生成题目时,就需要调用生成答案的函数,如果有重复的,再重新随机生成。最后的GUI就是主要调用我们已经生成好的程序,这些程序主要是队友对一些代码和读取方式进行调整来生成的,这一部分的图形界面代码主要由我来完成。
    以下是整体的流程:
调用
queston
不重复
不重复
重复题目
主程序
生成题目
解题
答案ans数组
question.txt
输入
历史记录
  1. 查阅资料以及方法确定:
  • 生成四则运算题目:这一部分比较难的点在于需要生成的题目不重复,这一点比较麻烦,因为不重复的定义很细致,起初我是想逐条分析,避免不重复情况,但是后来看到有人写的博客中,选择的思想是:只要题目的答案不重复,题目一定不重复,所以在这里我们决定采用这种保险的方法。
  • 解四则运算题目:这一部分我们选择的方法是比较实用的后缀表达式算法,存到栈中,然后进行计算,这一部分的难点在于对于乘方的计算,因为乘方是比较特殊的算法,优先级最高,所以乘方上花了一些时间。

三、设计实现及代码说明

我们的代码主要就是分为两个部分,generate.cppsolve.cpp两个部分,为了更加清晰,我们设置了一个大类Operator.h,下面是两部分的流程:

调用
调用
queston
不重复
不重复
重复题目
主程序
Operation.h
generate.cpp
solve.cpp
答案ans数组
question.txt
输入
正误判断
  • Arithmetic.cpp是主函数,主要是用于对命令行输入错误的判断,并给出提示显示如何正确输入:
if (flag == 1)   //输入有误时
	{
		cout << "Please check your input!" << endl;
		cout << "Just input like this: Arithmetic.exe N mode" << endl;
		cout << "N means the number of questions, mode means the power." << endl;
		cout << "mode=1:^   mode=2:**" << endl;
		return 0;
	}

注:命令行格式应该是Arithmetic.exe 生成题数 乘方模式

  • generate.cpp用来随机生成四则运算题目,因为这个需要用到随机函数,随机函数是用来产生数字的,为了能做到随机产生运算符,我就把运算符存在了一个字符串中,随机函数取出第几个位置的字符
    大致流程如下:
Created with Raphaël 2.2.0 开始 i<N? 随机生成10个以内的运算符 随机在运算符中加数字 随机添加括号 结束 yes no

但是这一部分有很多细节需要注意,我总结出的注意点如下:

1.避免产生除数为0的情况
2.避免产生0^0的情况
3.避免结果过难计算或过大,对^后的数字限制在3以内
4.避免同一数字两边都有括号的情况:例如(10)
5.避免在同一部分两边加了多次括号

接下来是部分重要的代码:
----这一部分是产生随机括号的位置:

do
{
	bracket1[j] = (rand() % (symbolnum - 1));
	bracket2[j] = (rand() % (symbolnum - bracket1[j] - 1)) + bracket1[j] + 2;
} while (sym[bracket1[j] - 1] == '^' || sym[bracket1[j] - 1] == '/' || sym[bracket2[j]] == '^');
//乘方的情况下不加括号,防止过大
//除法后不加括号,防止除数为0的情况

----以下是删除重复括号的部分:

if (bracket2[m] == bracket2[n])
{
	if (bracket1[k] != 20 && bracket2[n] != 20)
	{
		bracket1[k] = 20;
		bracket2[n] = 20;   //删除重复括号
	}
}

注:bracket数组用来存储括号加的位置,bracket1[]存储前括号的位置,即第bracket1[]个数字的前面,同理,bracket2[]存储后括号的位置,即第bracket2[]个数字的后面。

  • solve.cpp是用来求解题目过程,主要分为两个部分,先是将算式化成后缀表达式,存到栈中,然后对这个后缀表达式求解:
    其中主要的函数就是计算函数,calculate如下:
//计算后缀表达式
void Operation::calculate(deque<char>& coll3, stack<double>& coll4)
{
	double num = 0;
	while (!coll3.empty())
	{
		int flag = 0;
		char c = coll3.front();
		coll3.pop_front();
		char d = 0;
		if (!coll3.empty())
		{
		d = coll3.front();
		}
		else
		{
			flag = 1;
		}
		//如果是操作数,压入栈中
		if (c >= '0'&&c <= '9')
		{
			num = num * 10 + c - '0';
			if ((d == ' '&&flag == 0) || flag == 1)
			{
				coll4.push(num);
				num = 0;
				if (flag == 0)
					coll3.pop_front();
			}
		}
		else	 //如果是操作符,从栈中弹出元素进行计算
		{
			double op1 = coll4.top();
			coll4.pop();
			double op2 = coll4.top();
			coll4.pop();
			switch (c)
			{
			case '+':
				coll4.push(op2 + op1);
				break;
			case '-':
				coll4.push(op2 - op1);
				break;
			case '*':
				coll4.push(op2*op1);
				break;
			case '/':
				coll4.push(op2 / op1);
				break;
			case '^':
				coll4.push(pow(op2, op1));
				break;
			}
		}
	}
}

四、单元测试及代码说明

单元测试的部分

  • 模式判断的测试:
    方法:读取生成题目字符串,是否出现不对应字符
		TEST_METHOD(TestMethod3)  //检查输入模式1时是否使用的是"^"
		{
			argv[1] = "100";
			argv[2] = "1";
			int result = main(argc, argv);
			assert(result == 3);
		}

		TEST_METHOD(TestMethod4)  //检查输入模式2时是否使用的是“**”
		{
			argv[1] = "100";
			argv[2] = "2";
			int result = main(argc, argv);
			assert(result == 4);
		}
  • 题目是否重复的测试:
    对于题目的测试方法:检查输入重复的四则运算题目,结果是否提示重新生成。
		TEST_METHOD(TestMethod5)//检查输入重复的四则运算题目,结果是否提示重新生成
		{
			Operation M;
			string str = "3+(2+1)=";
			string str2 = "1+2+3=";
			int result1 = M.solve(str);
			int result2 = M.solve(str2);
			assert((result1!=result2)==1);
		}
  • 题目计算结果判断的测试:
    我们主要给出了如下几个用例:
    1.检查输入带括号的四则运算题目: 3+(2+1)=
    2.检查输入带^四则运算题目: 3^(2+1)=
    3.检查输入带**型的乘方的四则运算题目
    4.检查输入复杂的四则运算题目: 3^(2+1)/3*2=
    5.检查结果不是整数的运算: 3^(2+1)/4=
    6.检查输入结果为真分数的运算
    7.检查输入结果为负数的运算

下面是单元测试的结果,证明我们检查的部分都正确了:

  • 覆盖率展示:
    覆盖率
    可以看出,测试代码的覆盖还是比较好的。

五、程序性能及质量分析

由于我们选择的方法是判断答案是否重复,所以并不浪费时间,下面是我们的性能分析:

这里可以看到,比较费时间的是Generate和solve部分,这也是整个代码的核心部分。
运行结果没有警告产生:
警告

六、GUI

对于GUI的设计,我认为是这个项目中较为重要的部分,因为之前的程序是一个整体,不是很适用于GUI直接调用,所以很多函数需要单独生成一个程序便于GUI部分的调用,这里我们一共用到了三个程序:

  • Generator_wm.exe:用于生成给定数目的不重复题目
  • Solve.exe:用于计算输入的结果是否准确,并且记录到历史记录中
  • Count_num.exe:用于最后给出整体的正误个数

接下来是关于代码的解释和成果展示:

  • 第一个窗口:设置题目数量和乘方模式

    重要代码:
private void button1_Click(object sender, EventArgs e)
        {
            string para = quenum + " " + mode;
            System.Diagnostics.Process.Start("Generator_wm.exe", para).WaitForExit();
            Form2 f2 = new Form2();
            this.Hide();
            f2.Show();   //调用做题窗口
        }

注:quenum和mode都是从textbox中提取出来的结果。

  • 第二个窗口:做题并反馈

    重要代码:
 void FillGrid()
        {
            ts = new TimeSpan(0, 0, 20);   //设置时间最多20s
            this.timer1.Enabled = true;
            StreamReader str1 = new StreamReader(@"question.txt");
            string quebefore;
            for (int i = 0; i < count; i++) 
            {
                quebefore = str1.ReadLine();
            }
            string que = str1.ReadLine();
            if(que==null)
            {
                System.Diagnostics.Process.Start("Count_num.exe");
                MessageBox.Show("no question left! Please quit!", "警告", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            label5.Text = que;
            if (que != null)
            {
                count++;
            }
            label6.Text = count.ToString();
            textBox3.Text = "Input your answer here";
            label7.Text = ts.Seconds.ToString();   //显示还剩多长时间
        }

注:以上是填空界面的函数。

 private void button1_Click(object sender, EventArgs e)
        {
            string que = label5.Text;
            string ans = textBox3.Text;
            string para = que + " " + ans;
            System.Diagnostics.Process.Start("Solve.exe", para).WaitForExit();
            FillGrid();
        }

注:以上是计算结果是否正确的部分,点击下一题同时计算前一题是否正确。

 private void timer1_Tick(object sender, EventArgs e)
        {
            label7.Text = ts.Seconds.ToString();
            ts = ts.Subtract(new TimeSpan(0, 0, 1));   //隔一秒
            if(ts.TotalSeconds<0.0)
            {
                timer1.Enabled = false;
                string que = label5.Text;
                string ans = textBox3.Text;
                string para = que + " " + ans;
                System.Diagnostics.Process.Start("Solve.exe", para).WaitForExit();
                MessageBox.Show("You have used out of the time!", "超时警告", MessageBoxButtons.OK, MessageBoxIcon.Error);
                FillGrid();
            }
        }

注:以上是计时器部分的代码,用到了timer控件,这一部分是经过查阅资料学会的。

  • 第三个窗口:显示历史记录

    重要代码:
 private void Form3_Load(object sender, EventArgs e)
        {
            StreamReader sr = new StreamReader("Judgements.txt", Encoding.Default);//将选中的文件在textBox2中显示
            richTextBox1.Text = sr.ReadToEnd();
            sr.Close();
        }

注:以上是读取历史记录文件。

七、总结

这一次的项目,我学到了很多,有很大的收获,也有过一些不足之处,也是我们日后再开发软件可以用到的经验,下面是我通过这次经历学到的:

  • 这次最不同的一点是合作项目,过程中我明白了一个项目的成功离不开配合,好在我和队友的配合十分默契。
  • 我深刻体会到了软件开发过程中团队合作的重要性,多人的力量一个胜过一个人,也会使得结果更加周到全面。
  • 这次的项目让我知道了软件前期设计的重要性,我们分配好了模块,目标十分清晰,这大大提高了我们的效率。
  • 这次明确了代码规范的重要性,因为我和队友的模块有数据交互的过程,而代码的规范使得我们能十分清晰地明确对方需要什么。
  • 在代码交接的过程中,我意识到了一定要保证已完成模块的正确性,再做交接,因为在交接合并过后,程序一旦出现问题,很难发现究竟是那一部分出现的错误。
  • 但是我们在最后总结时,共同认为我们有一点没有考虑到的是,我们对于乘方的模式,在生成题目选择2模式时,我将^换成了**,然而队友在计算中又为了方便换了回去,这样就相当于重复做了无用功。我们总结造成这样的结果主要原因还是我们没有将模块再细化划分,造成了内部功能的重复,产生了代码坏味道
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值