软件工程启程篇章:结对编程和进阶四则运算(197 & 199)
0x01 :序言:无关的事
I wrote a sign called "Dead End" in front of myself,
but love crossed it with a smile and said ,
"I can enter anywhere"
在我们不知所畏却还敢胡作非为的最肆意的年纪里,
不为未得到而抑郁难捱,
不为已失去而怅然若失,
踮起足弓,蜷起脚尖,用最大的力气为己所拥有而喝彩,
趁时间正好,一切还在;
——因为《夏洛特烦恼》
0x02 :软件工程和PSP表格记录
To the world,you maybe a person.
But to a person,you maybe the world.
PSP 2.1 |
Persinal Software Process Stages |
Time(Estimated) |
Time(Real) |
Planning |
计划 |
|
|
Estimate |
估计这个任务需要多少时间 |
24h |
36h |
Development |
开发 |
|
|
Analysis |
需求分析(包括学习新技术) |
8~10h |
10h+6h 基本UI框架:1.5h 树的最小表示:2h 随机数效率评估:1.5h XML参数形式:1h |
Design Spec |
生成设计文档 |
2~3h |
2h |
Design Review |
设计复审 |
1~2h |
3h |
Coding Standard |
代码规范 |
1~2h |
10min(*) |
Design |
具体设计 |
1~2h |
3h |
Coding |
具体编码 |
15h~16h 基本框架:7h 单元测试:5h UI框架:3h |
18h 基本框架:7h 单元测试:5h UI框架:3h 调试编码:3h |
Code Review |
代码复审 |
2~4h |
4h |
Test |
测试 |
5~7h |
6h |
Reporting |
报告 |
|
|
Test Report |
测试报告 |
2~3h |
1h |
Size Measurement |
计算工作量 |
15min |
10min |
Postmorten & ProcessImprovement Plan |
事后总结, 并提出过程改进计划 |
2~3h |
1h |
|
合计 |
39h15min~ 52h15min |
66h20min |
表格补充说明1:(*)代码规范时间较短的原因将在后期进行说明
表格补充说明2:而此次预估时间和实际时间相差较大,主要因为大量时间用以设计阶段的优化,保证各类对自身的功能划分清晰;而后期因为测试和编码是同时进行的,因此合计时间是两人共同的消耗时间的计算,实际消耗的总时间可能少于预期;
:XML参数形式:1h,考虑到程序本身的结构,且XML解析的部分由于自身Core逻辑的缘故,Core尽可能向前端提供了充裕的信息,而不必使用中间层过渡“前后端”,类似于停留在“非直接耦合”到“数据耦合”的阶段,因此模块本身的独立性较强
0x03 :结对编程
To the world,you maybe a person.
But to a person,you maybe the world.
图1:结对编程中编码者同步进行的白盒测试
图2:结对编程中测试者同步进行的黑盒测试
图3:结对编程中针对测试中标注“红色且程序崩溃的错误”测试者立刻提出,并交予编码者共同完成代码调试和复审
0x0304:结对编程队友的优缺点
其实当获知结对编程的伙伴后,内心是略显的崩溃的,因为未进行结对编程时对队友尚不是很了解,而且因为自己着实不擅长交流和沟通,所以在结对编程起始前首先交流了一下相互的算法层次上的实现,和算法层次的正确性验证;而在算法的实现上和结对编程的队友颇有“南辕北辙”的风范,虽说最终实现的正确性均能被正确的验证,但除最基本的计算功能外(几乎都选用了逆波兰表达式的方式实现运算,毕竟其时间复杂度为O(kn),而其他效率更高的算法也只是对常数k进行优化,且实现的复杂度极高),生成算法和查重方法都不相同,前者通过子集构造的方式保证高效查重,后者通过完全随机和限制输入条件完成了高效生成,因此,交流后,首先从代码复审阶段相互了解,而代码复审的阶段,着实发现,和队友颇为适合相互的“主导者与协同者”模式;
我的队友曾哲昊同学对结构分析透彻,对软件中各部分功能划分较为清晰,能够避免很多相似功能、重复功能的函数出现;这里重新复审自己代码,发现自己在模块设计上,没有将各部分功能按照面向对象的基本原则封装起来,导致部分“上帝类”、“越俎代庖类”的出现
我的队友思维严谨,能够充分构造充裕的测试样例,并考虑更为复杂的异常状况的抛出,并且工作态度严谨,能将测试样例分类输入,并共同积极探讨这一错误和类似错误的异同,在基础类的构架上,避免了很多未考虑的判断,使得依赖底层类的“高级类”在实现和测试上压力骤减
我的队友具备独特的审美观,设计风格非常符合“Winform”类型的且干净清晰UI构图,且工作效率较高,较为认真;但这里阐释自己的一点失误,自己在设计初始是没有考虑接口问题的,而在架构上起初尝试通过底层类的拼接直接完成主要功能,在逐层封装的时候,发现代码冗杂,且和UI框架有着较大差异,于是在已开展5小时简单粗暴的尝试编码后,重构;
我的队友编码风格稍微处于劣势,并非是功能的划分,主要集中在冗杂代码块的生成上,在编码时会将部分简单问题复杂化,比如此前讨论算法实现时对细节的把握稍有欠缺
0x0308:结对编程的合作分工
因此,考虑到结对编程整体的搭配风格,最终我和队友选取了相互的“主导者与协同者”模式,“主导者负责最大化的创造尽可能多的点子,协同者则需要分析这些点子,发起重要问题,考虑边际条件,将方案与更宏观的用户或者业务上下文绑定分析”(摘自https://www.mockplus.cn/blog/post/188),根据PSP 2.1表格所罗列的必要阶段,不妨将整体的软件划分为五个不同阶段阐释这种结对编程模式:需求分析、设计文档规范、具体架构和细节梳理、测试和代码复审、移植WinformUI界面;
当然,具体的编程阶段细节将不在此地址讨论,移至软件工程的算法实现部分
需求分析 : 在需求分析阶段,以我为主导者,队友作为协同者;首先我初步梳理并提取需求中的关键点,并将它分为基础功能和扩展功能两部分,在这一部分我们不讨论实现上的复杂度和所需要考虑的异常状况,仅考虑基本的功能,和功能的需要考虑的限制参数(比如生成表达式的运算符个数)等情况
设计文档规范:在此阶段,为了使得两人都能很好理解本身的功能,方便编码和测试,由队友作为主导者,我作为协同者;主要规范注释头书写、代码必要的逻辑框架(比如避免两个功能杂交在一起,使得功能上存在重复)等方面
具体架构和细节梳理:在此阶段,以我为主导者,队友作为协同者;首先我根据此前定义的需求分析,按底层类到高层类的封装逻辑进行本身的“架构”,在各类的属性和方法设置上,此部分分歧较多,因为需要严格保证各类之间不会存在“交叉数据”,以防最后封装的过程出现冗杂类,这里在遇到分歧时,一般会记录下后,各自分析,暂停15min后继续讨论,最终完成整体的架构
测试和代码复审:这里不存在所说的主导和协同模式,我作为编码者完成基本的单元测试(包含白盒测试),同时队友尽可能根据此前“契约式”编程提供的前置条件,进行测试样例的构造,并完成大量的黑盒测试;在这一阶段,起初是直接发送exe文件,后来Core核逐步完成后,移步Nunit框架;这里需要特别说明的一点,是测试者发现错误后如何反馈给编码者?首先,我们将测试划分为四种颜色:
图4:结对编程中黑盒测试四色测试方法
这份表格是Core.Calc的测试表格V1.0,如图
ü 栏,根据实现的逻辑,若计算过程中出现浮点数,则结果一定为浮点数,而测试者认为应当对有限小数全部转换为分数计算,这里最后统一为实现的逻辑;
ü 红色表示此测试不通过,而且是非常紧急的错误,必须要编码者立刻修改本身的逻辑防止问题更为严重;此时,测试者将立刻找到编码者并将测试和错误输出交予编码者进行修改
ü 栏,测试者认为这里应当提供“分子不能为浮点数”这种更为详细的错误信息交付给用户,此测试属于后期的优化,帮助编码者预留一些架构,方便后期的修改
ü 黑色表示此测试完全通过,符合测试者和编码者的统一认知
移植WinformUI界面:在此阶段,由队友作为主导者,我作为协同者,在统一确定要实现的功能后,由队友绘制出WinformUI的纸质草图,而我提供部分修改意见,完成UI界面的设计和关键事件的链接,这里由于自己“无知”,以为UI界面并不需要耗费很大的精力,但实际编码后发现,关键事件的链接上必须要保证完整的操作逻辑!比如对于“试题挑战”这一功能,我们提供了“下一题”“开始挑战”“终止挑战”等按钮,但如果用户重复点击终止挑战按钮呢?或者用户在没有点击开始挑战后直接点击了终止挑战?因此,这里每个操作之前的联系都需要通过一些参数的设置来实现各个按钮之间的“交互”,当时自己本身想设置多线程,让本身运行的更为漂亮,但后来考虑到工作量之后,决定折衷,直接在界面的属性中添加了部分变量辅助交互,但这也使得我在UI界面的设计时候发现,在界面设计时,也要充分和编码者或构架者沟通,考虑着其中可能的逻辑,方便编码者对功能和按钮种类提供良好的建议!
0x030c:结对编程的优点和缺点
其实谈及结对编程的优缺点,的确是难以回答清晰的一个问题,在零零散散约一周的工作时间中,并没有采取传统的结对编程中“Driver-Navigator”模式,可能是因为个人习惯的差异,并没有采取如同此前两个人共享一个“屏幕”的方式(比如一个人查看堆栈调用信息,一个人翻看打印的重要信息),而是采取主导者-协同者模式,用开发-测试相互耦合的方式完成了此次结对编程的软件,这里,也将一些简单的看法罗列于下,而具体的参考文献,请浏览此链接:http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html
坦诚而言,结对编程的优点还是较为显著的,最大的优点就是随时反馈的高效;因为结对编程时,即便两个人不“共享一块屏幕”,这必然存在中间的沟通、交流、反馈过程,而这一过程避免了Deadline的重度拖延,比如写一段代码,开心的通过单元测试后,愉快地开始刷剧(瞪眼O.O)。结对编程可以使得两个人随时同步进度,相互督促,注意力高度集中也就意味着效率的大幅提高,因此,结对编程随时反馈的高效性能够很好的实现。
同时,由于实时反馈机制,使得结对编程最终生产的代码质量较高。因为在此次开发-测试分工中,遇到测试的非黑色样例后事实通过编码者,将底层类的逻辑错误封杀在摇篮中,不至于此错误放大,而封装到高层类中出现了不可预期的错误!比如,在底层类的异常捕捉时,V1.0的黑盒测试就调出了近5个BUG(当时有2个是被自己的单元测试所捕捉),诸如“直接输入非最简分数,结果不是最简”“gcd模板忘记添加除0的返回值”等等,这导致后面封装的时候效率极高。这一点自己真的是颇有感触,因为之前调试了小伙伴的程序,遇到了和上次C1903一样几乎无从下手的错误,错误极难在准确的位置被还原,每次出现的时间都不尽相同(现在回想,当初应该附加一个随机数打印的log方便错误重现),而错误发生后VS发生了类似“死锁”的状态,只能终止程序。后来,他用了尽一个小时的时间成功调试而出,据说是测试阶段忘记对类中的某一分支进行测试,而出现问题的分支恰存处于该分支内。
最后,“在企业管理层次上,结对能更有效地交流,相互学习和传递经验,能更好地处理人员流动。因为一个人的知识已经被其他人共享”,可能因为此次结对编程并未同其他组一样交流异常密切,对这一点的感悟上虽说并不是收获颇丰,但总是有收获的。经验和学习,个人认为最重要的条件就是有一个不同性格的人去提问,提问 — 说服 — 反思 — 统一意见,这一过程可以使得两个人都对软件本身有着更深入的理解,同时也交流了很多算法、思路上的意见,所以个人其实蛮偏向于结对编程的。
然而,结对编程如果论缺点,仍是存在的。但不同于某些方面,即便它存在缺点或者反对结对编程的人,都承认了它很多优点的存在。个人感觉,可能结对编程的缺点在于个人的差异、注意力高度集中导致更为疲倦;不得不承认,两个人性格差异较大,很可能将导致软件产生质量更高,从不同层次思考问题,更有利于发现问题。但编码风格(由于此前经过代码复审,两个人的编码风格都有所了解,可以做出必要的划分)、性格、平台差异、逻辑思维、甚至生活习惯都可能导致“矛盾”,也就可能导致低效的“罢工”。
因此,结对编程总体而言,最重要的还是两个人之间的沟通和交流。如果能实现良好的分工,结对编程无疑是不错的选择。
0x04 :重要的设计方法
To the world,you maybe a person.
But to a person,you maybe the world.
“我们在应用程序开发中,一般要求尽量两做到可维护性和可复用性。应用程序的复用可以提高应用程序的开发效率和质量,节约开发成本,恰当的复用还可以改善系统的可维护性。而在面向对象的设计里面,可维护性复用都是以面向对象设计原则为基础的,这些设计原则首先都是复用的原则,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性。面向对象设计原则和设计模式也是对系统进行合理重构的指导方针。”
此段内容摘自吴际老师的面向对象课程的讲义中,提及到信息隐藏原则(OCP的理解)、接口设计、契约式编程等概念,这里就不妨罗列出部分阅读的参考资料和个人理解,进一步阐释和说明
0x0404:Information Hiding(信息隐藏)
In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change)
—— 摘自Wikipedia(https://en.wikipedia.org/wiki/Information_hiding)
在概要设计时列出将来可能发生变化的因素,并在模块划分时将这些因素放到个别模块的内部。这样,在将来由于这些因素变化而需修改软件时,只需修改这些个别的模块,其它模块不受影响。信息隐蔽技术不仅提高了软件的可维护性,而且也避免了错误的蔓延,改善了软件的可靠性。现在信息隐蔽原则已成为软件工程学中的一条重要原则
这里的论述相对有些晦涩,其实可以简单理解为变量的“封装”,而这里的封装,从简单入手,就是我们需要确保某一类中的数据成员为private属性,不会因为其他类随意调用了你的类的实例中的某一属性,而导致后面在调用这一对象是出现“莫名其妙的错误”。而从复杂方面理解,其实就是“抽象化”的趋势,换句话说,我们不允许最重要的抽象层模块被修改,我们希望在扩展某一软件的新功能时,尽可能提供新的行为函数,而不是对你的代码进行反复的修改,这样能导致软件开发过程中的软件始终保持较好的稳定性,不至于瞬时间“面目全非”。
其实,对于信息隐藏,感触最深的还是可读性问题。从底层类逐步封装到高层类,如果存在超大类、超长类、超长方法的存在,一旦更改某一底层的函数,我们将连带修改若干变量,而这一修改如果,不全面,将会带来非常严重的后果!甚至会导致你情绪崩溃!这里必须展示一段当时自己写过的一段注释:
/*Dear maintainer:
Once you are done trying to optimize the routine with HUGE class or method ,
and have realized what a mess debuging corresponding parameter,
increment the following counter as a warning to another program:
total_hours_debugging_wasted :
*/
在此次的设计中,从底层类到高层类的封装时,我尽可能保证高层类通过调用底层类的特定的public函数,而将其他方法尽可能隐藏为private,使得高层类不会重复调用底层类的组合引发错误。当然这里特别说明一点,因为此前在浏览SOLID原则的时候发现DIP原则,个人理解是,高层类需要通过调用底层类的函数实现,但最后实现上要考虑的是高层的抽象。比如,开关中,的确包含灯的开关,但不能完全依赖灯开关的实现;我们要做的,是关注开关的标准接口,这样,一旦我们需要扩展开关为电器的开关,我们只需要保证灯、电器都继承了开关的接口。这和高层类调用底层类,个人理解,是两个不同的原则方面
0x0408:接口设计(Interface Design)
Interface Segregation Principle,接口隔离原则,是SOLID原则中重要的组成部分。“比如,我们对电脑有不同的使用方式,比如:写作,通讯等,如果我们把这些功能都声明在电脑的抽类里面,那么,我们的上网本,PC机,服务器,笔记本的实现类都要实现所有的这些接口,这就显得太复杂了。所以,我们可以把其这些功能接口隔离开来,比如:工作学习接口,编程开发接口,上网娱乐接口,计算和数据服务接口,这样,我们的不同功能的电脑就可以有所选择地继承这些接口。”(参考链接:http://www.educity.cn/se/1383082.html),这很有搭积木的感觉,因此,个人理解优秀的接口,应当对整体的功能框架有着良好的划分。这里也展示一下,此次设计的接口。
interface CoreInterface
{
void setting(int MinRange, int MaxRange,
int minOp, int maxOp, long number,
bool isFactor, bool isDecimal, bool isMin,
bool isBracket, bool isMul);
bool setJudge();
string CreateSingleExpression();
string[] CreateExpression();
string[] CorrectionJudge(string exercise, string answer);
string Calc(string formula);
}
是的,我其实非常想吐槽自己的接口。这次即便在设计阶段耗费了大量的时间,但最终接口的实现上仍不尽人意,甚至最后的程序为了兼容这一接口,不得不添加一些属性来保证效率。这里还是存在改进方法的,一方面是设计一个中间类,兼容我的UI和Core,另一方面,重新设计接口并实现。个人感觉,此次底层的实现思路都比较清晰,重点就在于接口中我想给我的其他程序什么信息?我必须实现哪些功能?。因为现在自己仍在尽力耦合自己的Core核心和其他团队的UI界面,瞬间感觉当时自己应当联系其他团队开发出统一的一套接口标准,方便后期的耦合;但事已致此,我们只能在两个接口之间搭建一个中间类,实现逻辑层面的转化,而这也是此次附加题中打算耦合的思路。
0x040c:松耦合(loose coupling)
In computing and systems design a loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Sub-areas include the coupling of classes, interfaces, data, and services —— 摘自Wikipedia(https://en.wikipedia.org/wiki/Loose_coupling)
一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。换句话说,对于OOD而言,此法则的初衷就在于降低类之间的耦合,尽量减少类对其他类的依赖(写到这里,突然对DIP原则有着一定的认识,换句话说,对于高层类,我们需要抽象化接口并始终维护保持稳定性,底层类,则尽可能少地依赖其他类的实现,保证不会因为某一类的改变影响全局)
个人感觉,此次松耦合概念实现难以预判,因为,实现过程中,自己的高层类很多都需要底层类的方法,但是由于接口层的存在,所以使得程序稳定性相对没有较大的变化。不过,松耦合最重要的就是talk only to your immediate friends。
关于这一概念的理解,在进一步的团队编程中,将进一步熟悉这一原则。
0x05 :契约式设计(Design By Contract)
To the world,you maybe a person.
But to a person,you maybe the world.
契约式设计(Design By Contract)把类和它的客户程序之间的关系看作正式的协议,描述双方的权利和义务。Bertrand Meyer把它称作构建面向对象软件系统方法的核心。
契约式设计的提出,主要基于软件可靠性方面的考虑。可靠性包括正确性和健壮性,正确性指软件按照需求规格执行的能力,健壮性指软件对需求规格中未声明状况的处理能力。健壮性主要与异常处理机制相关。(摘自:http://www.cnblogs.com/riccc/archive/2007/11/28/design-by-contract-dbc.html)
在此次的结对编程项目中,契约式设计或契约式编程是非常重要的组成部分!因为个人在编码时,能够通过前置条件的设定完成基础的单元测试,而在后期封装时,我们可以通过对不满足前置条件的设定抛出异常的方式解决此问题,从而使得编码有着类似“工程化”的方法;而对于测试者,测试能够通过对前置条件的“划分”,实现全面的前置条件的测试,也为测试提供了很多便利。
因此,从个人的角度理解,契约式编程是一种基于代码正确性证明的编程方式,能够方便编码者和测试者之间的交流,也极大增强的代码的可读性;个人在底层类的Factor和Poiland中也在单元测试中设置了类似的前置条件,方便本身的单元测试。
0x06 :单元测试(Unit Test)
To the world,you maybe a person.
But to a person,you maybe the world.
单元测试、白盒测试、黑盒测试,自己一开始混淆了这三部分概念,这里针对某一个问题,展开自己对三方面测试的理解。
Q : 单元测试属于白盒测试?
自己的误区主要集中于这一句话,因此,根据个人理解将以此展开进行论证。首先,单元测试是允许黑盒测试存在的,因为黑盒和白盒测试主要分别验证总体功能是否实现要求、内部操作是否符合规格要求,因此,我们不能单纯将白盒测试和单元测试设置一个单纯的自己关系,两者从测试目的上就有着较为明显的差异:单元测试主要集中于测试独立模块的正确性,白盒测试主要测试程序的整体逻辑,即内部操作的规范性。因此,个人理解,两者之间存在联系,即在编码阶段实现单元测试的开发人员实施白盒测试的成本较低。
函数名 |
属性与对象说明 |
|
|
结果 |
public static Factor operator +(Factor lhs, Factor rhs) |
首先根据isDecimal的bool属性值进行划分,将参数划分为浮点数类型和分数类型(整数类型归类为特殊的分数),因为不妨构造测试样例,测试浮点数、非整数分数、分数、假分数间的加法,同时交换顺序保证笔误;补充上溢出检查 |
lhs |
rhs |
|
Factor(true, 1.7, 1, 2) |
Factor(true, 2.1, 2, 1) |
Factor(true, 3.8, 0, 0) |
||
Factor(false, 1.7, 3, 1) |
Factor(true, 1.7, 1, 2) |
Factor(true, (decimal)1/3+1.7, 0 ,0) |
||
Factor(true, 2.1, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(true, 2.1+(2/1), 0, 0) |
||
Factor(false, 1.7, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(false, 0, 3, 7) |
||
public static Factor operator -(Factor lhs, Factor rhs) |
同理 |
lhs |
rhs |
|
Factor(true, 1.7, 1, 2) |
Factor(true, 2.1, 2, 1) |
Factor(true, 1.7-2.1, 0, 0) |
||
Factor(false, 1.7, 3, 1) |
Factor(true, 1.7, 1, 2) |
Factor(true, (decimal)1/3-1.7, 0 ,0) |
||
Factor(true, 2.1, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(true, 2.1-(2/1), 0, 0) |
||
Factor(false, 1.7, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(false, 0, 3, -5) |
||
public static Factor operator *(Factor lhs, Factor rhs) |
同理 |
lhs |
rhs |
|
Factor(true, 1.7, 1, 2) |
Factor(true, 2.1, 2, 1) |
Factor(true, 1.7*2.1, 0, 0) |
||
Factor(false, 1.7, 3, 1) |
Factor(true, 1.7, 1, 2) |
Factor(true, (decimal)1/3*1.7, 0 ,0) |
||
Factor(true, 2.1, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(true, 2.1*(2/1), 0, 0) |
||
Factor(false, 1.7, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(false, 0, 3, 2) |
||
public static Factor operator /(Factor lhs, Factor rhs) |
同理,但是在此函数中,我们抛出了两个异常DivideByZeroException,OverflowException;前者由题意即可判断,而后者是由于long类型的加法溢出导致,因此,在关键函数嵌套了checked关键字保证了溢出检查; |
lhs |
rhs |
|
必须在前置条件中保证除数非0,否则将造成infinte结果的返回 |
Factor(true, 1.7, 1, 2) |
Factor(true, 2.1, 2, 1) |
Factor(true, 1.7/2.1, 0, 0) |
|
Factor(false, 1.7, 3, 1) |
Factor(true, 1.7, 1, 2) |
Factor(true, (decimal)1/3/1.7, 0 ,0) |
||
Factor(true, 2.1, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(true, 2.1/(2/1), 0, 0) |
||
Factor(false, 1.7, 3, 1) |
Factor(false, 1.7, 1, 2) |
Factor(false, 0, 6, 1) |
||
Factor(true, 2.1, 3, 1) |
Factor(true, 0, 1, 2) |
[ExpectedException(typeof(DivideByZeroException))] |
||
Factor(true, 2.1, 3, 1) |
Factor(false, 1.7, 0, 1) |
[ExpectedException(typeof(DivideByZeroException))] |
||
Factor(false, (decimal)2.1, 1000000000000000000, 1) |
Factor(false, (decimal)2.1, 9000000000000000000, 1) |
[ExpectedException(typeof(OverflowException))] |
||
public int CompareTo(Factor other) |
实现Icomparable<Factor>接口的方法,重点考察浮点数和分数的等值;这里由于选用decimal方法导致,可以通过"="判断; |
this |
other |
|
Factor(false, 1.7, 3, 1) |
Factor(true, 1.7, 1, 2) |
-1 |
||
Factor(true, 1.7, 1, 2) |
Factor(false, 1.7, 3, 1) |
1 |
||
Factor(true, (decimal)2.0, 3, 1) |
Factor(false, (decimal)1.7, 1, 2) |
0 |
||
public override void ToString() |
this |
|||
ToString()函数重载,测试样例主要确保假、真分数、整数、非最简分数的正确表示,特别说明,对于负数强制添加括号,因此,主要围绕此方面设计测试样例 |
Factor(true, (decimal)1.2, 0, 1) |
"1.2" |
||
Factor(true, (decimal)-1.2, 0, 1) |
"(-1.2)" |
|||
Factor(false, (decimal)1.2, 1, 3) |
"3" |
|||
Factor(false, (decimal)1.2, 7, -3) |
"(-3/7)" |
|||
Factor(false, (decimal)1.2, 3, -7) |
(-2'1/3) |
|||
Factor(false, (decimal)1.2, 8, 2) |
"1/4" |
|||
public static long gcd(long denominator, long numerator) |
static |
|||
gcd()辗转相除法,测试样例仅在单元测试中给予,未列于表格中 |
||||
private void Console_Integer() |
private函数本身测试需要改写为public,而此三函数的主要作用是解析不同类型的字符串,转换为定义的Factor类的对象,但由于public void StringToFactor()本身是调用此三函数实现,因此仅对后者进行检测; |
|||
private void Console_Factor() |
||||
private void Console_Decimal() |
||||
public void StringToFactor() |
this |
|||
这里掺杂大量黑盒测试和部分白盒测试,因为测试量相对较大,因此这里仅列出部分的测试,更为充足的黑盒测试请翻阅其他表格; |
17 |
Factor(false, 0, 1, 17) |
||
0 |
Factor(false, 0, 1, 0) |
|||
003 |
null |
|||
-7 |
Factor(false, 0, 1, -7) |
|||
0/17 |
Factor(false, 0, 17, 0) |
|||
17/0 |
null |
|||
-3/7 |
Factor(false, 0, 7, -3) |
|||
3/-7 |
null |
|||
0'3/7 |
null |
|||
-2'3/7 |
Factor(false, 0, 7, -17) |
|||
01'5/7 |
null |
|||
3'-5/7 |
null |
|||
003.31415 |
null |
|||
3.141500000 |
Factor(true, (decimal)3.141500000, 0, 0) |
|||
.7 |
null |
|||
public Poiland(string str) |
this |
|||
此函数为读取表达式,简单验证其运算符合要求,再将其转换为后缀表达式;这里更准确地描述,测试样例主要通过黑盒测试的方式构造,因为此正则表达式的构造相对复杂,而且这其中也有一段非常有趣的经历,这里也仅列举必要的测试样例,具体详见其他表格 |
Poiland("2 + 1 - (3+ 1)") |
—— |
||
Poiland("(-2) + 3") |
—— |
|||
Poiland("(-2) + (-3)") |
—— |
|||
Poiland("21 - 2/5 × (-9 + 9.4 ÷ 4.7)") |
—— |
|||
Poiland("13 ÷ 12 + (8 - 7/12)") |
—— |
|||
Poiland("1 + (- 9)") |
—— |
|||
Poiland("× 13 - 9 + (2 - 3)") |
ExpectedException(typeof(NotImplementedException)) |
|||
Poiland("17 - 8 + (-9 + (9 - 1)") |
ExpectedException(typeof(NotImplementedException)) |
|||
Poiland("8 × -1") |
ExpectedException(typeof(NotImplementedException)) |
|||
Poiland("7 ×+ 7") |
ExpectedException(typeof(NotImplementedException)) |
|||
Poiland(" (+2) + 3") |
ExpectedException(typeof(NotImplementedException)) |
|||
Poiland("-2 + 3") |
ExpectedException(typeof(InvalidOperationException)) |
|||
Poiland("- 3 + 1") |
ExpectedException(typeof(InvalidOperationException)) |
|||
private bool comparePrior(char op_1, char op_2) |
||||
此函数为经典的运算符优先级比较函数,由于private类型且逻辑可由算法正确性直接证明,因此,不予测试 |
||||
private Factor calFactor(Factor number_1, Factor number_2, string op) |
||||
此函数调用此前测试的operation [+][-][*][/]方法,其测试样例的正确性可由此前的测试样例证明,同时代码本身的逻辑实现正确,可直接证明,因此,不预测试 |
||||
private void ConvertToPoiland() |
||||
此函数为中缀表达式转换为后缀表达式的算法实现,算法可予证明,而本身的正确性,不妨通过后缀表达式运算的正确性间接验证,因此,在测试过程中,若测试通过则默认此函数实现正确,否则再次单独设计调试样例进行测试 |
||||
public string getResult() |
this |
|||
此函数用以计算后缀表达式,并返回计算结果Factor类的ToString()形式,在计算过程中,由于中缀表达式检测是删去空格进行检测,因此部分能通过中缀表达式检测的运算式,可能无法计算,如:"9 +2"等形式,因此,与此前类似,仍添加大量黑盒测试保证其正确性 |
Poiland("2 + 1 - (3 + 1)").getResult() |
"(-1)" |
||
Poiland("(-2) + 3").getResult() |
"1" |
|||
Poiland("(- 2) + 3").getResult() |
"1" |
|||
Poiland("13 ÷ 12 + (8 - 7/12)").getResult() |
"13 ÷ 12 + (8 - 7/12)" |
|||
Poiland("1 + (- 9)").getResult() |
ExpectedException(typeof(InvalidOperationException)) |
|||
Poiland("1 +2").getResult() |
ExpectedException(typeof(NotSupportedException)) |
|||
Poiland("1 2.2").getResult() |
ExpectedException(typeof(NotSupportedException)) |
|||
Poiland(".2").getResult() |
ExpectedException(typeof(FormatException)) |
|||
Poiland("7/0").getResult() |
ExpectedException(typeof(FormatException)) |
|||
Poiland("1 ÷2").getResult() |
ExpectedException(typeof(FormatException)) |
|||
public void setting(int MinRange, ……, Random rand) |
||||
此函数用于设置生成表达式的关键参数,属于getter and setter函数类型,因此,不予测试 |
||||
private void CreateFactorList() |
||||
此函数用于求解所生成的数字范畴,因此,此部分测试采用白盒测试,但未体现于代码中,主要通过列举不同的range和number后,打印createNumber的值判断是否符合要求,具体的范围列举在右侧 |
range |
number << 3 |
isFactor,isDecimal(false,—) |
|
range |
number << 3 |
isFactor,isDecimal(false,—) |
||
range << 6 |
number |
isFactor,isDecimal(true,true) |
||
range << 6 |
number |
isFactor,isDecimal(true,true) |
||
range * (maxDenominator - 1) |
number << 3 |
isFactor,isDecimal(true,false) |
||
range * (maxDenominator - 1) |
number << 3 |
isFactor,isDecimal(true,false) |
||
private void Judgement_ISD_ISF(long createNumber, bool isRandom, int minDenominator, int maxDenominator, int MinRange, int MaxRange) |
||||
此函数用于生成符合要求的数据,由于此函数作用于最终的结果密切相关,因此,这里采用黑盒测试,而具体数据请翻阅其他表格 |
||||
public string CreateExpression() |
||||
此函数用于生成符合要求的表达式,由于此函数作用于最终的结果密切相关,因此,这里采用黑盒测试,而具体数据请翻阅其他表格 |
||||
public List<string> getResult() |
||||
此函数用于生成符合要求的表达式组,直接调用前面封装的函数实现,因此此函数正确性与前者密切相关,因此,这里采用黑盒测试,仅列出一组用以捕获异常的测试点,而具体数据请翻阅其他表格 |
||||
public bool setJudge() |
||||
此函数用以检查参数的正确性,由于if-else结构相对比较清晰,较易获取其全部分支,这里不列举出测试样例 |
表1:单元测试样例表格,包含部分代码复审的内容
如图5,ExceptionSolutions为异常处理类,主要将异常转换为用户能够理解的提示信息的字符串;Program为Main()方法入口;FormNumerical为UI界面文件,在单元测试不必测试,而对于其他类,其代码覆盖率均值高于90%,极差为16.44%,整体代码覆盖率符合预期。而具体的单元测试构造代码如上表所示。
图5:结对编程中单元测试代码覆盖率说明
0x07 :UML图的设计思路整理与程序正确性证明
To the world,you maybe a person.
But to a person,you maybe the world.
图6 : UML类图的整体框架
图7-10 :图6中隐藏部分的UML类图的具体细节
这里根据UML图,来简单概述一下此次四则运算的需求分析和算法层次的实现。
0x0704:需求分析和项目工作
对于C#和四则运算生成和计算的进阶部分,首先我们确定基本的功能模块(http://www.cnblogs.com/jiel/p/4830912.html)
对于任意给定的表达式,能够识别并解析为标准的四则运算表达式,若匹配失败则需要反馈于用户具体的错误信息,若匹配成功,则需要尽可能快速地计算出正确结果
对于给定的一系列参数,我们能够读取用户所需求的参数,并生成给定参数的题目和答案,将它写入当前进程所在的文件夹;特别地,若参数不符合要求,我们需要反馈于用户具体的错误信息
对于给定的题目文件和答案文件,我们能够根据一定规则,对题目文件和答案文件进行多对多的检查,并返回最终的检查结果,输入到文件中;特别地,文件不存在或错误,我们需要抛出异常
特别说明,需要同时支持浮点数,整数,分数
对于扩展的功能,我们可以从以下几个方面入手进行分析
ü 增加试题挑战功能,能够生成一定范围的题目,让用户输入,并在挑战结束后核算成绩,用以检测用户的四则运算水平(模拟试题功能)
ü 增加竖式表示功能,能够将输入的算式转化为竖式表示,方便使用此软件的人员,能够根据最经典的竖式计算,逐步熟悉计算过程
ü 增加一元或二元运算符,如sin, cos, log等基本运算符
ü 增加进制转换和进制选择功能
因此,由基本的功能模块,我们可以将此次结对编程项目的编码过程分解为阶段性的模块工作
在底层上,需要实现同时支持{数域:真分数,自然数,浮点数}{运算符:四则运算符,左右括号}的数据结构或类,由于我们选择.NET的面向对象过程的建模,我们需要实现基础类class Factor(),并定义基本的四则运算(通过运算符重载实现即可),同时实现Icomparable<Factor>接口,提高排序效率
在功能层次上,主要实现表达式的构造、生成和计算,并能通过中缀表达式向后缀表达式的运算实现计算结果的正确性
同时,我们能尽可能在参数设置的范围内更多实现表达式的构造,同时又必须保证不具备重复性,根据参数设定的环境不能被破坏
在前端层次上,通过经典的Winform界面反馈给用户,令用户能够友好地操作程序,实现所需要的功能
0x0708:Factor基础类的架构
public class Factor : IComparable<Factor>
{
public bool isDecimal; //isDecimal = true : double decimalNumber is valid
public decimal decimalNumber; //Save decimal number while (isDecimal = true)
public long denominator; //Save fraction with numerator/denominator
public long numerator;
}
由UML图的基本架构中,在Factor类的基础类架构中,我们通过isDecimal的bool类型的值确定存储为浮点数或分数(含整数),重载基本的四则运算符和负数运算符,同时重载Tostring()方法,实现CompareTo()方法,保证输出的标准型和排序的快速性;
因此,基于此数据结构的架构,我们同样隐性地做出了如下的定义
假分数实现基本的加减乘除运算,同时为尽可能扩大假分数的运算范围,我们在每调用计算的时候都会调用最小公约数gcd()函数(由于为O(log)级的算法,所以可以相对忽略此时间消耗),从而保证计算效率
若恰为整数,则强制要求分母为1即可
在打印的过程中,将通过重载ToString()函数保证输出的正确性
因此,对于程序的正确性,首先我们需要从契约性设计的角度进行理论上的验证,对于修改了对象属性的方法,我们需要从理论上证明这种情况的改变是符合预期的,而类实现的过程也始终满足不变式的限制和整体Overview所展示的功能,同时,我们也通过覆盖性的单元测试,从浮点数,真分数,整数,带真分数四方面入手进行计算,运算结果符合预期,因此,通过单元测试,不妨默认程序在此测试样例的情况下正确运行,因此,在无特例的情况下不妨认为Factor类满足程序正确性。
0x070c: CreateList类的架构和其他
这里对于程序的正确性的证明在此前的博客中,已经提供了完整的论述,详见http://www.cnblogs.com/panacea/p/4831018.html,同时,此次的生成算法和上次的思路基本相同,但为了提高效率,削减此前由于数值无意义打表浪费的时间,这里做出了如下的优化策略:
CreatedList类主要实现随机四则运算表达式的生成,我们首先确定这样的基础思想,在任何range参数的取值情况下,生成数量与重复率成正相关,但在range参数足够大时,若通过随机化方法生成操作数和操作符随机组合,重复概率极小,理论计算基本处于1%%的数量级,甚至在随机化的处理方法上,表达式重复概率基本可以忽略不计,而若通过查重的方式逐步插入将占用大量的时间消耗,其消耗的CPU的资源率相当高,甚至在此前测试时占据96%的采样资源;
因此,这里我们从两方面入手分析这一问题,如何构造不重复的表达式,如何快速查询表达式的重复性
如何构造不重复的表达式
这里,我们采取子集生成的方式,对于随机的表达式集合,我们只生成其中的一部分子集,这里对子集做出如下的定义和证明:
表达式的操作符数和数字数具备“数字数 - 操作符数(不含括号) = 1”的数学关系,因此可将操作符随机选取依次穿插在数字间,生成中缀表达式
表达式的数字必须呈递减关系,且与非递增关系不同,我们必须确保生成的表达式的相邻三个数字不相同,从而保证表达式可以直接通过String.Equals()方法进行判断
对于减法关系的生成,在此前的策略中,我们通过限制操作符组合的方式完成了基本表达式的生成,但是,这里我们不妨采取通过减法前后表达式的对调完成正确表达式的生成
这里证明,对于交换性质的运算符+和×,对于A≥B,则A[+|×]B = B[+|×]A,当且仅当A=B,在此基本定理的情况下,可以证明直接通过String.Equals()方法实现表达式的查重
同时,我们重点讲述对此过程的优化空间
private void CreateFactorList()
{
long range = MaxRange - MinRange;
int maxDenominator = MaxRange > -MinRange ? MaxRange : -MinRange;
int minDenominator = MinRange <= 0 ? 1 : MinRange;
//isFactor : The Existence of Factor , false ::= only integer allowed
//isDecimal : The Existence of Decimal, false ::= only factor(including integer) allowed
long createNumber = (this.isFactor == false) ? ( range > (number << 3) ? (number << 2) : range ) //Number Of Integer
: this.isDecimal == true ? ( (range << 6) < number ? (range << 6) : (number << 2)) //Factor and Decimal
: range * (maxDenominator - 1) > (number << 3) ? (number << 2) : range * (maxDenominator - 1); //Only Factor
bool isRandom = (createNumber == (number << 2));
this.list = new Factor[createNumber];
Judgement_ISD_ISF(createNumber, isRandom, minDenominator, maxDenominator, MinRange, MaxRange);
}
图11:此前关于this.list生成的异常处理
在此前,我们通过了直接打表的方式生成足够数目的数字,而表达式直接通过调度list列表中的数字生成,避免重复数字的重复随机生成,提高生成数字的效率;但因此,当所生成的运算符数目过大时,会导致程序没有足够的内存存储相应的数组,而无法正确执行;
因此,在数组长度的选择中,此次我们通过打表和随机结合的方式保证数组中的数字足够生成充足数目的表达式。因此,这里我们折衷一种生成方法,当能够生成的数值个数远超过所需要的数值个数时,我们选择后者的一种运算结果作为数组长度,否则,我们将仅生成能够生成的数值个数。
0x0710:参数的控制渠道
参数控制需求 |
控制途径 |
具体模块 |
生成题目的数目 |
表达式生成 |
CreateList.getResult() |
生成题目的范围 |
数值生成 |
CreateList.CreateFactor() |
生成题目的运算符个数 |
表达式生成 |
CreateList.CreateExpression() |
生成题目是否存在分数 |
数值生成,除法控制 |
CreateList.CreateExpression(),CreateList.CreateFactor() |
生成题目是否存在浮点数 |
数值生成 |
CreateList.CreateFactor() |
生成题目是否存在乘除法 |
符号生成 |
CreateList.CreateExpression() |
生成题目是否存在负数 |
数值生成,减法控制 |
CreateList.CreateFactor(),CreateList.CreateFactor() |
生成题目是否存在括号 |
表达式生成 |
CreateList.CreateExpression() |
表 2 :参数控制渠道表
0x08 :一些有趣的事和虫
To the world,you maybe a person.
But to a person,you maybe the world.
0x0804:正则表达式的的分组构造
关于正则表达式,此前不了解分组构造的策略时,对正则表达式的使用一般都较为繁琐
/* @ Unsigned
* @ Support : Pure-Unsigned-Integer-Number , Such As 1, 17
* @ Incorrect Input Screened: 001, -3, +7
*/
private static Regex regex_Unsigned_Integer = new Regex("^(([0-9]{1})|([1-9][0-9]+))$");
private static Regex regex_Signed_Integer = new Regex("^([+|-]?)(([0-9]{1})|([1-9][0-9]+))$");
/* @ Unsigned
* @ Support : Pure-Unsigned-Factor-Number , Such As 17'3/7, 7/17, 17/7, 0/7
* @ Incorrect Input Screened: +/-1/7, 1'0/7
*/
private static Regex regex_Unsigned_Factor = new Regex("^(([0-9]+[/][1-9]{1}[0-9]*)|([1-9]{1}[0-9]*['][1-9]+[/][1-9]{1}[0-9]*))$");
private static Regex regex_Signed_Factor = new Regex("^([+|-]?)(([0-9]+[/][1-9]{1}[0-9]*)|([1-9]{1}[0-9]*['][1-9]+[/][1-9]{1}[0-9]*))$");
/* @ Unsigned
* @ Support : 0.7, 1.7
* @ Incorrect Input Screened: +/-1.7, 00.7, 001.17
*/
private static Regex regex_Unsigned_Decimal = new Regex("^((([0-9]{1})|([1-9]{1}[0-9]+))[.][0-9]+)$");
private static Regex regex_Signed_Decimal = new Regex("^([+|-]?)((([0-9]{1})|([1-9]{1}[0-9]+))[.][0-9]+)$");
在此过程中,我们通过大量的正则表达式的验证来保证所截取的字符串符合需求,这其中浪费了大量的时间成本,而且代码的可读性非常糟糕,同时,对于类似情况的正则表达式,我们很难以描述清楚如下的状况,当数字为有符号数时我们需要保证左右括号必须同时存在,而此时繁琐的正则表达式也不符合需求,这时翻看MSDN,终于有了意外的收获:
https://msdn.microsoft.com/zh-cn/library/vstudio/bs2twtah.aspx
如上的连接中详细讲述了分组构造的方法,简而言之,我们通过模拟简单的堆栈情况来保证正则表达式的匹配,而类似的条件判断的表达式也可以以此书写:
(?(groupName)thenExpression|elseExpression)
据此,我们可以轻松此改写正则表达式,改写后如下所示:
^(?:
(?<sign>\([-+])?
(?:[0-9]+')?
[0-9]+(?:.[0-9]+)?
(?:/[0-9]+(?:.[0-9]+)?)?
(?(sign)\))
)$
这样匹配,可以极大增加正则表达式的可读性
特别鸣谢:https://stackoverflow.com/users/3764814/lucas-trzesniewski,关于正则表达式的见解清晰而准确
0x0808:OpenFileDialog与ThreadStateException
private void button_FileAns_Click(object sender, EventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Filter = "(*.txt)|*.txt";
**** if (fileDialog.ShowDialog() == DialogResult.OK){}
}
如代码所示,我们在点击button_FileAns按钮后触发如下事件,而在这一过程中,我们尝试实例化OpenFileDialog的对象,保证“上传功能”的实现,但运行至****标记的语句时,程序触发ThreadStateException异常,而在找到的各式“OpenFileDialog”攻略,基本都做出了如下定义,而未见报错。在逐步了解Winform的机制后,发觉这属于典型的线程安全问题,因为任何显示UI的线程都将其声明为STA(单线程模式),同时,执行调度回路;因此,在此实例化后将“可能”导致主界面线程和新实例化线程的竞争,因此,这里我们需要改写此方法,保证这一过程线程安全;
具体解决方案可翻阅如下链接探索:
http://www.codeproject.com/Articles/841702/Thread-Apartment-Safe-Open-Save-File-Dialogs-for-C
0x09 :测试样例与代码分析
To the world,you maybe a person.
But to a person,you maybe the world.
这里不妨给出,当时黑盒测试的测试样例表格,以此共勉当初面向对象的测试(泪奔T T)
输入表达式 |
输入类型 |
期望答案 |
实际结果 |
符合预期 |
是否存在说明不清楚 |
以下是非法输入 |
|||||
N/A |
无输入 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
38 + 831 - |
末尾运算符 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
× 13 - 9 + (2 - 3) |
开头运算符 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
- 15/4 |
负号与数字分离 |
中缀表达式检测未通过 |
-15/4 |
否 |
|
1 + (- 9) |
负号与数字分离 |
中缀表达式检测未通过 |
程序崩溃 |
否 |
|
.2 |
开头小数点 |
解析数字时存在非法现象 |
解析数字时存在非法现象 |
是 |
|
-14. |
末尾小数点 |
解析数字时存在非法现象 |
解析数字时存在非法现象 |
是 |
|
172.3.7 |
多余小数点 |
解析数字时存在非法现象 |
解析数字时存在非法现象 |
是 |
|
-3 × 9 + 9) |
右括号不匹配 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
17 - 8 + (-9 + (9 - 1) |
左括号不匹配 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
-8 - 1 % 2 |
非法运算符 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
10000000000000000000000000 |
长数字 |
数字过长 |
程序崩溃 |
否 |
|
1 -2 |
运算符右侧未加空格 |
运算符未加空格 |
-2 |
否 |
|
÷2.2 |
运算符两侧未加空格 |
运算符未加空格 |
解析数字时存在非法现象 |
否 |
|
12 - 9+ 8 |
运算符左侧未加空格 |
运算符未加空格 |
解析数字时存在非法现象 |
否 |
|
12 /2.2 |
分数线左侧多余空格 |
分数中有多余空格 |
解析数字时存在非法现象 |
否 |
应说明多余空格 |
2 9.2 |
数字间缺少运算符 |
缺少运算符 |
9.2 |
否 |
|
8. 211 |
小数点后多余空格 |
解析数字时存在非法现象 |
解析数字时存在非法现象 |
是 |
|
8.(2) |
小数点后紧跟括号 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
1/0 |
否 |
||||
2.3/8 |
分数分子不为整数 |
解析数字时存在非法现象 |
解析数字时存在非法现象 |
是 |
应说明分子应当为整数 |
4/-9 |
分数分母为负数 |
解析数字时存在非法现象 |
解析数字时存在非法现象 |
是 |
应说明分母应当为正整数 |
8/0 |
解析数字时存在非法现象 |
否 |
|||
1000000000000000000 + 9000000000000000000 |
溢出 |
溢出 |
-8446744073709551616 |
否 |
|
7 ×+ 7 |
连续运算符 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
7 × + 7 |
运算符中间缺少数字 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
8 × -1 |
负数放中间时未加括号 |
中缀表达式检测未通过 |
中缀表达式检测未通过 |
是 |
|
以下是合法输入 |
|||||
218 |
正整数 |
218 |
218 |
是 |
|
-12.7 |
负分数(小数形式) |
-12.7 |
-12.7 |
是 |
|
21.00 |
末尾的0可以去掉的小数,输出时应去掉 |
21 |
21.00 |
否 |
|
0/9 |
分子为0的分数,等于0 |
0 |
0 |
是 |
|
2/8 |
非最简分数 |
1/4 |
2/8 |
否 |
|
17 + 2.6 |
整数与小数加法 |
19.6 |
19.6 |
是 |
|
-9 - 8/12 |
整数与分数减法 |
-29/3 |
-29/3 |
是 |
|
0.4 + 2/7 |
小数与分数加法 |
24/35 |
0.6857142857142… |
否 |
计算规则规定 |
19 × (-8) |
整数乘法 |
-152 |
-152 |
是 |
|
7 ÷ 2.1 |
整数与小数除法 |
10/3 |
3.3333333333333… |
否 |
计算规则规定 |
7 ÷ 21/10 |
整数与分数除法,与上题等价 |
10/3 |
10/3 |
是 |
|
18 + 11 × 9 |
乘加混合 |
117 |
117 |
是 |
|
11 × 9 + 18 |
与上题等价 |
117 |
38 |
否 |
|
11.9 + (-2.448 × 1.5) |
小数加法与乘法 |
8.228 |
8.2280 |
否 |
|
-2.448 × 1.5 + 11.9 |
与上题等价 |
8.228 |
10.952 |
否 |
|
19 × (5 - 1) |
单重括号 |
76 |
76 |
是 |
|
18 - ( 1 + (2 - 11) ) |
双重括号 |
26 |
26 |
是 |
|
21 - 2/5 × (-9 + 9.4 ÷ 4.7) |
四则混合 |
23.8 |
23.8 |
是 |
|
四则混合 |
21 |
2 |
否 |
||
8 × 3 - 9/3 |
与上题等价 |
21 |
2 |
否 |
|
8 × 3 - 3 |
与上题等价 |
21 |
2 |
否 |
|
与上题等价 |
21 |
21 |
是 |
||
(8 + 3) × (-8 - (-1)) |
四则混合 |
-77 |
-77 |
是 |
|
14 + 8 + 8 × (11 - 9) - ((-3) + 29) |
四则混合 |
12 |
2 |
否 |
|
13 ÷ 12 + (8 - 7/12) |
四则混合 |
17/2 |
389/12 |
否 |
|
13/12 + 8 - 7/12 |
与上题等价 |
17/2 |
-15/2 |
否 |
|
13/12 - 7/12 + 8 |
与上题等价 |
17/2 |
-15/2 |
否 |
|
7/1 × 1/8 |
分数乘法 |
7/8 |
7/8 |
是 |
|
7 ÷ 1 × 1 ÷ 8 |
与上题等价 |
7/8 |
7/9 |
是 |
|
7/11 - 9/19 |
分数减法 |
34/209 |
34/209 |
是 |
|
7 ÷ 11 - 9/19 |
与上题等价 |
34/209 |
-85/19 |
否 |
|
与上题等价 |
34/209 |
-85/19 |
否 |
||
7/11 - 9 ÷ 19 |
与上题等价 |
34/209 |
34/209 |
是 |
|
2000 + 2000 + …(共17个)… + 2000 + 2000 |
较长表达式 |
17000 |
17000 |
是 |
0x0a :结对编程项目总结
To the world,you maybe a person.
But to a person,you maybe the world.
写到这里,似乎也还有太多的话想说,Exception的识别和处理,对极小的range提供的最小生成树的做法(特别鸣谢:在选修课上共同探讨的乾神~,http://www.cnblogs.com/SivilTaram/),独到的见解真的是瞬间有种眼前一亮的感觉;似乎也还有太多的优化能做,分离界面和Core核的接口层,支持多线程的运行等等;
暂且写到这里吧,或许团队编程结束后,会对当时自己所说的所做的有着不同的见解了吧~
团队项目前夕,愿一切安好~
Hola~BugPhobia~
软件工程启程篇章:结对编程和进阶四则运算(197 & 199)的更多相关文章
- 2017-2018-2 165X 『Java程序设计』课程 结对编程练习_四则运算
2017-2018-2 165X 『Java程序设计』课程 结对编程练习_四则运算 经过第一阶段的学习,同学们已经熟悉了这门语言基本的用法.在一次又一次对着电脑编写并提交代码,进行练习的时候,有没有觉 ...
- 集大软件工程15级结对编程week1
集大软件工程15级结对编程week1 0. 团队成员 姓名 学号 博客园首页 码云主页 孙志威 20152112307 Agt Eurekaaa 孙慧君 201521123098 野原泽君 野原泽君 ...
- 20175312 2018-2019-2 《Java程序设计》结对编程练习_四则运算(第二周:整体性总结)
20175312 2018-2019-2 <Java程序设计>结对编程练习_四则运算(第二周:整体性总结) 结对对象与其博客链接 20175309 刘雨恒:https://www.cnbl ...
- 20175312 2018-2019-2 《Java程序设计》结对编程练习_四则运算(第一周:阶段性总结)
20175312 2018-2019-2 <Java程序设计>结对编程练习_四则运算(第一周:阶段性总结) 结对对象与其博客链接 20175309 刘雨恒:https://www.cnbl ...
- 20175208『Java程序设计』课程 结对编程练习_四则运算
20175208 结对编程练习_四则运算(第一周) 结对成员:20175208张家华,20175202葛旭阳 一.需求分析: 实现一个命令行程序,要求: (1)自动生成指定数量的小学四则运算题目(加. ...
- 2017-2018-2 20172310『Java程序设计』课程 结对编程练习_四则运算_第二周
2017-2018-2 20172310『Java程序设计』课程 结对编程练习_四则运算_第二周 博客要求 组内同学需各自发表博客 博客中需包含以下内容: 相关过程截图 关键代码解释 遇到的困难及解决 ...
- 2017-2018-20172309 『Java程序设计』课程 结对编程练习_四则运算_第三周
2017-2018-20172309 『Java程序设计』课程 结对编程练习_四则运算 组队成员: 仇夏 学号: 20172310 博客地址: @王志伟 四则运算第一周博客 @仇夏四则运算第一周博客 ...
- 2017-2018-20172309 『Java程序设计』课程 结对编程练习_四则运算——第一周
2017-2018-20172309 『Java程序设计』课程 结对编程练习_四则运算 组队成员: 仇夏 学号: 20172310 博客地址:点击这里 1. 需求分析: 可生成题目: - 输入要想生成 ...
- 2017-2018-2 20165225『Java程序设计』课程 结对编程练习_四则运算
2017-2018-2 20165225『Java程序设计』课程 结对编程练习_四则运算 需求分析: 支持整数,可进行多运算符运算,有优先级. 设计思路: 在有括号的情况下,先计算得出括号中的结果,如 ...
随机推荐
- 转:tcpdump抓包分析(强烈推荐)
转自:https://mp.weixin.qq.com/s?__biz=MzAxODI5ODMwOA==&mid=2666539134&idx=1&sn=5166f0aac71 ...
- Linux 小知识翻译 - 「文件系统的种类」
现在的Linux,主流的文件系统是 「ext3」.但是,文件系统除此之外,还有「ReiserFS」「XFS」「ZFS」等等. 此外,Windows的主流文件系统是「NTFS」,CD-ROM的主流文件系 ...
- WPFの操作文件浏览框几种方式
方式1: 使用win32控件OpenFileDialog Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog ...
- C#的深拷贝和浅拷贝
也许会有人这样解释C# 中浅拷贝与深拷贝区别: 浅拷贝是对引用类型拷贝地址,对值类型直接进行拷贝. 不能说它完全错误,但至少还不够严谨.比如:string 类型咋说? 其实,我们可以通过实践来寻找答案 ...
- python第五十一课——__slots
2.__slots__: 作用:限制对象随意的动态添加属性 举例: class Demo: __slots__ = ('name','age','height','weight') #实例化Demo对 ...
- log4j2的环境变量使用
官方文档 http://logging.apache.org/log4j/2.x/manual/lookups.html#EnvironmentLookup
- 20175310《Java2实用教程》第4周学习总结
20175310 <Java程序设计>第4周学习总结 教材学习内容总结 本周学习了第五章子类与继承的内容,这章主要讲的是面向对象的两个内容:继承与多态.其中重点是方法重写.对象的上转型对象 ...
- 【Atcoder yahoo-procon2019-qual D】 Ears
Atcoder yahoo-procon2019-qual D 题意:给你\(L\)个耳朵(???),以及一条范围从\(0\)到\(L\)的数轴,你可以选择一个出发点,从该点开始随意走动,如果经过了\ ...
- MySQL(二)数据的检索和过滤
使用频率最高的SQL语句应该就是select语句了,它的用途就是从一个或多个表中检索信息,使用select检索表数据必须给出至少两条信息:想选择什么,以及从什么地方选择 一.检索数据 1.检索单个列 ...
- kubespray -- k8s集群dashboard 访问方式
1.参考这篇文章: https://github.com/kubernetes/dashboard/wiki/Creating-sample-user 创建用户 2.获取token 3.kubectl ...