Lab4

3.1 Error and Exception Handling
这一节主要是要为各种可能出现的错误情况设计exception类,并在原来的工程中加入相应的代码。以下是各种具体的情况。所有的exception的handle类均在包exceptions中。

  1. 不符合语法规则的输入文件
    ⚫ IllegalScientificNotationException
    这个类用于提示出现非法的科学记数法,即10000以下的数使用了科学记数法,或10000以上的数未使用科学记数法。在Tools包中的isPhysicalNumLegal函数中使用这个exception用于判断是否合理。
    ⚫ FractionReservationException
    由观察可知,stellar系统的中环绕星体的数字属性,若用科学记数法表示,则应保留两位小数。因此我们定义此exception,并在Tools包中的isPhysicalNumLegal中判断该项是否合法。
    ⚫ FriendInfoLegalException
    在SocialNetworkCircle中,每个朋友的信息应该是合法的,即性别应当是M或F的一种,年龄应当为正整数。因此定义以上exception,并在SocialNetworkCircle的readfromfile函数,以及application中的添加好友的功能中加入该exception。
    ⚫ IlleagalElementNameException
    在AtomStructure中,元素的输入应当是合法的。这种检查措施在实验3中已有提及,因此设计以上exception,并在centralObject包中的AtomCentralObject类的初始化方法中加入该exception。
    ⚫ IllegalFriendNameException
    在SocialNetworkCircle中,每个人的名字是有一定规则的,即第一个字母为大写,并且后面姓氏的首字母也应当大写。我们在Tools中实现isFriendNameLegal函数以实现这种合法性的检验,并在使用SocialNetworkCircle的readfromfile函数从文件录入和application录入新的人时使用。
    ⚫ InvalidPropertyException
    在三个系统的文件录入过程中,均会根据::=将信息分开后前一个字串的内容决定后面内容的归属。因此我们定义以上的exception来handle前面的property非法的情况。在三个系统的readfromfile函数中均使用了这种exception。
    ⚫ InvalidTrackNumException
    在SocialNetworkCircle和AtomStructure中在application中添加轨道时应当验证轨道标号为正整数,因此建立以上exception并在添加轨道时验证,若不是正整数则抛出该异常。
    ⚫ NoSplitSymbolException
    对于三个系统,在其readFronFiles函数中,在每一次parse前判断字串中是否有分隔符::=,若没有则抛出以上异常。
    ⚫ StellarAngleException
    对于系统StellarSystem,文件读入和输入的角度值应当在0到360之间。因此,在其readFromFile函数中,在每一个行星的角度值处应当检查是否符合此标准,否则抛出该异常。同样,在application中添加新的行星时也应当做此检测。
    ⚫ StellarDirectionException
    对于系统StellarSystem,文件读入和输入的公转方向应当为“CW”和“CCW”之中的一个。因此,在readFromFile函数和application函数中均应当检查方向的合法性,若不合法则应抛出异常。
    ⚫ StellarNameUpperLowerCaseException
    对于系统StellarSystem,文件读入和输入的名字应当是第一个字母大写其余字母小写的。因此,在其readFromFile函数和application中添加新卫星函数中使用Tools包的isStellarStringLegal函数判断输入是否合法,若不合法则抛出该异常。

  2. 标签重复的元素
    ⚫ StellarHasSameObjectException
    对于系统StellarSystem,在其行星中不应当有重名的行星。因此,使用Tools包中的hasStellarSameObject函数,判断系统中是否有重名的星体,若有则抛出该异常。
    ⚫ SelfRelationException
    对于系统SocialNetworkCircle,需判断是否出现了指向自己的的关系。因此,在readfromFile函数和application中加入新的关系时,需判断两个人名字是否相同,若相同则抛出异常。
    ⚫ DefineMultipleRelationException
    对于系统SocialNetworkCircle,需判断是否出现对同一个关系多次赋值的情况。根据该关系存储的形式,只需在每次输入新的权值时判断该好友是否已经存在于当前好友的权重map中,具体如physicalObject包中的SocialNetworkPhysicalObject类中的setNewWeight方法中设置权重时,如果发现该记录已经存在,则抛出异常。

  3. 文件元素的错误依赖关系
    ⚫ AtomTrackNumMatchElectronException
    对于系统AtomStructure,需要判断在从文件输入时轨道条数是否和NumberOfElectron中表示的轨道数一致。因此,在其readFromFile函数中读入NumberOfElectron一行时判断分裂之后的个数是否与之前NumberOfTracks的个数一致,如果不相等则抛出异常。
    ⚫ IllegalFriendSocialTieException
    对于系统SocialNetworkCircle,需要判断文件中是否在socialTie中是否使用了之前friendset中未定义的朋友。因此,在处理时按以下代码进行处理。定义一个boolean型变量:
    boolean legalName = false;
    if (parseStr[0].equals(parseStr[1])) throw new SelfRelationException(parseStr[1]);
    if (parseStr[0].equals(getCentralPoint().getName())) {
    for (SocialNetworkPhysicalObject spo:frSet) {
    if (spo.getName().equals(parseStr[1])) {
    legalName = true;
    addToRelationSet(spo);
    centralRelationWeight.put(spo,Double.parseDouble(parseStr[2]));
    }
    }
    if (!legalName) {
    throw new IllegalFriendSocialTieException(parseStr[1]);
    }
    若出现未出现过的名字,则抛出该异常。
    同样,在application中,添加关系时,若出现没有的名字,则抛出该异常。主要代码:
    if (!set1&&!s.getCentralPoint().getName().equals(name1)) {
    throw new IllegalFriendSocialTieException(name1);
    }
    if (!set2&&!s.getCentralPoint().getName().equals(name2)) {
    throw new IllegalFriendSocialTieException(name2);
    }
    其中set1和set2是表示是否在physicalObject中找到的布尔型变量。若在环绕物体中未找到且不是中心物体,则抛出异常。
    3.2 Assertion and Defensive Programming
    3.2.1 checkRep()检查invariants

  4. ConcreteCircularOrbit
    在在三个类的父类ConcreteCircularOrbit中设计函数checkRep()来测试其表示不变量。为了方便直接在其三个子类中同时复制本小结中提到的检测项。首先测试轨道中没有相同半径的,主要方法是将每一个轨道的半径(string类型)加入一个字符串集合中,利用集合的相同对象只算一次的特点,比较轨道数和集合大小,判断是否有相同半径的轨道。测试朋友中不存在完全相同的两个人,只需比较两个非同一对象是否各参数均相等即可。每个关系仅出现一次,由于orbitRelationSetMap的结构保证了每个相同关系只会被记录一次,故此不变量可以不予测试。

  5. SocialNetworkCircle
    在SocialNetworkCircle中设计函数checkRep(),除了父类中的测试项目之外,图中每个人到中心的距离与其所处轨道数相同。其基本思想与实验三的思想基本一样。并在函数buildGraph的最后加上该函数用于测试该系统是否符合要求。

  6. AtomStructure
    在AtomStructure中,只需考虑该系统中不应存在半径相同的轨道。主要方法与SocialNetworkCircle类似。在readFromFile函数和application中添加轨道处均使用。

  7. StellarSystem
    除了父类的几个表示不变量的测试之外还需要测试该轨道系统每个轨道上均只能有一个物体。方法是直接获得每个轨道上的物体数量并判断其是否为1。在readFromFile和application中添加轨道和物体相关代码结束时也应当检测。
    3.2.2 Assertion保障pre-/post-condition

  8. ConcreteCircularOrbit
    在函数addToRelationSet,removeFromRelationSet,addTrack,deleteTrack,addToCentralPoint,addToTrack,deleteFromTrack,addRelationCentralTrack,addRelationTrackTrack,removeRelationTrackTrack中检测输入的参数是否为null,如果是则报错。除此之外,在函数getCentralPoint,getCentralOrbitRelation,getTrack,getOrbitObject,getOrbitOrbitRelation中检查返回值是否为空,若为空则报错。在函数addTrack中,在已经存在的轨道中若有半径和新轨道相等的,则assert一个错误。

  9. StellarSystem
    ⚫ 对于move方法在其调用的StellarPhysicalObject类的setAngle函数中检测输入的角度在0到360度之间。在move方法中检测setAngle函数返回值是否是true。
    ⚫ 对于readFromFile函数,在application类中调用它时,需要检查其返回值是否为true,不是则报错。
    ⚫ 对于getPositionAtTime函数,需检测输入值time是否大于等于0。检测输出值的map的size是否与行星数一样,因为每个轨道上只有一个星体,故检测是否与轨道数一样即可。

  10. AtomStructure
    ⚫ 对于readFromFile函数,同样是在application函数中调用时需检测返回值是否为true。
    ⚫ 对于函数transit,需检测输入的track是否在轨道中。而执行deleteFromTrack函数和addToTrack函数后需判断其返回值是否为true,否则报错。

  11. SocialNetworkCircle
    ⚫ 对于函数cloneFriendSet,需要在其返回时检测是否所有的朋友都已被复制,因此检测返回集合与friendsSet大小是否相同。
    ⚫ 对于函数modifyCentralWeight,需判断输入的权值是否大于0,以及输入的action是否有效,若输入不为add和delete的一种,则报错。
    ⚫ 对于函数modifyPhysicalWeight,需判断输入的value是否大于0,还需判断输入的action是否有效,即是否为add或delete的一种。
    ⚫ 对于函数setCentralRelationWeight,需判断weight是否大于0,以及输入的spo不为空。
    ⚫ 对于函数readFromFile,需在application中调用该方法时检测其是否返回ture值。
    ⚫ 对于函数buildGraph函数,需判断输入的集合大小是否与friendsSet大小相同。

  12. Tools
    ⚫ 对于函数sortTrack,对于输入的track的集合其大小必须至少为2,否则排序没有意义。对于返回的sortedTrack,其大小必须和输入的集合相等。
    ⚫ 对于函数processRadius,输入值不能为null。其次需判断返回值是否大于0,若不大于0则报错。
    ⚫ 对于函数isStellarStringLegal,其输入值不能为null。
    ⚫ 对于函数isPhysicalNumLegal,其输入值不能为null。
    ⚫ 对于函数isPositiveInteger,其输入值不能为null,或者是空串,或者是包含负号,否则报错。
    ⚫ 对于函数isFriendNameLegal,输入值name的长度至少应为3个字符,否则一定不合法,直接报错。
    ⚫ 对于函数isFriendSexLegal,输入字符串不能为null。
    ⚫ 对于函数hasStellarSameObject,输入的系统s不为null。

  13. CircularOrbitAPIs
    ⚫ 对于函数getObjectDistributionEntropy,输入的系统应当不为null,而输出的entropy值应当大于0。
    ⚫ 对于函数getLogicalDistance,输入的系统和两个物体均不能为null。
    ⚫ 对于函数getDifference,输入的两个物体均不为null。
    ⚫ 对于函数getPhysicalDistanceBetweenPlanets,输入的系统和两个环绕物体均不为null。并且输入的两个物体不能为同一个物体。对于返回值dis,其值必须大于0。
    ⚫ 对于函数getPhysicalDistanceBetweenStarPlanet,仍有系统和输入物体不为null。如果输入物体不存在于该系统中,则报错。
    ⚫ 对于函数getTrackOfItem,输入的系统和物体不能为null。如果输入的物体不在输入的系统中则报错。
    3.3 Logging
    为了记录和查询日志,我们在APIs包中新添加Logging类,并定义方法logExecution用于处理要输入的字符串,将日志输入到文件myLogging.log中,并将level设置为ALL。
    3.3.1 写日志

  14. Application
    为了记录在程序执行的过程中的信息,我们在main函数中新建一个Logging 对象,并传入各个具体实施方法中。

⚫ 首先考虑对异常/错误的记录。对本类中所有抛出异常的地方,在抛出之前使用方法logExecution和Tools中定义的方法genExceptionString(其效果是根据输入信息构造所需的exception字串并返回)产生日志。每一次产生均需要输入以下信息:异常的名字,异常所在的类名,异常所在的方法名,和异常发生的具体原因,以及处理方法(主要是catch语句块中的内容)。下面举一个例子。比如说我们需要抛出一个IllegalFriendSocialTieException,那么按如下方法记录日志:

log.logExecution(Tools.genExceptionString(“IllegalFriendSocialTieException”,
Thread.currentThread().getStackTrace()[1].getClassName(),
Thread.currentThread().getStackTrace()[1].getMethodName(),
“Illegal Friend Social Tie”+name1,“printStackTrace”));

其中使用Thread中的方法获得静态方法的类名和方法名·。
除此之外,对于出现assert错误的地方,使用类似的方法进行日志记录,举例如下。假设readFromFile函数返回false,那么会报错。而在assert之前添加语句:

if (!suc) {
				log.logExecution(Tools.genExceptionString("Assertion error",
					Thread.currentThread().getStackTrace()[1].getClassName(),
					Thread.currentThread().getStackTrace()[1].getMethodName(),
					"Read from file failed.",
					"Stop the program."));
			}

这样就记录了一个assert错误信息。

⚫ 其次考虑对多轨道物体的所有操作。对于每一个操作,若其操作成功,则均在适当的位置对其进行记录。下面举一个例子。比如说SocialNetworkCircle的操作6,要计算一个朋友所在的轨道。如果成功找到则使用如下语句进行记录:
log.logExecution(Tools.genExecutionString(“GetTrackRadiusOfItem”,
"Track radius is "+re,“SocialNetwork”));
其中方法genExecutionString是Tools中的一个用于保准化操作信息的方法,要求每一个记录提供操作名称,操作具体信息和操作所在的系统。

  1. CircularOrbitAPIs
    针对该类中的每一个静态方法,对于其中所有加有assert的地方均进行判断,若出现使得assert内容为false的情况,则在log中进行记录。举一个例子:在函数getObjectDistributionEntropy中我们设置assert entropy>0;。而在其之前我们添加如下语句:
    if (entropy<=0) {
    log.logExecution(Tools.genExceptionString(“NegativeEntropyFault”,
    Thread.currentThread().getStackTrace()[1].getClassName(),
    Thread.currentThread().getStackTrace()[1].getMethodName(),
    “Illegal entropy:”+entropy,
    “Stop the program.”));
    }
    这样若出现错误则可以进行记录。
  2. Tools
    其添加日志记录的方法与CircularOrbitAPIs几乎一样,此处不再赘述。

3.3.2 日志查询

  1. 我们在APIs包中建立LogSearcher类,用于读取文件中的log信息。我们在初始化方法中接受文件名并从中读取xml格式的日志信息。通过一系列的解析过程(具体在初始化方法中),我们将每一条记录的名字,记录的时间,其所属的类的名字,其所属的方法的名字,其所属的系统的名字(如果有的话,因为错误信息不记录所属系统类型,而操作信息需要记录所属系统),具体的信息,以及处理结果(如果有的话,错误信息有处理结果,而操作记录信息没有处理结果)记录在另一个类Log中,具体的存储过程有内部方法createLog完成,之后将所有的Log存储在一个集合中。
  2. 值得提及的是,我们重写Log类的toString方法,具体根据记录的类型(操作还是错误,具体判断方法是result记录是否为null,若是则为操作,否则为错误),将其中记录的信息以字符串的形式返回,以方便显示。
  3. 接下来考虑返回日志信息的方法。首先我们使用getFullLog方法来获得所有的日志信息。返回的是字符串的集合,每个字符串是一个日志记录的toString返回的结果。
  4. 然后考虑返回属于特定类的记录。建立方法getCertainClassLog,将每一个记录的className和输入的类名进行比较,若相同则加入返回的集合中。
  5. 考虑返回属于特定方法的记录。建立getCertainMethodLog方法,接受记录所在的类和方法(因为不同的类可能有同名的方法),并返回符合的记录。
  6. 考虑返回特定时间段的记录。建立方法getCertainTimeLog并接受一个开始时间和一个结束时间作为输入,并建立函数compareTime用于比较时间的大小。在compareTime函数中,一次比较年份,月份,天,小时,分,秒级别的时间记录,若这些结果都相同,那么两个时间则相同。在compareTime函数中使用另一个内部方法parseTime用于将输入的时间字符串分为各个时间量级,并使用一个map存储并返回。
  7. 在application中添加有关日志查询的功能。为了减少代码改动,我们将log的功能号定义为0.并作相应的调用。
  8. 具体功能展示如下。我们在菜单中选择第0项,进入相关的菜单:

我们先选择第一项,返回当前存在的所有log记录。由于日志记录较多,此处只截取了一部分结果:

之后我们再选择第二个功能,并输入相应的时间段,得到如下结果,可以看到,此时只筛选了时间段之间的结果:

之后再选择第三个功能,根据输入的类名进行筛选,我们做如下展示。这里需要说明的是,我们将类名的输入均设置为大小写不敏感的:

最后展示特定方法名的筛选,由于记录较多这里仅截取一部分:

3.4 Testing for Robustness and Correctness
3.4.1 Testing strategy

  1. 我们选择主要的包中的方法进行测试。在包circularOrbit中,我们选择ConcreteCircularOrbit和它的三个子类进行测试。首先,利用我们在第一节中在“异常测试输入文件“中为每一种可能的异常设计的输入文件,我们测试程序对于输入的健壮性。这些健壮性的测试放置在每一个子类的测试文件的readFromFilesTest函数中。除此之外,还测试每个子类和父类中的方法。

  2. 在包APIs中,我们测试CircularOrbitAPIs和Tools两个类中的所有方法。对于所有有返回值的方法,我们通过验证返回值是否与输入值相对应。对于所有没有返回值的方法,我们通过间接测试(比如说测试是否抛出相对应的异常),来判断方法是否正确。

  3. 在包physicalObject中,我们选择测试SocialNetworkPhysicalObject中的setNewWeightTest方法。

  4. 由于其它方法非常简单,在这里不做测试。
    3.4.2 测试用例设计
    我们使用第一节中建立的异常测试输入文件用于测试,分别在三个具体类的readFromFilesTest函数中进行测试。其余的测试用例根据具体的方法进行构建。
    3.4.3 测试运行结果与EclEmma覆盖度报告

  5. 测试运行结果:

  6. 以下是要测试的几个包的覆盖度:

可以看到CircularOrbitAPIs中覆盖度并不高,这是因为其中有大量日志记录assertion的错误的原因。由于这种assert的错误无法使用测试代码测试,因此无法覆盖到。

3.5 SpotBugs tool

  1. SS_SHOULD_BE_STATIC:在变量中一直不变的的变量应该加上static关键字。
  2. SIC_INNER_SHOULD_BE_STATIC:未改变的内部类应当定义为static final的。
  3. SBSC_USE_STRINGBUFFER_CONCATENATION:在将字符串进行拼接时,应尽量使用StringBuffer而不是直接相加。
  4. DM_STRING_CTOR:在确定返回的字符串不会被修改的情况下尽量不要使用String构造方法进行defensive copying。
  5. DM_DEFAULT_ENCODING:找到一个方法的调用,它将执行一个字节到字符串(或字符串到字节)的转换,并假定默认的平台编码是合适的。这会导致应用程序行为在不同平台之间变化。使用替代API InputStreamReader并明确指定字符集名称或字符集对象。
  6. NP_DEREFERENCE_OF_READLINE_VALUE:在使用readLine方法进行读取时,应当判断读到的内容是否为空。
  7. DM_BOXED_PRIMITIVE_FOR_PARSING:应当使用封装/反封装来解析一个基本类型。比如说使用了Integer类型获得一个值,应当使用intValue将其转化为int值。
  8. REC_CATCH_EXCEPTION:在捕获异常时最好不要有直接捕获Exception的情况,
  9. NM_METHOD_NAMING_CONVENTION:应当将方法名称首字母小写。
  10. BX_BOXING_IMMEDIATELY_UNBOXED:装箱之后又迫使编译器自己取消装箱。这时应该用intValue等函数自己取消装箱。
  11. DM_NUMBER_CTOR:方法调用低效的数字构造方法,应使用静态valueOf代替。
  12. UWF_UNWRITTEN_FIELD:某个字段从未被赋值过。这是由于写代码时的疏忽,需在初始化方法中对该字段进行初始化。
  13. URF_UNREAD_FIELD:出现从未被使用的属性,可以直接删除。这是由于编程时的疏忽。
  14. EC_NULL_ARG:调用了equals(null)方法,这是由于对null的本质理解不透彻造成的。应当改为==null。
  15. BX_BOXING_IMMEDIATELY_UNBOXED_TO_PERFORM_COERCION:封装了基本类型的值,然后为了进行基本类型的强制转换,又再次反封装。应当手动进行反封装。
  16. DM_STRING_VOID_CTOR:生成空字符串时使用了new String()构造函数。由于直接赋值空字符串「""」的效率较高,应当直接赋值空字符串
    3.6 Debugging
    3.6.1 理解待调试程序的代码思想
  17. FindMedianSortedArrays:用于寻找两个数组的中位数
    首先使A成为最短的数组,多次循环定位到A,B两个数组最靠近中位数的两个数字,根据数组总长度为奇数偶数,输出最后结果。
  18. RemoveComments:用于删除输入的“程序”中的注释。
    遍历每行“代码”的每个字符,如果读到“\”,该行后续字符完全跳过
    如果读到“/”,则忽略下面的字符直到读到“/”
  19. TopVotedCandidate:寻找输入时间前的获得最高票的候选人
    二分法,先查找最靠近要求查找时间最近的前面的时间结点,然后取该时间结点最后获取票的人输出。
    3.6.2 发现并定位错误的过程
  20. 将各变量的变化输出到控制台,再进行手动计算进行比较,定位错误发生的位置。
    3.6.3 如何修正错误
    FindMedianSortedArrays:
    1、int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;// 除法向下取整 因此应该加1
    2、if ((m + n) % 2 != 0) {
    // 数组长度为奇数时 取左面最大值即最中间的数 更改判断方式 防止负数的情况

RemoveComments:
1、List ans = new ArrayList<>();// 初始化改成ArrayList
2、if (!inBlock && i + 1 < line.length() && chars[i] == ‘/’ && chars[i + 1] == ‘’) {
// 去掉i+1和line.length()比较的等号 防止越界 增加i++保证去掉未去掉的/
else if (inBlock && i + 1 < line.length() && chars[i] == '
’ && chars[i + 1] == ‘/’) {
// 去掉i+1和line.length()比较的等号,防止越界增加i++保证去掉未去掉的/
else if (line.lastIndexOf("//") != -1 && !inBlock && i + 1 < line.length() && chars[i] == ‘/’ && chars[i + 1] == ‘/’) {
// 添加一种判断情况 保证读到“//”时 此行剩余部分直接越过

TopVotedCandidate:
1、 A = new ArrayList<>();// 添加<>
2、 Map<Integer, Integer> count = new HashMap<>();// 添加<>
3、 int c = count.getOrDefault(p, 0);// 计数从0开始
4、 count.put(p, c + 1);// c改为c+1 每次对某人计入一票 则加1
5、 int i = lo - 1;// 使i=lo-1 列表从0开始
6、 if (A.get(i).get(mi).time <= t)// 取等于号
7、 lo = mi + 1;// 取lo=mi+1每次取中间数高一位
8、 int j = Math.max(lo - 1, 0);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值