成员:顾思宇2016011993 程羚2016012050
1.仓库地址:https://git.coding.net/DandelionClaw/WEB_Calculator.git
注:
本项目为web端,并且需要连接SQL Server数据库。
可使用已注册用户“admin”密码“admin”登录,或注册新用户登录。
首页目录为/WEB_Calculator/mysite/WebRoot/index.jsp,虚拟目录名为Calculator
tomcat中需加入WEB-INF/lib中的包
2.PSP:
3. 接口设计:
Information Hiding, Interface Design, Loose Coupling,我对这三个概念比较陌生,查了许久的资料才理解他们的概念。
(1)Information hiding 信息隐藏
信息隐藏是程序设计过程中的一种隔离原则,可以防止用户接触到一个类的某些部分。一个程序模块可以将它的信息隐藏起来,对外仅仅展现出一种接口。当这个模块的具体实现发生改变时,只要保证它的接口不发生变化,则就算不修改模块外的其他代码,程序依旧可以正确执行。
(2)interface 接口设计
软件的接口设计元素描述了信息如何流入和流出系统以及被定义为体系结构一份的构件之间是如何通信的。这次的作业中用到的主要是各种设计构件之间的内部接口,通过这些接口让软件体系结构中的构件之间进行内部通信与协作。接口实现时,保证所有操作和消息传递模式都得到实现,且不同类的操作之间能够进行通信和协作。
(3)losing coupling 松耦合
松耦合系统通常是基于消息的系统,此时客户端和远程服务并不知道对方是如何实现的。客户端和服务之间的通讯由消息的架构支配。只要消息符合协商的架构,则客户端或服务的实现就可以根据需要进行更改,而不必担心会破坏对方。
对于信息隐藏,我使用了用private封装重要信息的方法,并设计get、set方法访问这些隐藏信息。
我们在进行设计的时候将命令行程序作为核心程序封装在package core中,使用servlet在调用该程序,调用时只需要定义一个字符串数组,将命令作为参数即。并且为了维护程序的稳定性,我使用了大量的异常处理。而命令行程序即Class Command调用了我上一次个人作业所写的程序接口。因此,在此次作业中,我用了很短的时间就完成了命令行程序的编写。
这次的作业让我充分感受到了面向对象编程和接口设计的魅力。
4. 计算模块接口的设计与实现过程:
(1)我设计了4个类,比个人作业时多出了两个,分别是Command和MyException。
其中Command调用了个人作业所写的Class Main,它主要负责处理用户输入的命令和抛出异常,
而MyException用于异常处理。Main和Formula除了在上次的基础上优化了一下以外基本没有改动,在这里就不再赘述了。
下图,图一为各类的调用关系,图二图三为Main类和Formula类所包含的成员。
(2)我算法关键和独到之处,除了上次所写的算式计算外,我觉得就是我这次的异常处理机制了,没有异常处理的程序是脆弱而不完整的程序,这必然是一个程序的关键点。
5. 计算模块接口部分的性能改进:
在性能改进这一块我花费了大约2小时,我的主要改进思路是对于内存和运行时间的优化
因此除了将原本的命令行程序扩展命令格式以外,我还优化了字符串存储方式,详细内容如下:
1.CPU Views:
由图可见,消耗最大的是replyonlineFinal.jsp,这个页面是用于计算用时、获取正确率和展示错题,消耗主要在访问数据库上。
2. 内存优化:
当输入参数为时 效能分析图为
经分析String内存占用较大,原因是因为我用一个String存储了全部的算式并且没有释放内存,优化代码后性能明显改善了许多。
另外我还发现了一个有趣的现象,当代码执行完成后,内存并没有释放而是叠加了起来,如图所示:
当题目为50时内存变化,之后内存不断攀升达到一定数值后稳定下来。
当我再次输入50时,内存变化
经研究发现这是因为java特殊的内存释放机制所导致的。
6. 计算模块部分单元测试展示:
我的测试思路是测试所有的正常和异常情况,确保程序能够正常运行。
@Test
public void testMain(){//本函数为正确输入形式,并打乱了命令输入顺序
String[] args = {"-o","2","-n","20","-c","-b","-m","1","100"};
command.main(args);
}
@Test
public void testMain1(){//本函数只正确输入了-n命令,但没有输入另一个必须输入的命令-m
String[] args = {"-n","20"};
command.main(args);
}
@Test
public void testMain2(){//本函数测试不输入任何命令的情况
String[] args = {};
command.main(args);
}
@Test
public void testMain3(){//本函数测试输入了必须输入的-n、-m命令,但-m命令少输入了一个参数
String[] args = {"-n","20","-m","1"};
command.main(args);
}
@Test
public void testMain4(){//本函数测试输入错误命令
String[] args = {"aaaa"};
command.main(args);
}
@Test
public void testMain5(){//本函数测试输入了-n命令,但缺少了参数
String[] args = {"-n"};
command.main(args);
}
@Test
public void testMain6(){//本函数测试输入了-0命令,但缺少了参数
String[] args = {"-o"};
command.main(args);
}
@Test
public void testMain7(){//本函数测试输入了必须输入的-n、-m命令,但-n参数范围错误
String[] args = {"-n","1000000","-m","1","100"};
command.main(args);
}
@Test
public void testMain8(){//本函数测试输入了必须输入的-n、-m命令,但-n参数类型错误
String[] args = {"-n","aaa","-m","1","100"};
command.main(args);
}
@Test
public void testMain9(){//本函数测试输入了必须输入的-n、-m命令,但-m参数范围错误
String[] args = {"-n","10","-m","100","50"};
command.main(args);
}
@Test
public void testMain10(){//本函数本函数测试输入了必须输入的-n、-m命令,但-o参数范围错误
String[] args = {"-n","10","-m","1","100","-o","-100"};
command.main(args);
}
11个测试函数均运行正确
覆盖率达到了90%以上
7. 计算模块部分异常处理说明:
我设置了大量的异常处理,确保程序无论如何都能够正确运行:
-n 这个命令是伴随一个参数的,我设计了①用户没有输入参数,②参数范围不对的情况
-m 这个命令伴随了两个参数,我设计了①用户没有输入参数或只输入了一个,②参数范围不对的情况,③下限数值高于上限。其中第三点明显是一个老师故意留下的小陷阱,老师题目要求的上限值最低是50,但下限值最高是100。
-o 这个命令是伴随一个参数的,我设计了①用户没有输入参数,②参数范围不对的情况
其他 除了针对上述命令的处理外,还有参数类型错误的处理,两个必要的命令-n、-m缺少了任一或都没出现的处理,没有输入命令的处理
为了避免脱离程序而使描述抽象的问题,我将单元测试样例和相应错误场景以注释形式写在了6.代码中。
8. 界面模块的详细设计过程:
由于我们两人分工明确,我负责后端,她负责前端,所以所有前端代码都不是由我完成。但是我作为后端,也就是功能的实现者自然也负责需求的提出。
登录和注册界面,用户名密码这个自然是必须要有的
题库定制界面,我们设计了导航栏、语言切换、用户切换、查阅历史题目以及一个页内跳转的按钮。点击“开始做题”或滚轮向下滑动,就到定制区,我们在前端也设计了异常处理,如果用户输入的参数格式不对或者范围不对,前端会显示提示并且表单将会无法提交,直至用户输入正确参数为止。
做题界面,除了必要的提交按钮外,我们还人性化地设计了“上一题”、“下一题”按钮,以及“放弃答题”按钮。
做题完成界面,在本页面展示了用户的用时和正确率,并且展示了用户的所有错题。
历史记录界面,在本页面展示了当前用户的所有做题记录和最佳记录。
界面展示见9.
下面给出了一个做题界面按钮的CSS代码
button{
margin: 20px;
}
button a{
color: black;
}
.button{
margin:60px 220px;
margin-top: 60px;
}
.bt{
-webkit-transition: all 300ms cubic-bezier(0.19, 1, 0.22, 1);
transition: all 300ms cubic-bezier(0.19, 1, 0.22, 1);
-webkit-transform: translateX(47px);
will-change: transform;
z-index:;
overflow: hidden;
border-radius: 40px;
padding: 12px 15px;
background: #43CB9D;
color: white;
font-size: 16px;
font-weight:;
letter-spacing: 1px;
line-height:;
text-transform: uppercase;
}
.bt:before {
-webkit-transition: opacity 300ms ease-out, -webkit-transform 0ms 300ms;
transition: opacity 300ms ease-out, -webkit-transform 0ms 300ms;
transition: opacity 300ms ease-out, transform 0ms 300ms;
transition: opacity 300ms ease-out, transform 0ms 300ms, -webkit-transform 0ms 300ms;
opacity:;
-webkit-transform-origin: center center;
transform-origin: center center;
-webkit-transform: scale(0);
transform: scale(0);
will-change: transform, opacity;
content: "";
display: block;
z-index: -1;
top:;
left:;
width: 100%;
height: 100%;
background: #3FE2D9;
border-radius: 40px;
}
.bt{
float: left;
}
9. 界面模块与计算模块的对接:
(1)UI设计
我们现在纸上完成了基本的UI 界面设计,然后由我的搭档负责实现和美化,详细内容在8.中都已说明。
(2)前后端对接
我采用servlet+JavaBean+form表单的方法将前后端联系起来。
例如在产生算式时,用form表单将用户输入的参数获取至servlet,将命令行程序import进servlet中,使用接口调用命令行程序产生算式。然后将生成的算式保存至数据库中,再在前端使用JavaBean链接数据库,把算式输出到前端。用户注册、答题等同理。
(3)功能展示
①登录和注册功能:
②附加功能1 多语言功能:
③题库生成功能,若参数错误前后端均提供了异常处理,前端显示提示并使得表单无法提交,后端throw catch处理异常。
④做题功能,计时功能,本次正确率统计功能,错题展示功能
⑤上传题目功能
⑥附加功能2 分用户历史做题记录展示,并显示最佳记录
10. 结对过程
我们小队分工比较明确,我主要负责后端,队友主要负责前端。队友为我们小组的领航员~
我们的第一步:选择了我的第一次作业代码,队友在原本的基础上面进行更改,我开始做基本的前端页面。我们先自己做自己的,在各自擅长的领域奋斗前行~
我们的第二步:队友开始写单元测试,她继续美化我们的web界面,我们对web界面有比较高的要求,一致认为好的界面是吸引用户的最好手段!
我们的第三步:初步前后端合体,实现一些基本功能,如:题目定制,在线答题,输出结果……因为我们是舍友,进行交流也比较方便,虽然出过一些bug,但是也很快就解决了。开心
我们的第四步:一起为模块增加异常处理,主要为“生成算式数量”异常和登录异常,在核心模块写入后,我们又考虑到用户体验,使用jQuery撰写了相应的及时提醒以及防止跳转,双重保障更贴心呢!
我们的第五步:检查整个项目,并进行优化。
11. 结对编程的优点和缺点
看教科书和其它参考书,网站中关于结对编程的章节,例如:
http://www.cnblogs.com/xinz/archive/2011/08/07/2130332.html
说明结对编程的优点和缺点。同时指出结对的每一个人的优点和缺点在哪里 (要列出至少三个优点和一个缺点)。(5')
结对编程的优点:
1. 在这次结对项目中,我的队友担任的是领航员的角色,我们各有所长,且把这份长处最大化。所以我觉得结对编程最大的好处在于1+1>2,做出的项目会更加的完美。
2. 在编程中,相互讨论,可以更快更有效地解决问题。比如在这次项目中,我们在做jQuery表单验证时,防止页面跳转的点怎么也做不出来,尝试过百度给的所有解决方法,大晚上把所有宿舍串了一遍也没
解决,然后有天闲聊,聊着聊着就聊出了一个新的方法。也验证了那句,语言是思维碰撞的开始。
3. 最深有感触的一点:bug解决的更快了!
结对编程的缺点:
- 对于有不同习惯的同学,在一起编程可能会有比较多的麻烦,导致效率底下。
- 部分人可能更适合个人编程,而不习惯在编码的时候还要应对他人的感受,甚至导致1+1<1
我可爱的队友:优点1. 脾气好,有干劲
优点2. 有耐心,遇到bug也愿意一直调,想办法解决
优点3. 能力强,提出的需求都完成了
缺点:缺点是木有缺点!
12. 附加题!!!
一、 多语言支持
实现原理:通过jquery+ajax实现。核心思想是通过i18n属性设置多语言的key,然后插件对所有带i18n属性的dom进行扫描处理。但是前端多语言并不仅仅是这一个需求,进而延伸的还有事件函数中需要使用的消息,比如
新增一条数据后的提示消息“新增成功”。这时,就需要对普通字符串对象,即String对象进行处理,使其支持多语言。
这里贴出部分代码:
1. 浏览器语言的获取
/**
* 获取浏览器语言
* @returns language
*/
function getLang(){
if(typeof(cacheLang) != "undefined"){
return cacheLang;
}
if (navigator.language) {
cacheLang = navigator.language.toLowerCase();
return cacheLang;
}else {
cacheLang = navigator.browserLanguage.toLowerCase();
return cacheLang;
}
}
2.多语言配置的加载
var url = $.type(p) == "string" ? p : "i18n/" + getLang() + ".json";//p为参数,可通过p手动指定配置路径,此时不根据默认路径加载多语言消息配置
$.ajax({
url : url,
dataType : 'json',
type : "GET",
success : function(data, textStatus, jqXHR) {
messages = data;//缓存消息
run(data);//执行dom处理
callback(self);//回调处理,self为插件本身对象
},
error : function(a, b, c) {
throw "Load i18n message error [" + p + "], cause by : " + b;
}
});
3.DOM处理
/**
* 替换国际化消息
* @param ms 消息对象
*/
function run(ms){
$("*[i18n]").each(function(){
var o = $(this);
var key = o.attr("i18n");//获取key
var val = o.attr("i18n-set");//获取消息设置目标
var message = _getMessage(ms, key);//取得消息
switch(val){//根据不同目标设置到不同的地方
case undefined:
o.html(message);
break;
case "append":
o.html(o.html() + message);
break;
case "html":
o.html(message);
break;
case "value":
o.val(message);
break;
default:
o.attr(val, message);
break;
}
o.removeAttr("i18n");//移除属性,避免二次处理
o.removeAttr("i18n-set");
});
}
4. 获取多语言的方法
/**
* 获取多语言消息
* @param msgs 消息对象
* @param key 消息名称
* @returns value 消息
*/
function _getMessage(msgs, key){
var message;
try{
message = msgs[key];
}catch(e){
message = key;
}
return message;
}
5.实现对String对象的改造
/**
* 为String提供local方法获取多语言消息
* @return value 消息
*/
String.prototype.local = function(){
return _getMessage(messages, this);
}
二、多用户支持
主要通过javaBean+JSP连接数据库来实现,建立Users表和Score表,分别记录用户信息和得分信息。
在用户登录时将用户名保存在session中,通过在jsp页面中调用数据库来获得当前用户的做题记录和得分信息。
最佳记录使用SQL语句排序来获得
String sql = "SELECT TOP 1 per FROM Score where username='"+User+"' ORDER BY per DESC";
ResultSet rs = connDB.executeQuery(sql);
String best = "";
if(!rs.next()){
best = "您未做过题目";
}
else best=rs.getString(1);