软件工程启程篇章:C#和四则运算生成与运算
0x01 :序言
I leave uncultivated today,
was precisely yestoday perishes tomorrow
which the person of the body implored
“看不清楚的时光印痕,像是泛黄的底片,明明还记得那个故事,却忘了故事里的风月”,不知如今因为生成规则、词法排序或效率而争执地面红耳赤的少年们,多少岁月走过重新翻阅看着七零八落的注释和代码段,是否只得慨叹岁月这把最锋利的杀猪刀,然而,即便最终能停留在代码段的注释行不过寥寥数语,然而乏善可陈却又万分郑重的写下每一字,想让她听起来特别,我听到她说谢谢我,就像从List<String>.contains()到Hashtable.Add(string.getHashCode(),string)的变化,就像从Number.Created()到CreatedList.Created()的变化,正确性,结构化,效率。
谨以此文致以软件工程的启程,你好,旧时光
0x02 :软件工程和PSP表格记录
I am a slow walker,
but I never walk backwards.
Although with hippopotomonstrosesquippedaliophobia and bugphobia
PSP 2.1 |
Persinal Software Process Stages |
Time(Estimated) |
Time(Real) |
Planning |
计划 |
|
|
Estimate |
估计这个任务需要多少时间 |
8h |
17h |
Development |
开发 |
|
|
Analysis |
需求分析(包括学习新技术) |
1~2h |
2~3h |
Design Spec |
生成设计文档 |
20~40min |
20min |
Design Review |
设计复审 |
5~10min |
1min |
Coding Standard |
代码规范 |
20~40min |
20min+2h(*) |
Design |
具体设计 |
1~2h |
15min+2h(*) |
Coding |
具体编码 |
2~3h |
6~7h |
Code Review |
代码复审 |
30min~1h |
30min+2h(*) |
Test |
测试 |
1~2h |
1h30min |
Reporting |
报告 |
|
|
Test Report |
测试报告 |
30min~1h |
20min |
Size Measurement |
计算工作量 |
1h |
2~3h |
Postmorten & ProcessImprovement Plan |
事后总结, 并提出过程改进计划 |
30min~1h |
1h |
|
合计 |
8h15min~ 14h30min |
16h16min~ 19h16min |
表格补充说明:(*)表示因为前期思考时间不足而重构所增加的时间
对于C#和四则运算生成和计算,首先我们确定基本的功能模块(http://www.cnblogs.com/jiel/p/4810756.html)
对于参数range和number,能够依据参数生成特定的四则运算表达式,对于必须的基本要求,我们必须确保生成子表达式结果非负数,同时运算符个数也不超过3个,而且各表达式不能通过有限次的乘法或加法交换为同一道试题
对于参数range和number,在实现基本功能的前提下需要考虑的是性能和效率的问题,比如:“生成的表达式中是否各符号的出现率相差不超过10%”,“括号的出现是否具备较强的随机性”,“表达式的数值是否均匀分布在[0,range]区间”,“查重或者所定义的规则是否具备较高的时间效率”
对于参数exercise和answer,我们需要能够解析标准的四则运算表达式并将它计算出正确结果,与answer文件进行对照从而记录正确与错误题号
对于参数exercise和answer,在解析并计算标准的四则运算表达式时应尽可能快速地计算出正确结果,而对于逆波兰表达式,其时间复杂度为O(kn),而其他效率更高的算法也只是对常数k进行优化,且实现的复杂度极高,因此不妨直接选取逆波兰表达式作为解析方法
0x03 :项目设计文档基本思路
1 universe, 9 planets,
204 countries,809 islands, 7 seas,
and i had the privilege to meet you.
由基本的功能模块,我们可以将此次个人项目分解为阶段性的模块工作:
在底层上,需要实现同时支持{数域:真分数,自然数}{运算符:四则运算符,左右括号}的数据结构或类
在功能层次上,主要实现表达式的构造、生成和计算,并能通过中缀表达式向后缀表达式的运算实现计算结果的正确性
同时,我们能尽可能在尽可能的大的范围内实现表达式的构造,同时又必须保证不具备重复性,且减法和除法有效性不能被破坏
在前端层次上,主要实现命令行参数的解析,最终实现函数所具备的的生成和计算功能
0x04 :UML图的设计思路整理与程序正确性证明
To the world,you maybe a person.
But to a person,you maybe the world.
0x0401 Number基础类的架构
internal class Number
{
/*Basic Parameter for class Number (Random with the parameter of -r)*/
private long parent;
private long child;
}
由UML图的基本架构中,在Number类的基础类的构造中,不仅需要设置存储形式为“假分数”即(child/parent),同时重载了基础的四则运算(但未同C++时重载运算符),同时重载(Override)ToString()方法,保证在输出的时候仍为标准形态的真分数;
因此,基于此数据结构的架构,我们隐性地做出了如下的定义:
假分数实现基本的加减乘除运算,同时为尽可能扩大假分数的运算范围,我们在每调用计算的时候都会调用最小公约数gcd()函数(由于为O(log)级的算法,所以可以相对忽略此时间消耗),从而保证计算效率
在打印的过程中,将通过重载ToString()函数保证输出的正确性
因此,对于程序的正确性,通过覆盖性的单元测试(共4 * 4 * 4 = 64种),即将Number划分为int类型的自然数,long类型的自然数,真分数,带真分数四种情况对程序进行测试,运算结果符合预期,因此通过单元测试不妨默认程序在此测试样例的情况下程序正确运行,因此,在无特例的情况下不妨认为Number类满足程序正确性
0x0402 ParseNumber基础类的架构
internal class ParseNumber
{
private String parse;
private Number result;
private Regex regex_1 = new Regex("^[1-9]{1}[0-9]*['][0-9]+[/][1-9]{1}[0-9]*$");
private Regex regex_2 = new Regex("^[0-9]+[/][1-9]{1}[0-9]*$");
private Regex regex_3 = new Regex("^[0-9]+$");
}
ParseNumber类通过正则表达式的检测实现字符串类型变量expression到Number类型的转换,而若无法通过正则匹配时由于字符串切分工作通过其他类正确实现,因此,可直接构造抛出new NotImplementedException();,通过屏蔽机制在Main函数中try-catch捕获即可,对于功能模块的此类,只能通过单元测试来尽可能保证程序正确性,因此与Number类的程序正确性证明类似,不妨在无特例的情况下认为ParseNumber类满足程序正确性
0x0403 Poiland/MyApp类的架构
Poiland类通过通过数据结构栈的使用实现后缀表达式的快速解析,在注释中有着较好的说明:
/* Kernel Method
* - Poiland temp = new Poiland(String);
* temp.getResult();
*
* = String presents for the input of expression(Type:String)
* Init Object temp with initation of input and convert to Behind-Expression
* Cal Such Expression with Stack
*/
而MyApp类提供Main函数入口,因此这两类程序正确性证明暂时可认为满足程序正确性,主要将针对CreatedList类展开程序正确性的论证
0x0404 CreatedList类的架构
CreatedList类主要实现随机四则运算表达式的生成,我们首先确定这样的基础思想,在任何range参数的取值情况下,生成数量与重复率成正相关,但在range参数足够大时,若通过随机化方法生成操作数和操作符随机组合,重复概率极小,理论计算基本处于1%%的数量级,甚至在随机化的处理方法上,表达式重复概率基本可以忽略不计,而若通过查重的方式逐步插入将占用大量的时间消耗,其消耗的CPU的资源率相当高,甚至在此前测试时占据96%的采样资源;
因此,这里我们从两方面入手分析这一问题,如何构造不重复的表达式,如何快速查询表达式的重复性
这里,我们采取子集生成的方式,对于随机的表达式集合,我们只生成其中的一部分子集,这里对子集做出如下的定义和证明:
表达式的操作符数和数字数具备“数字数 - 操作符数(不含括号) = 1”的数学关系,因此可将操作符随机选取依次穿插在数字间,生成中缀表达式
表达式的数字必须呈递减关系,且与非递增关系不同,我们必须确保生成的表达式的相邻三个数字不相同,从而保证表达式可以直接通过String.Equals()方法进行判断
对于减法关系的生成,我们必须不妨定义相邻符号组为“[—][+][+|—|×|÷]”或“[+][ —][+]”或“[—][+]”或“[+][ —]”或“[ —]”的组合
这里证明,对于交换性质的运算符+和×,对于A≥B,则A[+|×]B = B[+|×]A,当且仅当A=B,在此基本定理的情况下,可以证明直接通过String.Equals()方法实现表达式的查重
这里放置两张优化图,说明List<String>.Contains()和Hashtable.Contains()的优化程度(getHashCode()方法),这里我们设置参数为“-r 1000 –n 100000”
未优化前对List<String>.Contains()函数的观察可以据此判断
优化后Hashtable.Contains()函数的观察可以据此判断
因此,实际上,Hashtable的优化,基本可以使得查重的效率呈现数量级上的差异,同时由于任何运算过程中,包括Number的识别和转换都需要调用正则表达式进行判断,因此对于100,000道题目的生成,是相对可以容忍而若需要优化,可能会涉及到代码重构的操作
0x05 :测试样例与代码分析
To the world,you maybe a person.
But to a person,you maybe the world.
这里测试样例仅针对“-r -n”的类型操作进行说明(特别说明,若非从命令行中读入,而是通过VS的调试窗口进行读入,应屏蔽MyApp.exe的参数,只敲入-r integer –n interger进行测试,通过cmd则不会讲MyApp.exe读为args[0])
CreateMaths.exe –r 1.3 –n 5.6
Print the IndexOutOfRangeException's Message As Followed :
[Message Start]
索引超出了数组界限。
[Message End]
Less Args is Invalid, Such Args Are Valid:
Myapp.exe -r 10 -n 10
Myapp.exe -e <exercisefile>.txt -a <answerfile>.txt
Press Enter to Exit
CreateMaths.exe –r 10000 –n 100
Print the StackOverflowException's Message As Followed :
[Message Start]
没有足够的内存继续执行程序。
[Message End]
Range parameter(-r) is too large
Press Enter to Exit
CreateMaths.exe –r 10000 –n 100
Print the StackOverflowException's Message As Followed :
[Message Start]
没有足够的内存继续执行程序。
[Message End]
Range parameter(-r) is too large
Press Enter to Exit
CreateMaths.exe –r 5000 –n 100
argument's carried out successfully of -n -r
Press Enter to Exit
CreateMaths.exe –r 3 –n 10000
class CreatedList : NotSupportedException Happening!
Print the NotSupportedException's Message As Followed :
[Message Start]
不支持所指定的方法。
[Message End]
You May Well Input "-r 1 -n 10", Large but not Larger -r or -n is welcome
Invalid args Mainly Because number is out of the limition of range
Press Enter to Exit
CreateMaths.exe –r 100 –n 100000
argument's carried out successfully of -n -r
Press Enter to Exit
测试答案的测试用例由于文件较大,在此不再展示,围绕文件读取合法性、题目格式的正确性、答案格式的正确性、题目文件和答案文件是否匹配、答案正确的题目数量等方面设计测试用例即可
0x05 :测试样例与代码分析
To the world,you maybe a person.
But to a person,you maybe the world.
在代码分析上,我们不妨采取设置参数为生成10000道范围限制在100内的四则运算表达式
显然, Hashtable的优化,基本可以使得查重的效率呈现数量级上的差异,同时由于任何运算过程中,包括Number的识别和转换都需要调用正则表达式进行判断,因此对于10,000道题目的生成,是相对可以容忍而若需要优化,可能会涉及到代码重构的操作,因此其改进空间应只涉及重构层次上的修改,在此次优化过程中,可以判定为改进空间不大
0x06 :个人项目总结
To the world,you maybe a person.
But to a person,you maybe the world.
个人项目,实现和设计的差异性可能只有深入思考才会去探究清晰,工程项目不同于理论学习最大的特征在于测试的完全性和程序本身的鲁棒性,可能时候面向对象建模课程的影响,在处理输入的参数时总会考虑大量内容,从而确保项目能够在任何输入的情况下均能完美给出相对的错误信息和输出信息;
同时,自己在初期设计的时候没有梳理清楚“表达式的生成规则”和“基础的类的设计”,导致类之间的关联与预期偏离,不同功能交叉在不同对象之间,极大增加了思维难度,从而导致后续的重构消耗了大量的时间,可能软件工程最大的目的,在于,用清晰的设计实现优美的代码,以上
Hola~BugPhobia With Pocket Panacea