四则运算生成器
本次个人项目为实现一个随机生成四则运算算式的程序,同时提供检查答案的功能。整个项目的情况如下。
一、总体项目PSP表格
PSP2.1 |
Personal Software Process Stages |
Time |
Planning |
计划 |
8小时 |
· Estimate |
· 估计这个任务需要多少时间 |
8小时 |
Development |
开发 |
15小时20分钟 |
· Analysis |
· 需求分析 (包括学习新技术) |
40分钟 |
· Design Spec |
· 生成设计文档 |
30分钟 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
10分钟 |
· Design |
· 具体设计 |
1小时 |
· Coding |
· 具体编码 |
6小时 |
· Code Review |
· 代码复审 |
0 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
8小时 |
Reporting |
报告 |
|
· Test Report |
· 测试报告 |
0 |
· Size Measurement |
· 计算工作量 |
30分钟 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30分钟 |
合计 |
17小时20分钟 |
代码实现过程中,由于对设计复审、代码复审以及测试报告的具体作用的不理解,未能进行这几个过程。
二、需求分析
这里仿照《一线架构师实践指南》中给出的ADMEMS矩阵(需求层次——需求方面矩阵)方法对个人项目进行需求分析。由于这里没有运维等业务级需求,因此将原方法进行了简化。在此,我将原题目中的需求划分为了用户级和开发级。其中,用户级需求是从用户角度看,满足所需要实现的种种功能。而开发级需求则是在开发的过程中,我们所需要考虑的开发层面的问题。同时,我们将每一级的需求划分为三类,功能性需求、质量要求以及约束条件。
将原题目中给出的所有需求按照这些分类整理在下面的表格中。我们可以根据用户的“支持10000道题目生成”进一步推出一个原需求中未提及的开发质量需求:整个程序的算法复杂度在O(n)到O(nlogn)左右,否则无法在相对合理的时间内生成一万道题目。
功能 | 质量 | 约束 | |
用户级需求 |
命令行参数输入出题数、数据范围 自动生成四则运算,支持整数和真分数 支持括号 题目存入Exercises.txt中 同时生成题目答案,存入Answers.txt中 可以判断答案对错 支持一万道题目的生成 |
用户输入有误时程序不崩溃 错误输入时需输出帮助信息 |
生成的算式需满足: 减法计算无负数 除法计算结果为真分数 运算符不超过3个 +和*交换不重复 真分数五分之三表示为3/5 真分数二又八分之三表示为2’3/8 Exercises.txt文件格式如下: 1. 四则运算题目1 2. 四则运算题目2 …… Answers.txt文件格式如下: 1. 答案1 2. 答案2 判卷结果输出到文件Grade.txt,格式如下: Correct: 5 (1, 3, 5, 7, 9) Wrong: 5 (2, 4, 6, 8, 10) |
开发级需求 |
写出至少10个测试用例确保你的程序能够正确处理各种情况。 |
算法复杂度在O(n)到O(nlogn)左右 经过Code Quality Analysis工具的分析并消除所有的警告 |
采用C++或者C#语言实现,可以使用.Net Framework 运行环境为32-bit Windows 7或8 |
三、项目设计
(1) 概要设计
分数类:用于处理分数之间的计算以及输入输出,是整个程序中数的最基本单位。
1 class Fraction 2 { 3 public: 4 Fraction(); 5 Fraction(const Integer& a, const Integer& b = 1); 6 ~Fraction(void); 7 8 Fraction operator + (const Fraction&) const; 9 Fraction operator - (const Fraction&) const; 10 Fraction operator * (const Fraction&) const; 11 Fraction operator / (const Fraction&) const; 12 13 bool operator > (const Fraction&) const; 14 bool operator < (const Fraction&) const; 15 bool operator == (const Fraction&) const; 16 17 bool valid();18 Integer getNumerator() const;19 Integer getDenominator() const;20 21 friend std::wostream& operator << (std::wostream& out, const Fraction& frac); 22 friend std::wistream& operator >> (std::wistream& in, Fraction& frac); 23 }; 24
算式类:在分数类的基础上,提供和算式相关的功能,包括输入输出计算答案,比较两个算式是否相等。
1 class Equation 2 { 3 public: 4 Equation(); 5 Equation(const Equation& eq); 6 Equation(Fraction *n); 7 Equation(Equation* l, Equation* r, wchar_t flag); 8 ~Equation(void); 9 10 const Fraction& getAns() const;11 bool valid();12 13 bool operator == (const Equation& eq) const; 14 friend std::wostream& operator << (std::wostream& out, const Equation& eq); 15 friend std::wistream& operator >> (std::wistream& in, Equation& eq); 16 };
用户界面模块:解析命令行参数输入,负责与用户相关的交互等。
随机出题模块:负责不重复地生成一定数量的题目。
(2)具体设计
在上述的设计中,Equation类的内部需要单独进行一些设计。因为,其涉及到如何判断两个等式是否相等、如何计算答案的问题。
经过思考,我最终采用了树状结构来表示整个表达式。我按照计算顺序将其构建为一棵树,其中每个叶节点为算式中的数,而内部节点为符号。
四、测试用例
(1)整体测试
case 1:
直接运行Myapp.exe,测试无参数状态下能否正常输出提示。
case 2:
Myapp.exe -n 10,测试无-r是否输出提示。
case 3:
Myapp.exe -n -1 -r 10
Myapp.exe -n 10 -r -1
Myapp.exe -n 2t -r 10
Myapp.exe -n 10 -r ttt
测试-n和-r参数有误时能否正确输出提示。
case 4:
Myapp.exe -n 10 -r 10
测试正确输入下,能否按照需求正确生成不重复的式子。
case 5:
Myapp.exe -n 10000 -r 10
测试正确的大数据量输入下,能否按照需求正确生成不重复的式子。
case 6:
Myapp.exe -e Exercises.txt -a Answers.txt
测试正确输入下能否正确进行判卷工作。
case 7:
修改之前的Answer.txt,将部分答案改成错误的。
Myapp.exe -e Exercises.txt -a Answers.txt
测试正确输入下能否正确进行判卷工作。
case 8:
修改之前的Answers.txt,将部分答案改成错误的格式。
Myapp.exe -e Exercises.txt -a Answers.txt
测试错误输入下能否正确输出相应的提示。
case 9:
生成10000个的同时进行要求程序判断Exercises.txt和Answers.txt中的答案是否正确。
Myapp.exe -n 10000 -r 10 -e Exercises.txt -a Answers.txt
多次运行,确保每次的Grade.txt中的结果均为正确10000道。
(2)单元测试
在开发的过程中,我首先进行了单元测试,以保证关键模块的正确性。
对于Fraction类,我进行了以下几组测试:
- 检验构造函数及化简是否正确,比对(i*j)/j和i是否相等(i,j在1-100之间枚举),若相等则说明Fraction的构造函数及相关的自动化简等功能正确。
- 枚举所有i/j,其中i,j在100的范围内,将该分数写入到文件,再从文件读出,若二者相等,则说明Fraction类对于输入输出流的重载正确。
- 测试文件中的数据若是格式错误的,能否正确设置流的badbit。
对于Equation类,我进行了以下几组测试:
- 测试Equation类相等的实现是否正确。采用了老师在个人项目的描述中所使用的几个例子。
- 同Fraction类一样,测试了输入输出流相关重载是否正确。(共选用了4组较为具有代表性的用例)
由于这两个类十分核心,因此,这两个类的单元测试通过,基本可以确保程序整体上是正确的。
根据VS给出的代码覆盖率分析,我进行的测试可以覆盖70%以上的代码,余下的部分主要是拷贝构造函数、getter/setter、分数的四则运算等极其不易出错的短代码。
而位于核心部分的输入输出流的解析等都覆盖得相对较为完整。
五、性能分析及改进
整个程序常调用的一个路径是维护分数的最简的代码。同时VS的分析也指出了,去重部分占据了相当大的一段时间。我的去重是采用set完成的。set内部的红黑树也许是一个值得改进的地方。
红黑树的算法复杂度相较于哈希表较高,可以采用C++11标准中的unordered_set代替set,以改进性能。