【6.10】软件构造Lab4

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

3.1 Error and Exception Handling
3.1.1 处理输入文本中的三类错误

  1. DataPatternException 数据常量错误导致没有匹配到三个元素
    抛出异常方法:在正则表达式匹配时,若没有匹配到则抛出该错误。
  2. EntryNumberFormatException 计划项编号不规范
    抛出异常方法:检查是否符合“前两个字符为大写字母,后2-4个字符为数字”。
  3. SameAirportException 起飞和到达地点相同
    抛出异常方法:对比两个机场字符串是否相等。
  4. TimeOrderException 起飞时间在到达时间之后或两个时间相同
    抛出异常方法:先用try结构进行判断,否则抛出DateTimeParseException;然后在finally中使用LocalDateTime.isBefore()方法比较。
  5. PlaneNumberFormatException 飞机编号不规范
    抛出异常方法:检查字符串长度以及首字母、后4位数字。
  6. HugeTimeGapException 起飞时间和到达时间超过一天
    抛出异常方法:判断起飞时间晚一天后是否比到达时间晚。
  7. PlaneInconsistentInfoException 不同航班计划项中有相同的航班飞机
    抛出异常方法:遍历航班飞机,若飞机号相同,但航班内容不相同,则报错。

3.1.2 处理客户端操作时产生的异常
ResourceSharedException 在为某计划项分配某资源的时候,分配后导致与已有的其他计划项产资源独占冲突问题。
抛出异常方法:遍历计划项,遍历查找分配资源。捕获到异常后将“允许删除标签”设置为false。

3.2 Assertion and Defensive Programming
3.2.1 checkRep()检查rep invariants
1.TimeSlot

【6.10】软件构造Lab4
2.Location
【6.10】软件构造Lab4
3.PlanningEntry
【6.10】软件构造Lab4
4.Resource
【6.10】软件构造Lab4
3.2.2 Assertion/异常机制来保障pre-/post-condition
1.EntryState
在修改状态时,前置条件和后置条件均要求当前状态合法。除了高铁计划项,其它两类的状态不能为blocked。

  1. PlanningEntry
    分配资源时,前置条件为:被分配的资源不能为空。
    (以ActivityCalendar为例)
    【6.10】软件构造Lab4更改状态时,后置条件为:更改后的状态不能为空且为某一合法状态。
    (以CommonPlanningEntry.start()为例)

【6.10】软件构造Lab4需要注意的是,PlanningEntry中的TrainSchedule有 “取第i个车厢”操作,对于i的前置条件为:不能查询第1站的到达时间并且不能查询最后一个站的出发时间。

  1. PlanningEntryCollection
    在计划项集合类中,关联到计划项编号的操作的前置条件:要求计划项编号参数不能为空;所有有关查询操作的参数不能为空。

3.2.3 你的代码的防御式策略概述
·客户端和API之间是根据用户输入参数进行控制的,因此要保证用户输入的正确性——API在起始阶段对用户输入进行检查。
·API操作完成后,在客户端或API中需要对结果进行正确性的大致检查,避免明显错误,否则可能引入隐式错误。
·API的操作会影响ADT,因此要检查参数是否正确。
·在修改ADT的内容之后,要确认修改后的ADT符合RI。可以调用ADT私有方法checkRep()进行校验。

3.3 Logging
3.3.1 异常处理的日志功能
首先调用 java.util.logging库,配置日志的输出语言为英文
然后设置日志显示信息的最低级别:INFO,WARNING,SEVERE。
对三个 logger 配置加入相应的文件管理,使得日志可以写入到文件中
对文件管理handler配置SimpleFormatter文件写入格式
记录SEVERE级别日志
记录WARNING 级别日志

3.3.2 应用层操作的日志功能
在应用中使用的任何功能都应在调用之后马上生成 INFO信息且在功能成功实现并结束后生成 INFO 成功信息。

3.3.3 日志查询功能
先处理字符串使其能够将所有的日志显示出来。复用Board功能,将 JTable可视化。
最后通过GUI显示日志,按钮设计在visualization内的“Show Logs”。

3.4 Testing for Robustness and Correctness
3.4.1 Testing strategy
使用等价类和边界值的测试思想为各 ADT 添加 测试策略。

3.4.2 测试用例设计
测试用例保存在data文件中。当测试各种Exception时至少只需要一组数据。

3.5 SpotBugs tool
·在修改3.6所用到的源代码时,spot到字符串判断相等用了==.
·对于Calendar时间的判断,源代码使用了>和<
·源代码中Collection.sort()函数有问题

3.6 Debugging
3.6.1 EventManager程序
·理解待调试程序的代码思想: 给定许多个事件的起止时间,找出某一时刻同时进行的事件的最大过数量

·发现并定位错误的程:属性是类型为TreeMap的变量temp,temp的键存储的是事件在时间轴上的时间,值对应的是在该时间点上面同时发生的时间数量的最大值。所以只需要循环temp的所有的值,找出最大的值即可。

·你如何修正错误:首先进行输入判断,将输入限制在合法范围内。然后将时间节点map中键值对重写,以满足如上要求即可。

3.6.2 LowestPrice程序
·理解待调试程序的代码思想:给定优惠并给出所需要的各个物品的数量,找出需要购买这些物品的最低价格。
·发现并定位错误的过程:输入要限制在合法范围内
·你如何修正错误:遍历每一种组合方式,对所有可能的情况进行计算,最小的那个就是要的结果。然后将递归语句重写。

3.6.3 FlightClient/Flight/Plane程序
·理解待调试程序的代码思想:给定一组航班和一组飞机,问能否在飞机不冲突的情况下给每个航班都安排一架飞机

·发现错误的过程并修改:
①while循环为永真,可能出现死循环。
添加界限,如果循环10000次仍无法找到有效解决办法,则说明无法分配。
②对于Calendar时间的判断,源代码使用了>和<。
使用before和after函数。
③源代码中用==比较字符串。
用equals函数代替。
④源代码中Collection.sort()函数有问题。
重写Comparator.Comparing函数。

实验过程中收获的经验和教训:
(1) 健壮性和正确性,二者对编程中程序员的思路有什么不同的影响?
健壮性要求程序员始终假定客户端会对程序做各种各样的破坏,需要将程序的关键部分保护好。正确性则要求程序员始终保证程序的逻辑不出错,不忽略任何一种可能的情况。
(2) 为了应对1%可能出现的错误或异常,需要增加很多行的代码,这是否划算?(考虑这个反例:民航飞机上为何不安装降落伞?)
如果这1%的错误或异常,导致整个程序崩溃,那么其造成的损失不是加多少行代码的价值所能衡量的。我们要保持敏捷的思维,就算有只有1%的可能也要尽量避免。
(3) “让自己的程序能应对更多的异常情况”和“让客户端/程序的用户承担确保正确性的职责”,二者有什么差异?你在哪些编程场景下会考虑遵循前者、在哪些场景下考虑遵循后者?
前者针对程序员,后者是针对用户。在防御式编程下需要更多的考虑前者;而在追求可维护性编程、追求高聚合、低耦合编程的条件下,需要更多的考虑后者。
(4) 过分谨慎的“防御”(excessively defensive)真的有必要吗?你如何看待过分防御所带来的性能损耗?如何在二者之间取得平衡?
防御是有必要的,如果同时还要求程序追求I/O大文件的性能,那么就需要去掉一些对于小概率错误的防御措施,以此为代价让程序在多数情况下都能保持高效、良好的运行状态。
(5) 通过调试发现并定位错误,你自己的编程经历中有总结出一些有效的方法吗?请分享之。Assertion和log技术是否会帮助你更有效的定位错误?
最原始的方法就是在需要调试错误的地方打印出相关的参数,进而根据哪些程序执行了,哪些程序没有执行来进行debug。
在程序较为复杂的时候,设置断点更为有效。
(6) 怎么才是“充分的测试”?代码覆盖度100%是否就意味着100%充分的测试?
充分的测试是指对代码的核心部分进行了全面的测试,并且使用了各种各样、不同情况的参数。