3.1 Error and Exception Handling· 2
3.2 Assertion and Defensive Programming· 2
3.2.1 checkRep()检查invariants· 2
3.2.2 Assertion保障pre-/post-condition· 2
3.4 Testing for Robustness and Correctness· 2
3.4.3 测试运行结果与EclEmma覆盖度报告···· 2
本次实验重点训练学生面向健壮性和正确性的编程技能,利用错误和异常处
理、断言与防御式编程技术、日志/断点等调试技术、黑盒测试编程技术,使程序
可在不同的健壮性/正确性需求下能恰当的处理各种例外与错误情况,在出错后
可优雅的退出或继续执行,发现错误之后可有效的定位错误并做出修改。
实验针对 Lab 3 中写好的 ADT 代码和基于该 ADT 的三个应用的代码,使用
以下技术进行改造,提高其健壮性和正确性:
⚫ 错误处理
⚫ 异常处理
⚫ Assertion 和防御式编程
⚫ 日志
⚫ 调试技术
⚫ 黑盒测试及代码覆盖度
在Eclipse MarketPlace中下载安装SpotBugs
在这里给出你的GitHub Lab4仓库的URL地址(Lab4-学号)。
https://github.com/ComputerScienceHIT/Lab4-1170300724
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明
针对三个场景写了三个exception
分别说明:
TrackGameExeption
分别进行了几个判断,
检测比赛类型,只能是100,200,400米跑
public static boolean checkGameType(int n) {
if(n==100||n==200||n==400) {
return true;
}
else {
return false;
}
}
检测轨道数目,需要在4-10之间
public static boolean checkNumOfTracks(String s) {
if(Integer.valueOf(s)<4||Integer.valueOf(s)>10) {
System.out.println("轨道数不在4-10之间");
return false;
}
return true;
}
检查运动员号码,必须为整数
public static boolean checkAnum(String s) {
boolean l=true;
if(s.split("\\.").length!=1) {
System.out.println("号码是小数");
l=false ;
}
if(Double.valueOf(s)<=0) {
System.out.println("号码是负数");
l=false;
}
return l;
}
检测运动员年龄,必须是正整数
public static boolean checkAage(String s) {
boolean l=true;
if(s.split("\\.").length!=1) {
System.out.println("年龄是小数");
l=false ;
}
if(Double.valueOf(s)<=0) {
System.out.println("年龄是负数");
l=false;
}
return l;
}
检测运动员的最高分
public static boolean checkAscore(String s) {
String tempString[]=s.split("\\.");
if(tempString[0].length()<1||tempString[0].length()>2||tempString[1].length()!=2) {
System.out.println("成绩格式出错");
return false;
}
return true;
}
检测运动员名字,没有空格和数字
public static boolean checkAname(String s) {
boolean l=true;
String regex1="\\s";
String regex2="[0-9]";
Pattern p1=Pattern.compile(regex1);
Matcher m1=p1.matcher(s);
Pattern p2=Pattern.compile(regex2);
Matcher m2=p2.matcher(s);
if(m1.find()) {
l=false;
System.out.println("有空格");
}
if(m2.find()) {
System.out.println("有数字");
l=false;
}
return l;
}
检测运动员的国际,必须是3位大小写字母
public static boolean checkAcoun(String s) {
if((!s.toUpperCase().equals(s))||s.length()!=3) {
System.out.println("国籍必须是三位大写字母");
return false;
}
return true;
}
SocialExeption
检测人的性别输入是否正确
public static boolean checkmale(String s) {
s=s.replaceAll("[\\s]+", "");
if((!s.equals("M"))&&(!s.equals("F"))) {
System.out.println("性别必须是M或F");
return false;
}
return true;
}
检测人的年龄输入必须是正整数
public static boolean checkyear(String s) {
boolean flag=true;
if(s.split("\\.").length!=1) {
System.out.println("年龄是小数");
flag=false ;
}
if(Double.valueOf(s)<=0) {
System.out.println("年龄是负数");
flag=false;
}
return flag;
}
AtomExeption
public static boolean checkname(String s) {
if(s.length()>2) {
return false;
}
return true;
}
public static boolean checkAtomNumOfTracks(String s) {
if(Integer.valueOf(s)<=0) {
return false;
}
return true;
}
-
- Assertion and Defensive Programming
- checkRep()检查invariants
- Assertion and Defensive Programming
利用checkrep进行保护
public boolean checkRep() {
try {
for(TrackGame k:strategy) {
if((!k.getCenter().getName().equals("empty"))) {
throw new TrackGameExeption("中心物体不为空");
}
}
int size=strategy.size();
for(int i=0;i<=size-2;i++) {
if(strategy.get(i).getTracks().keySet().size()>10||strategy.get(i).getTracks().keySet().size()<4) {
throw new TrackGameExeption("轨道数不在【4,10】中");
}
for(Track k:strategy.get(i).getTracks().keySet()) {
if(strategy.get(i).getTracks().get(k).size()!=1) {
throw new TrackGameExeption("前n-1组不满员或人数多于1");
}
}
}
return true;
} catch (TrackGameExeption e) {
System.out.println(e);
LogRecord lr=new LogRecord(Level.WARNING, e.getClass().getName()+":"+e.getMessage());
log.log(lr);
System.exit(0);
return false;
}
这部分是trackgame的,保证每个跑道上面都有一个人,中心物体不存在,整个人数不能超过跑道数
AtomStructure:
只检查中心物体是否存在
跃迁电子数不能超过源轨道上的电子数
SocialNetwork:
public boolean checkRep() {
try {
for(Track k:this.getTracks().keySet()) {
for(Person j:this.getTracks().get(k)) {
if(centralDistance(j)==-1) {
throw new SocialExeption("j.getName()"+"不应该出现在轨道系统中");
}
if(this.centralDistance(j)!=k.getR()) {
throw new SocialExeption(j.getName()+"出现在错误轨道");
}
}
}
return true;
} catch (SocialExeption e) {
System.out.println(e);
System.exit(0);
return false;
}
}
和中心物体没有关系的元素不应该出现在轨道系统中
每个Friend到中心点的逻辑距离即为其轨道的序号
-
-
- Assertion保障pre-/post-condition
-
没有增加断言,大部分地方加了checkRep来替代
public class log {
public Time time;
public String message;
public log(Time time, String message) {
super();
this.time = time;
this.message = message;
}
首先在网上找到了一个写日志不包含汉字的日志格式
创建了一个专门提供logger的类,每个包公用一个logger,所有logger共用一个Handler,写到同一个文件中,所有日志的级别都是info
FileHandler fileHandler=new FileHandler("txt/log.txt",true);
fileHandler.setFormatter(new MyFormatter());
log.addHandler(fileHandler);
log.setLevel(Level.INFO);
按时间查询
public String search(Time time1,Time time2) {
String string="";
for(log k:logs) {
if(k.time.after(time1)&&k.time.before(time2)) {
string=string+k.searchResult()+"\n";
}
}
return string;
}
因此建立了时间类
this.year = year;
this.month = month;
this.day = day;
this.hour = hour;
this.minite = minite;
this.second = second;
还建立了before和after方法
就是比较一下时间的大小
按关键字查询
public String search(String s) {
String string="";
Pattern p1=Pattern.compile(s);
for(log k:logs) {
Matcher m1=p1.matcher(k.message);
if(m1.find()) {
string=string+k.searchResult()+"\n";
}
}
return string;
}
关键字匹配就行
根据想到的错误,写几个测试
写几个错误的测试用例进行测试
ElementName ::= ER
NumberOfTracks ::= 5
NumberOfElectron ::= -1/2;2/8;3/18;4/30;5/8;6/2
Athlete ::= <Bolt,9.94,JAM,38,1>
Athlete ::= <Lewis,2,USA,31,10.00>
Athlete ::= <Ronaldo,10,CNH,20,9.85>
Athlete ::= <Wei,13,JPN,40,9.95>
Athlete ::= <Chen,7,KOR,29,10.12>
Athlete ::= <Park,12,USA,28,10.01>
Athlete ::= <Trump,9,CHN,19,9.89>
Athlete ::= <Obama,6,RUS,19,9.90>
Game ::= 102
Athlete ::= <Cliton,8,USA,21,9.92>
Athlete ::= <Chistropher,4,USA,39,9.92>
Athlete ::= <Peter,5,USA,40,10.10>
Athlete ::= <Tommy,3,JAM,19,10.11>
Athlete ::= <Coal,11,RUS,19,10.11>
NumOfTracks ::= 11
Athlete ::= <Bolt,1,JAM,38,9.94>
Athlete ::= <Lewis,2,USA,31,10.00>
Athlete ::= <Ronaldo,10,CNH,20,9.85>
Athlete ::= <Wei,13,JPN,40,9.95>
Athlete ::= <Chen,7,KOR,29,10.12>
Athlete ::= <Park,12,USA,28,10.01>
Athlete ::= <Trump,9,CHN,19,9.89>
Athlete ::= <Obama,6,RUS,19,9.90>
Game ::= 100
Athlete ::= <Cliton,USA,8,21,9.92>
Athlete ::= <Chistropher,4,USA,39,9.92>
Athlete ::= <Peter,5,USA,40,10.10>
Athlete ::= <Tommy,3,JAM,19,10.11>
Athlete ::= <Coal,11,RUS,19,10.11>
NumOfTracks ::= 11
-
-
- 测试运行结果与EclEmma覆盖度报告
-
因为有一些原本就写的测试代码,所以运行不到,注释掉之后覆盖率1
修改了一个判断条件的bug,就是说if判断里面,有多余的if完全没有必要。
FindMedian
为了寻找中位数,首先把长度较小的数组放在A中,较大的放在B中,然后在A,B两个数组中各找一半,其在数组中下标为i-1和j-1,设为初始结果,然后对其进行循环修改,修改方式有两种,一为如果A中i-1处数大于B中j处的数,说明A中多取了一个数,B中少去了一个数,则i=i-1,j=j+1,二为B中j-1处数大于A中i处的数,说明B中多取了一个数,A中少去了一个数,则i=i+1,j=j-1,找到正确的i和j后,进行判断,如果m+n为奇数,取到maxLeft直接返回即可,若为偶数,则再找到一个maxRight,取二者相加的一半返回。
RemoveComments
将连续字符串用注释隔开,删除注释之后前后是连在一起的之后的所有内容(该行的)都不应保留若字符串仅包括注释,则不保留该String
TopVotedCandidate 思路是维护一个List<List<Vote>> A;。A的序号为此Vote投票后,对应person的投票次数。A.get(i)表示一个vote列表,都有相同的投票次数。同时,它们的投票时间有小到大。
需要查询时,输入时间,首先定位此时最多的投票次数的列表。通过每个列表的第一个vote的时间二分查找,找到最接近的。找到这个后,再用二分查找,找到最接近的vote,输出人名。就可满足找到此时得票最多的人(如果相同则选择最近得票的人)的要求。
①FindMedian:
首先观察程序,发现取maxLeft和maxRight时都是取i-1,j-1处的数,发现i和j代表的意义,故得出结论i和j都应该向上取整,而原程序中j为向下取整,故此处错误。
其次在判断奇数偶数时应该是判断m+n,而非m+n+1,这个错的十分明显。
RemoveComments:
①原函数没有处理//之后的情况
②List是抽象的,不能直接被实例化,需要用ArrayList
- 重新开始读入非注释内容时应在“*/”的“/”处开启开关
- 数组边界应该是.length()-1而非.length()
TopVotedCandidate
1.Map<Integer, Integer> count = new HashMap()
猜测这是一个计数的,记录每人当前的得票数,但是却没有更新过。
2.更改后,由于二分法看得比较费劲,所以写了几个测试用例,debug了一下,发现二分法会陷入死循环。逐步调试了死循环的例子,增加了退出循环的条件,更改完成。
3.此时原用例已经没毛病了,看了看junit的覆盖率,有些分支没有用到,于是设计了新的测试用例,增加了新的人,果然漏了一种情况:当所有的A.get(i).get(0)的时间都比查询时间少时,仍会死循环,再更正。现在覆盖率已经100%了.
FindMedian:
把halflen改为(m+n+1)/2,即可使j向上取整。
把60行处m+n+1改为m+n
RemoveComments:
else if(!inBlock && i+1<line.length() && chars[i] == '/' && chars[i+1] == '/')
{inBlock = false; break; }
List换成ArrayList
重新开始读入使得判断条件为inBlock && i >=1 && chars[i-1] == '*' && chars[i] == '/'
为使i+1不越界, i+1 < line.length()
if (newline.length() > 0) ans.add(new String(newline));
TopVotedCandidate
1.取出数之后加1
2.if(lo==hi-1&&A.get(lo).get(0).time<=t&&A.get(hi).get(0).time>t)
break;
3. if(A.get(hi).get(0).time<=t) {
lo = hi;
break;
}
2和3两个二分法都需要加
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 计划任务 | 实际完成情况 |
2019.5.13 | 15:30-17:30 | 3.1和3.2 | 按时完成 |
2019.5.15 | 16:00-18:00 | 3.3和3.4 | 按时完成 |
2019.5.18 | 18:00-20:00 | 3.6 | 按时完成 |
2019.5.19 | 15:00-19:00 | 3.5 | 按时完成(遇到困难) |
遇到的难点 | 解决途径 |
在安装spotbug遇到了很大困难,eclipse报错
| 百度了错误代码,但是不知道是不是好使,总感觉它没有检测我的实际bug |
- 健壮性和正确性,二者对编程中程序员的思路有什么不同的影响?
健壮性
系统在不正常输入或不正常外部环境下仍能够表现正常的程度
处理未期望的行为和错误终止即使终止执行,也要准确/无歧义的向用户展示全面的错误信息,错误信息有助于进行debug:
封闭实现细节,限定用户的恶意行为,考虑极端情况,没有“不可能”
正确性
程序按照spec加以执行的能力,是最重要的质量指标!
永不给用户错误的结果; 让开发者变得更容易:用户输入错误,直接结束,正确性倾向于直接报错,健壮性则倾向于容错
对外的接口,倾向于健壮性;对内的实现,倾向于正确性。
- 为了应对1%可能出现的错误或异常,需要增加很多行的代码,这是否划算?(考虑这个反例:民航飞机上为何不安装降落伞?)
主要要看我们对这个程序的容忍度到底在哪里,就像飞机降落伞,这个错误出现概率极低,但是处理成本极高,就不需要处理了,但是对于一些容忍度极低的程序,比如核电站,哪怕是0.1%的错误也要进行修复
- “让自己的程序能应对更多的异常情况”和“让客户端/程序的用户承担确保正确性的职责”,二者有什么差异?你在哪些编程场景下会考虑遵循前者、在哪些场景下考虑遵循后者?
前者是说,用户输入的不对也能给出正确的反馈,后者是说用户输入的永远是对的,没有进行错误反馈。我觉得这个要区分程序的具体应用环境,因为在输入的时候给出足够的提示,用户输入错误的概率就会降低,而且代码量很低,所以我倾向于给出输入提示,而不是处理各种错误输入
- 过分谨慎的“防御”(excessively defensive)真的有必要吗?如果你在完成Lab5的时候发现Lab5追求的是I/O大文件时的性能(时间/空间),你是否会回过头来修改你在Lab3和本实验里所做的各类defensive措施?如何在二者之间取得平衡?
我觉得可以,很多防御其实是极不可能触发的,不需要重复检测,因为也没有人攻击这个程序…
- 通过调试发现并定位错误,你自己的编程经历中有总结出一些有效的方法吗?请分享之。Assertion和log技术是否会帮助你更有效的定位错误?
调试主要使用junit来进行定位的,利用log技术还要再写好多代码,不如应用现成的一些工具,增加效率。
- 怎么才是“充分的测试”?代码覆盖度100%是否就意味着100%充分的测试?
不是,充分测试应该是考虑了所有的错误可能性,覆盖率100%只是说对于写好的错误都测试到了,还有可能有没有考虑到的错误
- Debug一个错误的程序,有乐趣吗?
有一定的乐趣吧
- 关于本实验的工作量、难度、deadline。
都还可以
- 到目前为止你对《软件构造》课程的评价和建议。
感觉碰到的问题都有点独立
- 期末考试临近,你对占成绩60%的闭卷考试有什么期望或建议?//请严肃的提出,杜绝开玩笑,教师会认真考虑你们的建议。
挺好的,因为我觉得实验可能会出现各种各样扣分的情况,60%的考试可以补救大部分实验分,避免出现完全及不了格的情况,不过希望考试难度能低一些,因为期末好几科一起考,复习量太大了