HIT软构Lab2心得

2022年春季学期
计算学部《软件构造》课程

Lab 2实验报告
 

 

目录

1 实验目标概述··· 1

2 实验环境配置··· 1

3 实验过程··· 1

3.1 Poetic Walks· 1

3.1.1 Get the code and prepare Git repository· 1

3.1.2 Problem 1: Test Graph <String>· 1

3.1.3 Problem 2: Implement Graph <String>· 1

3.1.3.1 Implement ConcreteEdgesGraph· 2

3.1.3.2 Implement ConcreteVerticesGraph· 2

3.1.4 Problem 3: Implement generic Graph<L>· 2

3.1.4.1 Make the implementations generic· 2

3.1.4.2 Implement Graph.empty()· 2

3.1.5 Problem 4: Poetic walks· 2

3.1.5.1 Test GraphPoet· 2

3.1.5.2 Implement GraphPoet· 2

3.1.5.3 Graph poetry slam·· 2

3.1.6 使用Eclemma检查测试的代码覆盖度··· 2

3.1.7 Before you’re done· 2

3.2 Re-implement the Social Network in Lab1· 2

3.2.1 FriendshipGraph类··· 2

3.2.2 Person类··· 3

3.2.3 客户端main()· 3

3.2.4 测试用例··· 3

3.2.5 提交至Git仓库··· 3

4 实验进度记录··· 3

5 实验过程中遇到的困难与解决途径··· 3

6 实验过程中收获的经验、教训、感想··· 4

6.1 实验过程中收获的经验和教训(必答)··· 4

6.2 针对以下方面的感受(必答)··· 4

  1. 实验目标概述

本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象编程(OOP)技术实现ADT。具体来说:

  1. 针对给定的应用问题,从问题描述中识别所需的ADT;
  2. 设计ADT规约(pre-condition、post-condition)并评估规约的质量;
  3. 根据ADT的规约设计测试用例;
  4. ADT的泛型化;
  5. 根据规约设计ADT的多种不同的实现;针对每种实现,设计其表示(representation)、表示不变性(rep invariant)、抽象过程(abstraction function)
  6. 使用OOP实现ADT,并判定表示不变性是否违反、各实现是否存在表示泄露(rep exposure);
  7. 测试ADT的实现并评估测试的覆盖度;
  8. 使用ADT及其实现,为应用问题开发程序;
  9. 在测试代码中,能够写出testing strategy并据此设计测试用例。

       2.实验环境配置

简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。

特别是要记录配置过程中遇到的问题和困难,以及如何解决的。

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

3.实验过程

请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。

3.1Poetic Walks

在这里简要概述你对该任务的理解。

3.1.1Get the code and prepare Git repository

如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。

3.1.2Problem 1: Test Graph <String>

测试策略与对Graph.empty()静态方法的测试在GraphStaicTest.java中。由于该方法为静态方法,将只有一种实现方式,并且我们只需要运行一次测试。该部分测试已经给出,我们可以改变或添加内容,或不做改变。

在GarphInstanceTest.java中,对实例方法的测试使用emptyInstance()方法创建一个空图,并对Graph接口中的各个方法进行输入划分并测试。

对Graph接口中方法的测试策略大致可以阐述如下:

1) 将图划分为空图和非空图

2) 将图中用于测试的顶点分为两组,一组已经存在于图中,另一组不存在于图中。

3) 将图中用于测试的边根据端点在图中的存在性和边权的性质分组,根据端点存在性分为“两点存在”、“只有一点存在”和“两点不存在”三组;根据边权的性质将边分为“边权为0”和“边权为正整数”两组;

4) 测试方法的实现调用Graph接口提供的add(), remove(), set, sources, targets, vertices()等方法充分覆盖对输入集的划分。

3.1.3Problem 2: Implement Graph <String>

以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。

3.1.3 .1 Implement ConcreteEdgesGraph

  1. 实现Egde类

定义私有局部变量source,target和weight,并设计规约

根据规约设计checkRep函数检查三个变量的值是否合法

 

 

依次实现Edge类的方法:

 

ToString函数,用一种安全的方式打印边的信息

2.实现ConcreteEdgesGraph

 

定义私有变量,用Set存点,List存边并设计规约

 

构造性方法,只需定义一个空图即可

根据规约设计checkRep函数:

 

实现ConcreteEdgesGraph中的其他方法:

1) public boolean add(L vertex)

遍历vertices集合,查找是否存在与点vertex相同的点。若存在,则返回false;若不存在,则在vertices集合中添加点vertex,然后返回true;

2) public int set(L source, L target, int weight):

遍历edges集合,若存在对应端点的边,则根据weight的值进行修改权值/删除该边的操作,并返回原来边的权值;若不存在对应的边,则判断weight的值是否为0,权值不为0时,则添加该边至edges集合中,并将不存在于vertices集合中的点添加到vertices集合中,然后返回0;权值为0时不做任何操作,直接返回0。

3) public boolean remove(L vertex):

若vertices集合中不存在该点,则返回false。存在时,将edges集合中所有以vertex为源点/终点的边全部移除,将vertices集合中vertex删除,返回true。

4) public Set<L> vertices():

为了保证数据信息不泄露,需要返回一个由vertices集合复制的新集合。(防御式拷贝)

5) public Map<L, Integer> sources(L target):

所有终点为target的点的边的源点作为键名、权值作为键值添加到新生成的Map中,最后返回该Map。

6) public Map<L, Integer> targets(L source):

所有源点为source的点的边的终点作为键名、权值作为键值添加到新生成的Map中,最后返回该Map。

7) public String toString()

函数需要将图的所有信息打印出来,即所有的点与边的信息。故先将所有的点的名称打印出来,随后遍历edges集合,循环调用Edge类的toString方法即可。

​​​​​​​3.1.3.2 Implement ConcreteVerticesGraph

  1. 实现Egde类

Vertex中在HashMap中记录源点对应边的终点和权值,终点对应边的源点和权值。由于我们需要为每个Vertex变量定义特定的标识来区分不同的变量,因此增加L类型的变量name作为标记。

 

构造性方法:

 

根据规约设计checkRep函数检查合法性:

 

实现其他方法:

1)观察器:

 

2) public int setSources(L source, int weight)

权值为0:将这个点从map中删除,起到删除以该点为源点边的作用

权值不为0:将新的边的源点和权值键值对加入map中

若source点存在,返回值为其原键值(int型),否则返回null

3) public int setTargets(L target, int weight)

对终点进行如上操作

4) public boolean equals(Vertex<L> vertice)

定义标志变量name相同的两个点为同一个点,所以对name进行比较

5) public String toString():

根据对称性,打印该点的终点集信息即可。

2. 实现ConcreteVerticesGraph类:

 

根据规约设计checkRep方法:

 

 

实现其他方法:

​​​​​​​1)public boolean add(L vertex)

遍历vertices集合,查找有无与点vertex相同的点即可。若存在,则返回false;不存在,则在vertices集合中添加点vertex,然后返回true;

​​​​​​​2)public int set(L source, L target, int weight)

该方法主要分三种操作:修改已有的边,移除已有的边,添加新的边。因此,先遍历vertices集合,确定源点和终点是否存在。若源点存在,则在其终点集中进行setTargets操作;若终点存在,则在其源点集进行setSources操作。若某一端点不存在,则创建该点,设置对应的源点/终点集,然后在vertices集合加入新生成的点。返回值与对应setTargets/setSources操作返回值相同(checkRep()函数保证了setTargets/setSources操作返回值是相同的,返回任意一个即可)。

3) public boolean remove(L vertex)

先将edges集合中所有以vertex为源点/终点的边全部移除,然后根据hashSet类中remove()方法的定义及返回值,函数只需返回vertices.remove(vertex)即可。

4) public Set<L> vertices()

为保证数据信息不泄露,需要返回一个由vertices集合复制的新集合。

5) public Map<L, Integer> sources(L target)

函数需要返回所有直接指向target的点以及与之对应的边的权值。这里规定了返回值的类型为Map,故只需要生成一个Map然后遍历edges集合寻找所有以target为终点的边并将其源点作为键名、权值作为键值添加到新生成的Map中,最后返回该Map即可。

6) public Map<L, Integer> targets(L source)

函数需要返回所有source直接指向的点以及与之对应的边的权值。这里规定了返回值的类型为Map,故只需要生成一个Map然后遍历edges集合寻找所有以source为源点的边并将其终点作为键名、权值作为键值添加到新生成的Map中,最后返回该Map即可。

7) public String toString()

函数需要将图的所有信息打印出来,即所有的点与边的信息。故先将所有的点的名称打印出来,随后遍历edges集合,循环调用Edge类的toString方法即可。

​​​​​​​3.1.4 Problem 3: Implement generic Graph<L>

这一部分要求我们将原有的Graph<String>转化为泛型定义,注意同时要修改empty的方法。

​​​​​​​3.1.4.1 Make the implementations generic

我们可以在声明中改为Graph<L>即可

​​​​​​​3.1.4.2 Implement Graph.empty()

​​​​​​​ 

​​​​​​​3.1.5Problem 4: Poetic walks

​​​​​​​3.1.5.1 Test GraphPoet

对程序进行测试,分为三种情况:空文件、单段文字、多段文字进行测试,并检验是否符合预期结果。

​​​​​​​3.1.5.2 Implement GraphPoet

  1. 在读取.txt文件中的所有的单词,并将所有相邻的单词作为点添加到图中,边权记录相邻单词对出现的次数
  2. 读取后检查图是否合法
  3. 对目标字符串进行扩充:

首先要遍历给定文本的左右的单词,对于每一个单词,我们要找出该单词对应的点相邻点权值加和最大的单词,将其添加在原来的两个单词之间。遍历结束后,返回扩充后的字符串。

​​​​​​​3.1.5.3 Graph poetry slam

3.1.6​​​​​​​使用Eclemma检查测试的代码覆盖度

覆盖率较高,测试均被通过,符合预期结果。

​​​​​​​3.1.7 Before you’re done

在这里给出你的项目的目录结构树状示意图。

 

​​​​​​​3.2 Re-implement the Social Network in Lab1

我们需要利用在P1中已经实现的Graph类来实现我们在第一次实验中所设计的人际关系实验P3。我们不能改变现有的规约,而只能在此基础之上,利用Graph为我们提供的方法来进行设计。

相似的,我们应该首先确定Person的定义,其次在确定FriendshipGraph的具体方法,最后通过广搜来确定distance的大小。

3.2.1 FriendshipGraph类

我们将关系图声明为一个Graph类型的变量,由于已经泛化,我们可以将其中存储想要的Person类成员。首先先初始化,生成一个空图。

加点:不能重复,利用迭代器检查一遍

加边:利用graph类中的set方法,要注意这里是无向图,而我们的方法是有方向的,因此要将Target和Source互换调用两次,weight赋值为1即可。

返回graph的点的成员member:调用Graph.vertices()即可

 

最终是核心算法,利用广搜,可以计算最短路径,

​​​​​​​3.2.2 Person

首先我们为了保护数据,以下的数据均定义为private类型。

对于每一个Person类对象person,定义private final String name来储存他的姓名,其次用private Boolean isVisited来标记是否在搜索时被访问过。随后定义几个需要的观察者方法,来获取person的name和isVisited标记,并对标记初始化的函数initial和改变标记的函数visit。

3.2.3 客户端main()

实验要求直接使用Lab1中的main函数,运行一下观察结果的正确性。

3.2.4 测试用例 

测试用例主要关注于FriendshipGraph中的函数,而main函数主要起了调用的功能,因此无需测试。

对于加点,我们要考虑在空图中加新点,在已有非空图中添加新点,新点的name已经出现会报错,并终止程序。

 

对于加边,我们要考虑边的起始点不能相同,权值必须是正的。

对于计算距离,我们要考虑图中有环、联通性等对于距离的影响,包括自己到自己的距离等等,测试各种可能出现的情况,并验证实际值是否与理论值相同。

 

 

测试覆盖度:

​​​​​​​3.2.5 提交至Git仓库

如何通过Git提交当前版本到GitHub上你的Lab3仓库。

在这里给出你的项目的目录结构树状示意图。

 

4 实验进度记录

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

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

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

日期

时间段

计划任务

实际完成情况

3.28

15:00-17:00

完成graph两种实现

如期完成

3.29

18:00-20:00

完成poem部分

未按期完成,出现很多bug

3.30

18:00-20:00

实现poem walk

基本完成

4.3

13:00-15:00

重写FriendshipGraph

进行顺利但是有点速度慢

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

中遇到的困难与解决途径

遇到的难点

解决途径

实验要求晦涩难懂,在实现功能之前直接让写测试有点无从下手

复习课上内容,深入理解ADT、OOT、AF等知识

测试要使代码的覆盖度接近100%,最开始不知道如何实现

应当对测试空间分类,根据已经划分好的测试空间来进行测试样例的编写

在处理poem语句的时候总是有问题

Debug后发现忘记转化大小写,标点符号处理出错

在完成本实验的过程中,感受到了面向对象编程、泛型等的实际应用,同时对于Java语言的开发有了更多的理解。但是由于对java语言掌握的不是很好导致实验过程十分困难,进展缓慢,所以也在通过每次实验锻炼自己的开发能力。

​​​​​​​6.2 针对以下方面的感受(必答)

1.面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?

面向ADT编程,具有很好的规范性,主要是通过各个模块之间互相调用来实现功能;直接面向应用场景编程,是利用各个方法之间的相互调用,以整个过程为单位进行编程。

2.使用泛型和不使用泛型的编程,对你来说有何差异?

泛型有着极好的容错性和可移植性,可以使我们不必考虑变量的类型对他们进行相同的操作。

但是泛型也会弱化变量的一些具体的、特有的功能和方法。

3.在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?

如果先写代码再写测试,会受到编写程序的影响,陷入思维定势,很可能没有有效测出bug所在。我会逐渐适应的。

4.P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?

避免了代码、程序的重复设计,大大地节省了软件的开发时间和成本。

5.为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?

这些过程是对程序安全性、健壮性,设计框架的条理性与逻辑性的保障,更加体现了面向对象编程的抽象与封装概念,在程序设计的过程中,我们应该保持这种习惯。

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

工作量较多,难度较难,deadline合理。

7.《软件构造》课程进展到目前,你对该课程有何收获和建议?

通过本课程,我已经逐渐地了解了面向对象编程的大体过程、框架与部分技巧。但对于大部分知识掌握不是很好,应用起来也很困难,要继续学习。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值