软件构造-实验四-仅供参考
姓名 |
|
学号 |
|
班号 |
|
电子邮件 |
|
手机号码 |
|
目录
1 实验目标概述···· 1
2 实验环境配置···· 1
3 实验过程···· 1
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.3 Logging· 2
3.3.1 写日志···· 2
3.3.2 日志查询···· 2
3.4 Testing for Robustness and Correctness· 2
3.4.1 Testing strategy· 2
3.4.2 测试用例设计···· 2
3.4.3 测试运行结果与EclEmma覆盖度报告···· 2
3.5 SpotBugs tool 2
3.6 Debugging· 3
3.6.1 理解待调试程序的代码思想···· 3
3.6.2 发现并定位错误的过程··· 3
3.6.3 如何修正错误···· 3
3.6.4 结果···· 3
4 实验进度记录···· 3
5 实验过程中遇到的困难与解决途径···· 3
6 实验过程中收获的经验、教训、感想··· 4
6.1 实验过程中收获的经验和教训··· 4
6.2 针对以下方面的感受···· 4
1 实验目标概述
本次实验重点训练学生面向健壮性和正确性的编程技能,利用错误和异常处 理、断言与防御式编程技术、日志/断点等调试技术、黑盒测试编程技术,使程序 可在不同的健壮性/正确性需求下能恰当的处理各种例外与错误情况,在出错后 可优雅的退出或继续执行,发现错误之后可有效的定位错误并做出修改。 实验针对 Lab 3 中写好的 ADT 代码和基于该 ADT 的三个应用的代码,使用 以下技术进行改造,提高其健壮性和正确性:
错误处理
异常处理
Assertion 和防御式编程
日志
调试技术
黑盒测试及代码覆盖度
实验环境配置
安装jdk 随意选择目录 只需把默认安装目录 \java 之前的目录修改即可
安装jre→更改→ \java 之前目录和安装 jdk 目录相同即可
安装完JDK后配置环境变量 计算机→属性→高级系统设置→高级→环境变量
系统变量→新建 JAVA_HOME 变量 。
变量值填写jdk的安装目录
系统变量→寻找 Path 变量→编辑
在变量值最后输入 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;
系统变量→新建 CLASSPATH 变量
变量值填写 .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar(注意最前面有一点)
系统变量配置完毕
2 实验过程
2.1 Error and Exception Handling
我写了一个自己的判断读入文件是否符合标准的error exception
只有继承exception后才能生成自己的异常。然后我又针对每个项目都生成了他们的check方法,然后调用。
Stellarsystem:
然后在函数中调用:
Atomstructure:
在这个项目中,因为两个检查的函数传递的参数不一样,所以我分成了两个check和check1
在函数中的调用:
Socialnetworkcircul:
在方法中的调用:
这样我就完成了对于未知文件的输入内容的检查。
2.2 Assertion and Defensive Programming
2.2.1 checkRep()检查invariants
我对于每个基础的类文件都有checkrep()方法,因为这个方法是用来防止调用get类方法时有空的参数情况发生。
Stellarsystem:
Centralstarobject:
这样的好处是面对想要获取这个类对象的信息就要经受检查。
Physicalstarobject:
检查了所有信息都不能是无效的,对于调用get类的使用者来说也是一种保障。
Atomstructure:
Atomobject:
面对与所有的信息都要经过我们自己的判断才能流传出去,这就是我们计算机人的坚持。
Centeratomobject:
因为原子核的之子和中子不可能是0,所以有必要检测一下。
Socialnetworkcircle:
Centralsocialobject:
名字这些信息如果是空的被调用了get方法,多丢脸,说不定还发生一堆bug。
Socialobject:
原因也是一样的,不能让无用的信息流传出去。
2.2.2 Assertion保障pre-/post-condition
Assert这个断言关键字有很重要的作用,在checkrep中发挥了重要的作用。
在strllarsystem中:
Assert
this.PlanetName!=null&&this.PlanetRadius!=0&&this.PlanetWeight!=0;
assert this.PlanetName!=null&&this.PlanetRadius!=0&&this.PlanetForm!=null&&this.TrackRadiu!=0&&this.RevolutionSpeed!=0&&this.Orientation!=null&&this.Angle!=0&&this.planetColor!=null;
atomobject中:
assert ElementName!=null&&NumberOfTracks!=0&&NumberOfElectron!=null;
assert this.PlanetName!=null&&this.PlanetRadius!=0&&this.PlanetWeight!=0;
socialnetworkcircle:
assert name!=null&&ege!=0&&sex!=null;
assert name!=null&&ege!=0&&sex!=null;
这些断言就是运用在checkrep中的主要部分,检测内容是否为空,保障调用出去一定不能是空的。
2.3 Logging
2.3.1 写日志
对于写日志,我通常是先:
static Logger log = Logger.getLogger("temp");
对于日志也有等级之分
各级别按降序排列如下:
- SEVERE(最高值)
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST(最低值)
通常我运用的都是info等级
比如:log.info("stellersystem readfile");
最后的结果打印到了输出栏里
2.3.2 日志查询
日志查询的工作我百度了很久发现没有内置的方法让我可以查询,于是我只好运用先将日志写入文件中,再从文件中读入进行匹配查询,于是通过日志的不同内容我做了6块5个分类。我将日志的等级设置成了warning
输入1是匹配日志的生成时间,2是匹配日志的等级,3是匹配日志在哪一个类中生成,4是批评日日志在哪一个方法中生成,5是匹配日志的内容,6是通过正则表达式来匹配时间。
输入1:
输入2:
输入3:
输入4:
输入5:
输入6:
2.4 Testing for Robustness and Correctness
2.4.1 Testing strategy
对于非法输入我已经进行了检测以及报出异常因为我在写程序的时候就发生过因为输入用例不符合标准而出现的bug不知道是什么,反而白白浪费时间的情况了。
对于测试函数我就花费了一整天时间,用来将各个方法都照顾到。
2.4.2 测试用例设计
我设计的测试用例是:
Stellar ::= <hello,6,hello,3.0>
尖括号括住的第三处应该是一个double类型我改成了hello,在进行转换的时候会抛出错误。
结果不出我所料被check检查了出来并且抛出了我自己写的异常输出了“输入文件不合法”的字样并且打印出了出问题所在的栈的轨迹。
2.4.3 测试运行结果与EclEmma覆盖度报告
根据我的测试结果是符合预期的,覆盖度显示:
我运用eclemma导出了报告
所有test里面的测试都是我写出来的。
2.5 SpotBugs tool
安装spotbugs tool这个测试软件后我对于我所写的代码进行了测试发现:
一共有8处被标记了出来,经过查看发现有四处都是
(line=br.readLine())!=null
我经过思考发现不能这样写,因为line是一个string类型的变量,所以string类型的变量进行比较应该使用equal方法进行比较。
所以我改成了:!(line=br.readLine()).equals(null)
还有一处我是跟空字符进行比较了,没有意义。
还有2处就是在调用线程的时候有危险调用
常量字符串在JVM加载的所有其他类之间实现并共享。 因此,此代码锁定其他代码也可能锁定的内容。 这可能导致非常奇怪且难以诊断阻塞和死锁行为。
还有一处是因为我为了测试类直接在里面写了一个main方法,结果其他地方没有调用,被判定为死方法,建议我删除。
Bug: Dead store to f in P1.CircularOrbitHelper.main(String[])
This instruction assigns a value to a local variable, but the value is not read or used in any subsequent instruction. Often, this indicates an error, because the value computed is never used.
Note that Sun's javac compiler often generates dead stores for final local variables. Because SpotBugs is a bytecode-based tool, there is no easy way to eliminate these false positives.
Rank: Of Concern (15), confidence:
High
Pattern: DLS_DEAD_LOCAL_STORE
Type: DLS, Category: STYLE (Dodgy code)
经过反思,我认为我的代码还是不够严谨,在往后的日子里我要认真的书写规范的Java代码。
2.6 Debugging
2.6.1 理解待调试程序的代码思想
Findmediansortedarrays是寻找物理位置居中的数字,并且分别判断是奇数个还是偶数个。
Removecomments是移除一个字符串数组中的所有注释。
Topvotedcandidate是查找出在一个时间点上哪个人的投票多或者是票数一样哪个最近获得了投票。
2.6.2 发现并定位错误的过程
因为要debug,所以我先对每一个类都写了一个main方法,方便测试,然后对于每一个分叉都写了不同的system.out.print方法进行区分,输入符合测试方法的参数进行测试,查找返回地方的输出点。进行debug。
这个方法只对于前面两个管用,因为第三个根本就是一个死循环没有输出。所以只好根据它所使用的二分查找方法和构造方法进行研究,发现其中有很明显的语法错误,有初始化不是0,有获得选票没有+1的错误。在二分查找法中,对于索引的引用是最大的问题
2.6.3 如何修正错误
Findmediansortedarrays:
(m + n + 1) % 2 == 1改为
(m + n + 1) % 2 == 0
Removecomments:
If(inBlock)的判定条件改为了
if (!inBlock&&(chars[i]!='*'&&chars[i]!='/'))
Topvotedcandidate:
改为了
2.6.4 结果
Findmediansortedarrays:
输入:
int[] a= {1,1};
int[] b= {1,2,3};
输出:
Removecomments:
输入:
String[] source= {"a/* comment","line","more_comment */b"};
输出:
Topvotedcandidate:
输入:
int[] persons= {0,1,1,0,0,1,0};
int[] times= {0,5,10,15,20,25,30};
TopVotedCandidate k=new TopVotedCandidate(persons, times);
int s=k.q(10);
输出:
3 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 |
时间段 |
计划任务 |
实际完成情况 |
5.13 |
3:45-10:30 |
写完debug |
完成任务 |
5.14-5.15 |
下午5点以后 |
写完日志内容 |
完成80% |
5.16 |
6:00-11:00 |
完善内容并且加注释 |
写完了log之前的内容 |
5.17-5.18 |
有空闲就写 |
完成test测试 |
完成 |
4 实验过程中遇到的困难与解决途径
遇到的难点 |
解决途径 |
不会日志的应用
|
通过百度,并且自己写测试用例掌握 |
不会自己捕捉异常,和写自己的异常
|
在CSDN上面学习到了方法,并且掌握了 |
5 实验过程中收获的经验、教训、感想
5.1 实验过程中收获的经验和教训
5.2 针对以下方面的感受
(1) 健壮性和正确性,二者对编程中程序员的思路有什么不同的影响?
(2) 为了应对1%可能出现的错误或异常,需要增加很多行的代码,这是否划算?(考虑这个反例:民航飞机上为何不安装降落伞?)
(3) “让自己的程序能应对更多的异常情况”和“让客户端/程序的用户承担确保正确性的职责”,二者有什么差异?你在哪些编程场景下会考虑遵循前者、在哪些场景下考虑遵循后者?
(4) 过分谨慎的“防御”(excessively defensive)真的有必要吗?如果你在完成Lab5的时候发现Lab5追求的是I/O大文件时的性能(时间/空间),你是否会回过头来修改你在Lab3和本实验里所做的各类defensive措施?如何在二者之间取得平衡?
(5) 通过调试发现并定位错误,你自己的编程经历中有总结出一些有效的方法吗?请分享之。Assertion和log技术是否会帮助你更有效的定位错误?
(6) 怎么才是“充分的测试”?代码覆盖度100%是否就意味着100%充分的测试?
(7) Debug一个错误的程序,有乐趣吗?
(8) 关于本实验的工作量、难度、deadline。
(9) 到目前为止你对《软件构造》课程的评价和建议。
(10) 期末考试临近,你对占成绩60%的闭卷考试有什么期望或建议?//请严肃的提出,杜绝开玩笑,教师会认真考虑你们的建议。
- 面向健壮性的编程有以下几点要求或优点:处理未期望的行为和错误终止即使终止执行,也要准确/无歧义的向用户展示全面的错误信息错误信息有助于进行debug
健壮性原则:总是假定用户为恶意用户,假定自己的代码会失败把用户想象成一个silly b,可能输出任何东西注意,因为用户很silly,最好要返回给用户错误提示信息,而且要详细准确无歧义!对自己的代码要保守,对用户的行为要开放
正确性:永不给用户错误的结果
- 不划算。因为不能保证修复了这个bug而加的所有代码都正确,很有可能牵一发而动全身。就像飞机上不会装有降落伞一样,因为乘客(用户)压根不知道目标在哪里。
- 一个是健壮性,一个是正确性。在做一个客户端的时候我更多的考虑的是健壮性,因为有很多的操作是我们开发者想不出来的。但是在一个严肃的需要正确信息的地方我要考虑正确性。
- 可能有必要吧。我编程经验比较少,不做评论。
- 哈哈哈,就是printf(“hello world”)了。Log和assert 对我也有帮助。
- 覆盖率越高肯定越好,但是100%覆盖也不能说明完全测试,因为还有意想不到的输入。
- 有乐趣。
- 工作量较大,难度较低,deadline不紧。
- 软件构造课程让我更加详细的了解了Java程序的构成还是很有作用的。
- 期望能考出我应有的水平。