软件构造实验四报告

目录

 

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 和防御式编程

⚫ 日志

⚫ 调试技术

⚫ 黑盒测试及代码覆盖度

 

  1. 实验环境配置

在Eclipse MarketPlace中下载安装SpotBugs

在这里给出你的GitHub Lab4仓库的URL地址(Lab4-学号)。

https://github.com/ComputerScienceHIT/Lab4-1170300724

  1. 实验过程

请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明

    1. Error and Exception Handling

针对三个场景写了三个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("性别必须是MF");

            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;

    }

 

    1. Assertion and Defensive Programming
      1. checkRep()检查invariants

利用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到中心点的逻辑距离即为其轨道的序号

 

      1. Assertion保障pre-/post-condition

没有增加断言,大部分地方加了checkRep来替代

    1. Logging
      1. 写日志

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);

 

      1. 日志查询

按时间查询

    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;

还建立了beforeafter方法

就是比较一下时间的大小

 

按关键字查询

    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;

    }

关键字匹配就行

    1. Testing for Robustness and Correctness
      1. Testing strategy

根据想到的错误,写几个测试

      1. 测试用例设计

写几个错误的测试用例进行测试

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

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

因为有一些原本就写的测试代码,所以运行不到,注释掉之后覆盖率1

    1. SpotBugs tool

修改了一个判断条件的bug,就是说if判断里面,有多余的if完全没有必要。

    1. Debugging
      1. 理解待调试程序的代码思想

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,输出人名。就可满足找到此时得票最多的人(如果相同则选择最近得票的人)的要求。

 

      1. 发现并定位错误的过程

①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%了.

      1. 如何修正错误

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两个二分法都需要加

      1. 结果

  1. 实验进度记录

请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。

每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。

不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。

日期

时间段

计划任务

实际完成情况

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

按时完成(遇到困难)

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

遇到的难点

解决途径

 

在安装spotbug遇到了很大困难,eclipse报错

 

百度了错误代码,但是不知道是不是好使,总感觉它没有检测我的实际bug

  1. 实验过程中收获的经验、教训、感想
    1. 实验过程中收获的经验和教训
    2. 针对以下方面的感受
  1. 健壮性和正确性,二者对编程中程序员的思路有什么不同的影响?

 

健壮性

系统在不正常输入或不正常外部环境下仍能够表现正常的程度

处理未期望的行为和错误终止即使终止执行,也要准确/无歧义的向用户展示全面的错误信息,错误信息有助于进行debug

封闭实现细节,限定用户的恶意行为,考虑极端情况,没有不可能

正确性

程序按照spec加以执行的能力,是最重要的质量指标!

永不给用户错误的结果; 让开发者变得更容易:用户输入错误,直接结束,正确性倾向于直接报错,健壮性则倾向于容错

对外的接口,倾向于健壮性;对内的实现,倾向于正确性。

 

  1. 为了应对1%可能出现的错误或异常,需要增加很多行的代码,这是否划算?(考虑这个反例:民航飞机上为何不安装降落伞?)

 

主要要看我们对这个程序的容忍度到底在哪里,就像飞机降落伞,这个错误出现概率极低,但是处理成本极高,就不需要处理了,但是对于一些容忍度极低的程序,比如核电站,哪怕是0.1%的错误也要进行修复

 

  1. “让自己的程序能应对更多的异常情况”和“让客户端/程序的用户承担确保正确性的职责”,二者有什么差异?你在哪些编程场景下会考虑遵循前者、在哪些场景下考虑遵循后者?

 

前者是说,用户输入的不对也能给出正确的反馈,后者是说用户输入的永远是对的,没有进行错误反馈。我觉得这个要区分程序的具体应用环境,因为在输入的时候给出足够的提示,用户输入错误的概率就会降低,而且代码量很低,所以我倾向于给出输入提示,而不是处理各种错误输入

 

  1. 过分谨慎的“防御”(excessively defensive)真的有必要吗?如果你在完成Lab5的时候发现Lab5追求的是I/O大文件时的性能(时间/空间),你是否会回过头来修改你在Lab3和本实验里所做的各类defensive措施?如何在二者之间取得平衡?

 

我觉得可以,很多防御其实是极不可能触发的,不需要重复检测,因为也没有人攻击这个程序…

 

  1. 通过调试发现并定位错误,你自己的编程经历中有总结出一些有效的方法吗?请分享之。Assertion和log技术是否会帮助你更有效的定位错误?

 

调试主要使用junit来进行定位的,利用log技术还要再写好多代码,不如应用现成的一些工具,增加效率。

 

  1. 怎么才是“充分的测试”?代码覆盖度100%是否就意味着100%充分的测试?

 

不是,充分测试应该是考虑了所有的错误可能性,覆盖率100%只是说对于写好的错误都测试到了,还有可能有没有考虑到的错误

 

  1. Debug一个错误的程序,有乐趣吗?

 

有一定的乐趣吧

 

  1. 关于本实验的工作量、难度、deadline。

 

都还可以

 

  1. 到目前为止你对《软件构造》课程的评价和建议。

 

感觉碰到的问题都有点独立

 

  1. 期末考试临近,你对占成绩60%的闭卷考试有什么期望或建议?//请严肃的提出,杜绝开玩笑,教师会认真考虑你们的建议。

挺好的,因为我觉得实验可能会出现各种各样扣分的情况,60%的考试可以补救大部分实验分,避免出现完全及不了格的情况,不过希望考试难度能低一些,因为期末好几科一起考,复习量太大了

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值