这是《构建之法》实战教学的一部分。适合做为同窗们的第二个程序做业。html
第一个程序做业: 请看 “概论” 一章的练习,或者老师的题目,例如这个。算法
做业要求:编程
软件工程的做业愈来愈有意思了, 咱们在第一个做业中,用各类语言实现了一个命令行的四则运算小程序。 咱们看看若是要把咱们的小程序升级为能稳定运行,解决用户问题的软件,应该怎么作。 小程序
建议在作下面的题目的时候,采用结对编程的方式, 在练习中,让同窗们学会模块化编程,信息隐藏,接口设计,TDD,等。服务器
你们写了很多四则运算的练习,这些代码都各有特点,你们写的 “软件” 也有必定的用处。 若是咱们要把这个功能放到不一样的环境中去 (例如,命令行,Windows 图形界面程序,网页程序,手机App), 就会碰到困难, 由于目前代码的广泛问题是代码都散落在 main() 函数或者其余子函数中,咱们很难把这些功能完整地剥离出来,做为一个独立的模块知足不一样的需求。 架构
咱们看到,不一样的代码解决不一样层面的问题,有些是内部数据的计算 (例如四则运算);有些是和用户输入相关的 (例如 scanf,cin,图形界面的输入输出),有些是和数据的展示相关的 (例如 printf ,cout,println,DrawText),有些是和程序所在平台的架构相关的(例如 main 函数,程序倒计时的实现等)。 这就须要咱们对软件的架构作一些整理和优化。框架
建议你们把四则运算的计算功能包装在一个模块中 (这个模块能够是一个类 Class, 一个DLL,等等), 为了方便起见,咱们叫它 “计算核心” 模块, 这个模块至少在两个地方可使用:模块化
测试程序,这个能够是一个命令行的程序,或者是JUnit 的框架,或者是Visual Studio 单元测试的框架。这样,咱们在算法层级保证了这个模块的正确性。 函数
实际的软件,这是交付给最终用户的软件,有必定的界面和必要的辅助功能。单元测试
那么这个“计算核心”模块和使用它的其余模块之间是什么关系呢? 它们要经过必定的API (Application Programming Interface) 来和其余模块交流。 这个API 接口应该怎么设计呢? (这是一个给有必定经验和实力的同窗的题目), 为了简单,咱们能够从下面的最简单的接口开始:
Calc()
这个Calc 函数接受字符串的输入(字符串里就是运算式子,例如 “ 5+3.5“, “7/8 – 3/8 ”, “3 + 90 * (-0.3)“ 等等),这个模块的返回值是一个字符串,例如,前面几个例子的结果就是 ( ”17.5“, “ 1/2”, “-24“).
假设咱们用的是类,咱们的测试程序刚开始能够是很是简单的测试例子: (用伪代码表示)
String result = Core.Calc(“1 + 1”) ;
Assert ( result == “2”); //咱们断言 1 + 1 的结果必定是 2.
而后同窗们实现本身 Core 的这个功能。
第一阶段目标 - 能把计算的功能封装起来,经过测试程序和API 接口测试其简单的加法功能。
加法成功以后,而后咱们再作减法, 乘法,除法,咱们假设目前为止都是两个操做数的运算,仍是很容易实现的。 因为同窗们已经在本身之前的程序中实现了各类算法,这时候只要把实现的算法搬过来就行了。 你们能够不断增长测试的数量,在每实现一个新的功能的时候,要保证之前运行正确的例子继续是正确的, 经过这样的 “回归测试“, 来保证本身实现的函数一直是正确的 (请看书中关于单元测试,回归测试的内容)。
第二阶段目标 - 经过测试程序和API 接口测试其简单的加减乘除功能。并能看到代码覆盖率。
而后,更欢乐的状况出现了, 多个运算符的运算,带负数的运算。
啊,等一下,若是咱们考虑这些状况的话, 咱们这个模块有一些参数要设置,例如,最多几个运算符,能带括号么?数据范围是多少,还要设置计算的精度(保留几位小数,必须是用分数形式表示么,等等), 这是由什么API 来决定呢? 咱们能够扩展 Calc() 的定义,让它接受一个新的参数 “precision”, 或者咱们能够启用一个新的函数 Setting()。
若是我想表示:
最多4 个运算符
数值范围是 -1000 到 1000
精度是小数点后两位
怎么经过API 告诉咱们的模块呢? 咱们固然能够用函数的参数直接传递,可是参数的组合不少,怎么定义好参数的规范呢? 建议你们考虑用 XML 来传递这些参数。
增长了新的Setting() 函数以后,咱们要让模块支持这样的参数,同时,还要保证原来的各个测试用例继续正确地工做。
第三阶段目标 - 经过测试程序和API 接口测试对于各类参数的支持。并能看到代码覆盖率。
这个时候,若是输入是有错误的,例如 “1 ++ 2”, 在数值范围是 -1000 .. 1000 的时候,传进去 “10000 + 32768 * 3”, 或者是 “ 248.04 / 0” 怎么办? 怎么告诉函数的调用者 “你错了”? 把返回的字符串定义为 “-1” 来表示? 那么若是真的计算结果是 “-1” 又怎么处理呢?
建议这个时候,咱们要定义各类异常 (Exception), 让 Core 在碰到各类异常状况的时候,能告诉调用者 - 你错了! 固然,这个时候,咱们一样要进行下面的增量修改:
定义要增长什么功能 - 例如:支持 “运算式子格式错误” 异常
写好测试用例,传进去一个错误的式子,指望能捕获这个 异常。 若是没有,那测试就报错。
在 Core 模块中实现这个功能
测试这个功能
同时测试全部之前的功能,保证之前的功能还能继续工做 (没有 regression)
确认功能完成,继续下一个功能
第四阶段目标 - 界面模块,测试模块和核心模块的松耦合。
既然各组各组同窗都写了高质量的各个模块,并且模块之间的关系是明肯定义的,一致的,那么,小组A 的测试模块就能够测试小组B 的核心模块;小组C 的用户界面模块就能够和小组B 的核心模块结合起来,正常运行。对吧?! 那咱们就让两个小组 (A,B) 在一块儿,测试一下下面的状况:
- A 的核心模块, 加上B 的测试模块和用户界面模块
- B 的核心模块,加上A 的测试模块和用户界面模块
两组同窗分析合并以后出现了什么问题,为什么会出现这样的问题?如何改进? 而且改进各类模块中的 bug
(请看北航同窗的做业心得:http://www.javashuo.com/article/p-riylxjvn-kp.html )
第五阶段目标 - 经过增量修改的方式,改进程序, 完成对各类错误状况的处理。
选择两组程序中高质量的模块,增长必要的功能,把全部代码签入源代码管理服务器, 同时,把这个软件发布出来。
(请看北航一个同窗的尝试:http://www.javashuo.com/article/p-rzomntcj-kn.html)
第六阶段目标 - 如何把这些模块重用到别的相关项目中去。
例如,如今你们要作一个 【24点游戏】:
有四个数字,经过加减乘除括号等四则运算,把四个数字组成一个算式,结果是 24。 这个游戏有两档难度,入门级:数字在 1..10 之间; 高级:数字在 1..99 之间
可用命令行或本地GUI 或 网页界面的方式实现这个游戏
咱们要从头开始写全部的程序么?这个题目须要的一些功能和咱们花了很长时间作的 “四则运算” 模块有很多相似之处, 那么,如何把现有模块通过少许的改动,快速地构建成为一个 24 点游戏所需的模块呢? 作到这些,并能总结一些规律,软件工程课程就有点摸到 “工程” 的边了!