Lab 4

1 实验目标概述

本次实验重点训练学生面向健壮性和正确性的编程技能,利用错误和异常处
理、断言与防御式编程技术、日志/断点等调试技术、黑盒测试编程技术,使程序
可在不同的健壮性/正确性需求下能恰当的处理各种例外与错误情况,在出错后
可优雅的退出或继续执行,发现错误之后可有效的定位错误并做出修改。
实验针对Lab3中写好的ADT代码和基于该ADT的三个应用的代码,使用以下技术进行改造,提高其健壮性和正确性:

  • 错误处理
  • 异常处理
  • Assertion和防御式编程
  • 日志
  • 调试技术
  • 黑盒测试和代码覆盖度

2 实验环境配置

操作系统:macOS Mojave 10.14.5
硬件环境:CPU:Intel Core i7-7920HQ@3.1GHz
RAM:16GB LPDDR3
开发、测试、运行环境:IntelliJ IDEA Ultimate 2019.1.2
GitHub Lab_4 URL:available

3 实验过程

3.1 Error and Exception Handling

设计了多个异常,实际用到的如下:

3.1.1 FileFormatException

文件格式异常,包括了其他数种异常情况,作为超类使用。包含了LabelExceptionNumberFormatException, SameLabelException, SentenceException, TimeFormatException

3.1.2 LabelException

标签异常,当数据中的标签类数据不符合规范时抛出该异常。

3.1.3NullApplicationException

空应用异常,当调用关系或添加安装、使用、卸载信息时找不到指定应用时抛出该异常。

3.1.4 NumberFormatException

数字格式异常,当数据中的数字类数据不符合规范时抛出该异常。

3.1.5 OrbitIntervalException

轨道间隔异常,监测到两个轨道间隔小于行星半径和时抛出该异常。

3.1.6 SameLabelException

相同标签异常,某些轨道系统不允许有相同标签的物体。当检测到物体存在相同标签时抛出该异常。

3.1.7 SentenceException

sentence格式异常,当数据中的sentence类数据不符合规范时抛出该异常。

3.1.8 TimeFormatException

时间格式异常,当数据中的时间类数据不符合规范时抛出该异常。

3.1.9 IllegalInputStatementException

输入异常,当输入时间错误时抛出该异常。

3.2 Assertion and Defensive Programming

3.2.1 checkRep()检查invariants

由于大部分的不变性已经在具体实现中不断检查,所以主要的checkRep只检查基本的属性。

3.2.1.1 StellarSystem
private void checkRep() throws RuntimeException {
   //Every track has a planet
   for (Track track: getTracks()) {
      assert getPhysicalObjectsOnTrack(track).size() == 1;
   }
}

检查每个轨道只有一个行星。

3.2.1.2 AtomStructure

public void checkRep() { assert getCentralObject() != null; }
检查核心存在。

3.2.1.3 PersonalAppEcosystem
private void checkRep() {
   assert getCentralObject() != null;
   assert period >= 1 && period <= 4;
   assert endTime.after(startTime);
}

检查每个用户存在,并且时间分割段处于设定范围中,并且结束时间必须位于开始时间之后。

3.2.2 Assertion保障pre-/post-condition

关于pre-/post-condition,考虑到实际情况,大多只是判定为不等于null,此处不做过多介绍。

3.3 Logging

3.3.1 写日志

使用了第三方库log4j记录日志,储存在src中的logs/logs.log中。
配置文件如下:

log4j.rootLogger=debug,D

log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File=logs/logs.log
log4j.appender.D.Append=true
log4j.appender.D.Threshold=DEBUG
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %c[%L][%M]%n

便于使用,简化了输出格式。
设置了3个等级:INFOWARNERROR
在各个应用的操作和异常位置记录log。

3.3.2 日志查询

设置了3种查询方式:根据时间段查询、根据日志类型查询以及根据方法名查询。
在APIs包中新创建了一个LogSearch类,便于简化操作,有以下域:

private String fileName;
private List<String> lineData = new ArrayList<>();

分别储存当前日志文件相对地址以及格式化数据(便于直接输出)。
主要使用正则表达式将日志格式化并进行比对,正则表达式如下:
([12]\\d{3}-[01]\\d-[0-3]\\d)\\s([012]\\d:[0-5]\\d:[0-5]\\d)" + ".\\d{3}\\s\\[([A-Z]+)]\\s[\\w.]+\\[[\\d]+]\\[(\\w+)]")
下面是主要方法:

private void update() throws FileNotFoundException //更新目录并重新读取文件,因为实际使用时日志会不断刷新
public void searchByPeriod(String start, String end)
      throws FileNotFoundException, ParseException //根据时间段查询日志。使用正则表达式格式化输入内容并进行异常判断,如果时间格式或不符要求会抛出异常
public void searchByType(String type) throws FileNotFoundException //根据类型查询日志。使用正则表达式格式化输入内容并进行异常判断,如果没有找到相应类型会抛出异常
public void searchByMethodName(String methodName) throws FileNotFoundException //根据方法名查询日志。使用正则表达式格式化输入内容并进行异常判断,如果没有找到该方法会提示没有搜索结果

3.4 Testing for Robustness and Correctness

3.4.1 Testing strategy

对所有主要核心方法进行了深度测试,针对很多复杂情况做了针对各种特殊情况的测试,很好地检验了核心底层方法的正确性和处理特殊情况的能力。

3.4.2 测试用例设计

以下简单介绍几个基本测试以展现测试的深度:
例如StellarSystem的测试:设计了多种异常情况文件,包括缺少中心物体,缺少轨道物体,各种数据格式错误,相同标签异常,轨道间隔异常等等,最终很好地检验了程序的健壮性,并且可以处理一些不符合规定格式的情况。
其他几个应用亦然。
对于底层的ConcreteCircularSystem,考虑的所有方法的测试,并且对于一些复杂度较高的钱方法,例如移除轨道,需要考虑到轨道上物体及其所有关系也要移除,因此专门测试了较复杂情况的移除,结果无误。对于其他方法也有同样处理。

3.4.3 测试运行结果与EclEmma覆盖度报告

最终根据覆盖率测试,全部方法覆盖率达到75%,代码量覆盖率72%。
核心代码覆盖率基本在85%到95%以上。未覆盖到的是get方法和接口、GUI、主方法等无需测试的内容,可以说基本完成的全部功能的测试。
覆盖率报告在另外的文件夹中,为html格式,可自行查看。

3.5 SpotBugs tool

使用了IDEA插件FindBugs发现了一些常见错误,比如switch中忘记加default,文件读写没有加编码格式,重载equals没有instanceof,格式化输出中建议使用%n换行等等。只需根据提示简单修改即可,最终实现了全部隐藏bug和警告的消除。该插件对于编程习惯的养成有较大帮助。

3.6 Debugging

3.6.1 理解待调试程序的代码思想

分别是找中位数、去注释和求出在时间t领先选举的人数。

3.6.2 发现并定位错误的过程

逐行分析代码并且根据自己设计的思路判断其正确性,如果认为不正确就进行修改,并根据调试过程确认正确性。

3.6.3 如何修正错误

根据自己设计的思路将认为不正确的地方进行修改,并根据调试过程确认正确性。通过进行测试,最终判断正确;如果测试不通过,则重新进行debug,并不断设断点进行调试。

3.6.4 结果

根据结果设计了一些测试,全部通过并证明了修改的正确性。

4 实验过程中遇到的困难与解决途径

遇到的难点解决的途径
不会自定义异常网络搜索并学习
不会记录日志,不会日志类的配置文件与使用网络搜索并学习

5 实验过程中收获的经验、教训、感想

5.1 实验过程中收获的经验和教训

在编程过程中要充分考虑程序的健壮性,针对各种异常情况进行测试,并且要考虑添加异常来帮助判断bug的位置,便于调试和维护使用。

5.2 针对以下方面的感受

  1. 健壮性和正确性,二者对编程中程序员的思路有什么不同的影响?
  2. 为了应对1%可能出现的错误或异常,需要增加很多行的代码,这是否划算?(考虑这个反例:民航飞机上为何不安装降落伞?)
  3. “让自己的程序能应对更多的异常情况”和“让客户端/程序的用户承担确保正确性的职责”,二者有什么差异?你在哪些编程场景下会考虑遵循前者、在哪些场景下考虑遵循后者?
  4. 过分谨慎的“防御”(excessively defensive)真的有必要吗?如果你在完成Lab5的时候发现Lab5追求的是I/O大文件时的性能(时间/空间),你是否会回过头来修改你在Lab3和本实验里所做的各类defensive措施?如何在二者之间取得平衡?
  5. 通过调试发现并定位错误,你自己的编程经历中有总结出一些有效的方法吗?请分享之。Assertion和log技术是否会帮助你更有效的定位错误?
    Assertion的确有助于定位错误位置,可以帮助进行debug。
  6. 怎么才是“充分的测试”?代码覆盖度100%是否就意味着100%充分的测试?
    即使覆盖率达到100%也不意味着充分测试,需要对各种可能的情况进行测试,否则只是简单地运行而已。
  7. Debug一个错误的程序,有乐趣吗?
    没有乐趣,除非是自己写的程序。
  8. 关于本实验的工作量、难度、deadline。
    如果lab3已经考虑到相关内容,则几乎不需要增加很多工作量,整体来看工作量适中,难度适中deadline适中。
  9. 到目前为止你对《软件构造》课程的评价和建议。
    极度占用其他科目时间。
  10. 期末考试临近,你对占成绩60%的闭卷考试有什么期望或建议?//请严肃的提出,杜绝开玩笑,教师会认真考虑你们的建议。
    一定不要考手写代码。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值