哈工大软件构造Lab2(2022)

(防扒小助手)

本人CSDN博客:

本人博客园博客(同步CSDN):

如果对你有用的话欢迎点赞关注哟!

1、实验目标概述

2、实验环境配置

3、实验过程

3.1 Poetic Walks

3.1.1 Get the code and prepare Git repository

3.1.2 Problem 1: Test Graph

3.1.3 Problem 2: Implement Graph

3.1.4 Problem 3: Implement generic Graph

3.1.5 Problem 4: Poetic walks

​​​​​​​3.1.6 Before you’re done

​​​​​​​3.2.1 FriendshipGraph类

​​​​​​​3.2.2 Person类

​​​​​​​3.2.3 客户端main()

3.2.4 测试用例

3.2.5 提交至Git仓库

4 实验进度记录

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

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

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

6.2 针对以下方面的感受

本次实验训练抽象数据类型(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 并据此设计测试用例。

(1)安装测试用例代码覆盖度插件

经过网上查阅资料了解到,在IDEA中已经集成了代码覆盖度插件JaCoCo,切换方式如下:

点击Edit Configuration,弹出如下窗口:

在Code Coverage一栏点击Modify勾选红框中的选项,则会弹出切换代码覆盖度工具的选项:

通过咨询软件构造课程老师与助教老师,了解到使用IDEA自带的代码覆盖度工具即可,不需要切换到JaCoCo,因此下文代码覆盖度测试均使用IDEA自带的coverage runner进行测试。

2、GitHub Lab2仓库的URL地址(Lab2-学号)

3.1 Poetic Walks

该任务主要是通过实现一个图的模块来练习ADT的规约设计和ADT的不同实现。

(1)完善Graph接口类,并运用泛型的思想,将String拓展为泛型L类;

(2)实现Graph接口类:以边和点两种方式实现接口;

(3)利用实现的Graph类,应用图的思想,实现GraphPoet类。如果输入的文本的两个单词之间存在桥接词,则插入该桥接词;若存在多个单一桥接词,则选取边权重较大者。

从要求文件中对应网址下载得到实验代码,建立好project,进入目录,打开Git bush

依次输入:

git init
git remote add origin git@github.com:ComputerScienceHIT/HIT-Lab2-120L022408.git
git pull origin master
git add .
git commit -m “init”
git push origin master

测试Graph的静态方法。

为了方便测试Graph的多种实现,在 GraphInstanceTest 中测试了实例方法。

编写测试用例主要利用等价类划分的思想进行测试,测试策略如下:

分别编写覆盖以上条件的测试用例。

运行 Graph S tatic T est得到测试结果如下:

3.1.3.1 Implement ConcreteEdgesGraph

(1)Edge类实现

定义两个private String类型的变量source和target存放每个边的起止点

定义一个private int类型的变量weight保存这条边的权重(长度)

private final String source ,target ;
private final int weight ;

关于AF,RI和rep exposure:

① 构造器 constructor

构造方法,使用上述三个数据域声明一个新的边

public Edge (L source_new,L target_new,int weight_new)
{
this .source = source_new;
this .target = target_new;
this .weight = weight_new;
checkRep ();
}

② 检查表示不变量 checkRep

检查表示不变量,其中source和target必须非空,weight必须大于0

public void checkRep ()
{
assert source !=null ;
assert target !=null ;
assert weight >0 ;
}

③ get方法

get_Source:返回source域
get_Target:返回target域
get_Weight:返回weight域

④ toString方法

返回一个字符串表明这条边是从哪个source到哪个target,其weight是多少。

public String toString ()
{
return source .toString () +”->”+target .toString () +”\t 权重为”+weight +’\n ‘;
}

(2)ConcreteEdgesGraph实现

vertices和edges分别记录当前graph所含有的点和边

private final Set vertices =new HashSet <>();
private final List >edges =new ArrayList <>();

关于AF,RI和rep exposure:

① add方法

public boolean add (String vertex)

如果顶点不为空,添加一个顶点。如果在vertices的Set集合中成功添加了vertex,则返回true。

② Set方法

public int set (String source,String target,int weight)

输入source,target,weight,确定一条有向边。

具体做法:如weight!=0,移去可能已经存在的相同起始点的边,然后加入新的边,如weight=0,寻找可能已经存在的相同起始点的边,删去。

③ remove方法

public boolean remove (String vertex)

从vertices中删去给定的vertex点,遍历edges,寻找该vertex是否为某条边的起点或者终点,删去相应的边。在使用迭代器遍历时要使用iterator.remove方法保证安全性。

④ vertices方法

public Set vertices ()

返回vertices集合。注意做到safety from rep exposure ,使用Collections.unmodifiableSet()方法。

⑤ sources方法

public Map sources (String target)

参数:target。根据传入的target参数寻找以targe为终点的边。返回一个键值对为(点,权重)的map。

实现:建立一个map,利用迭代器遍历edges,如果某个edge的edge.get_Target()和传入参数target相等,则将该边的source和weight存入map中。

⑥ targets方法

public Map targets (String source)

参数:source。根据传入的source参数寻找以source为起点的边。返回一个键值对为(点,权重)的map。

实现:建立一个map,利用迭代器遍历edges,如果某个edge的edge.get_Source()和传入参数source相等,则将该边的target和weight存入map中。

⑦ 检查表示不变量 checkRep

思路:n个点,最多构成n*(n-1)条有向边,因此存在这种不可变的数学关系

⑧ toString方法

对每条边调用toString方法,整合起来。

public String toString ()
{
String s =””;
for (Edge e :edges )
{
s = s +e .toString ();
}
return s;
}

(3)ConcreteEdgesGraphTest测试

JUnit测试结果如下:

测试覆盖率:

3.1.3.2 Implement Concrete Vertices Graph

(1)Vertex类实现

定义两个private String类型的变量source和target存放每个边的起止点

定义一个private int类型的变量weight保存这条边的权重(长度)

private final String name ;
private final Map sources ;
private final Map targets ;

关于AF,RI和rep exposure:

① 构造器 constructor

构造方法,传入参数name创建新的点。

public Vertex (String name)
{
this .name = name;
sources =new HashMap <>();
targets =new HashMap <>();
}

② 检查表示不变量 checkRep

检查表示不变性,各边weight的值应该永远大于0。

③ get方法

get_Name:返回source域
get_Sources:返回weight域
get_Target:返回Targets域

④ set方法

set_Target:为当前点新增一个target,

如果weight为0,删去当前点的target,成功返回删去target的weight,不存在返回0;如果weight不为0,为当前点新增一个target,长度为weight,如果该点已存在,返回旧的weight,否则返回0

set_Source:为当前点新增一个source,

如果weight为0,删去当前点的source,成功返回删去source的weight,不存在返回0;如果weight不为0,为当前点新增一个source,长度为weight,如果该点已存在,返回旧的weight,否则返回0

⑤ remove方法

remove_Source:删去当前点的指定source

public int remove_Source (String source)
{
Integer weight =sources .remove (source);
return weight ==null ?0 :weight ;
}

remove_Target:删去当前点的指定target

public int remove_Target (String target)
{
Integer weight =targets .remove (target);
return weight ==null ?0 :weight ;
}

⑥ toString方法

返回一个字符串表明这个顶点的信息

public String toString ()
{
return String .format(“Vertex %s has %d sources and %d targets”,this .get_Name ().toString (),this .get_Sources ().size (),this .get_Targets ().size ());
}

(2)ConcreteVerticesGraph实现

使用如下数据类型保存顶点的数据:

private final List >vertices =new ArrayList <>();

关于AF,RI和rep exposure:

① 检查表示不变量 checkRep

所有点的标识不能为空

private void checkRep ()
{
assert vertices !=null ;
}

② add方法

public boolean add (String vertex)

参数:vertex,判断vertices中无重复点就加入

③ Set方法

public int set (String source,String target,int weight)

参数:source, target, weight。先将可能不在vertices中的source点和target加入vertices。随后遍历vertices,找到source对它增加一个target,找到target为它增加一个source,并设置距离。

④ remove方法

public boolean remove (String vertex)

参数:vertex。遍历vertices,如果当前点是vertex,删去(使用iterator.remove方法),如果不是,检查它的source和target是否包含vertex,如果有删去。

⑤ vertices方法

遍历vertices,找到每个点对应的string,添加进set即可。使用防御性拷贝:

public Set vertices ()
{
Set set =new HashSet <>();
for (Vertex v :vertices )
{
set .add (v .get_Name ());
}
return set ;
}

⑥ sources方法

public Map sources (String target)

参数:target。根据传入的target参数寻找以targe为终点的边。返回一个键值对为(点,权重)的map。

⑦ targets方法

public Map targets (String source)

参数:source。根据传入的source参数寻找以source为起点的边。返回一个键值对为(点,权重)的map。

实现:建立一个map,利用迭代器遍历edges,如果某个edge的edge.get_Source()和传入参数source相等,则返回source对应的目标点图。

⑧ toString方法

打印当前顶点图的顶点数量:

public String toString ()
{
return String .format(“This graph has %d vertices”,this .vertices .size ());
}

(3)ConcreteEdgesGraphTest测试

JUnit测试结果如下:

测试覆盖率:

3.1.4.1 Make the implementations generic

将具体类的声明更改为:

public class ConcreteEdgesGraph implements Graph { … }

class Edge { … }

public class ConcreteVerticesGraph implements Graph { … }

class Vertex { … }

更新两个实现以支持任何类型的顶点标签,使用占位符L代替String。

充分利用IDEA的智能改错功能快速修改成泛型实现。

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

选择ConcreteEdgesGraph来实现Graph.empty()

测试全部通过:

3.1.5.1 Test GraphPoet

关于测试策略:

具体测试:

​​​​​​​3.1.5.2 Implement GraphPoet

首先声明:

private final Graph graph =new ConcreteEdgesGraph ();

关于AF,RI和rep exposure:

① 检查表示不变量 checkRep

所有点的标识不能为空

private void checkRep ()
{
assert graph !=null ;
}

② GraphPoet方法

参数:corpus文件路径。打开文件,读取文件输入,识别序列,构建图结构。

③ poem方法

参数:input。

利用相同方法分割输入字符串,声明一个StringBuilder保存返回结果。每次读取一个词,然后以当前词为source,下一个词为target,在graph中寻找符合此条件的边,记录权值,结束后选择权值最大的,利用StringBuilder. Append方法,将节点名字加入字符串。

④ toString方法

调用ConcreteEdgesGraph的toString方法,输出图结构

public String toString ()
{
return graph .toString ();
}

​​​​​​​3.1.5.3 Graph poetry slam

语料库为泰戈尔经典名句集锦

输入输出如下:

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

git add .
git commit -m “P1 Finished”
git push -u origin master

项目的目录结构树状示意图。

这个实验是基于在Poetic Walks中定义的Graph及其两种实现,重新实现Lab1中的 FriendshipGraph类。我们需要尽可能复用ConcreteEdgesGraph或 ConcreteVerticesGraph中已经实现的add()和set()方法,而不是从零开始。另外基于所选定的 ConcreteEdgesGraph 或 ConcreteVerticesGraph的rep来实现,而不能修改父类的rep。

(1)设计思路

继承ConcreteEdgesGraph

public class FriendshipGraph extends ConcreteEdgesGraph

(2)方法实现

构造一个ArrayList类型的变量person_list存储顶点列表

private final ArrayList person_list =new ArrayList ();

① public boolean addVertex(Person people)

这个函数是为把参数添加到图中,作为图的一个顶点,直接调用父类的this.add()即可。调用过程中检查顶点列表中是否已出现参数对应的顶点,若重复则打印错误信息并返回false,成功添加顶点则返回true

② public boolean addEdge(Person people1, Person people2)

构建图的要素,在图中添加边。先调用 this.vertices().contains()方法来判断所添加边的顶点是否存在,再判断两顶点之间是否已有边连接,若条件满足,则调用this.set()方法设置边,权重初始化为1并返回true,其余情况返回false。

③ public int getDistance(Person People1, Person People2)

获取两个顶点之间距离的函数,题目要求返回最短距离,因此采用广度遍历的方式,此处需要用到Queue的数据结构,并且设置了一个List来存放已经访问过的person。

Person类根据FriendshipGraph类的需求编写的。它用于描述每个成员的性质,主要是实例化姓名的构造方法,getName()方法,判断姓名是否重复的isSameName方法。

public class Person
{
private final String Name ;

public Person (String Name)
{
this .Name = Name;
}
public String getName ()
{
return this .Name ;
}
public boolean isSameName (String Name)
{
return this .Name .equals (Name);
}
}

main函数主体内容即为实验指导书给定的内容:先new一个FriendshipGraph类的对象,然后添加顶点,添加边。

输出错误类型在实现FriendshipGraph类时已输出,故此处不需要再次判断是否出现错误。

① 正常输出的测试结果

② 注释掉rachel -> ross后的测试结果

测试策略:

根据划分的等价类设计测试用例

测试结果与覆盖度报告:

通过Git提交当前版本到GitHub上的Lab2仓库。

git add .
git commit -m “P1 P2 first finished”
git push -u origin master

本项目的目录结构树状示意图:

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

时间段

计划任务

实际完成情况

2022-05-23

8:00-11:30

浏览报告,查看MIT相关内容要求

按计划完成

2022-05-23

15:00-19:00

完成P1的GraphStaticTest类测试

按计划完成

2022-05-24

9:00-11:00

完成P1的ConcreteEdgesGraph类编写

按计划完成

2022-05-24

15:00-20:00

完成P1的ConcreteVerticesGraph类

按计划完成

2022-05-25

8:15-11:00

实现泛型Graph

按计划完成

2022-05-25

13:00-14:00

浏览Poetic Walks的编写要求

按计划完成

2022-05-25

21:00-23:00

尝试实现Poetic Walks

遇到困难,延期完成

2022-05-26

9:00-17:00

完成Poetic Walks的test和Implement

按计划完成

2022-05-27

8:00-18:00

完成P2的Social Network的改写

按计划完成

遇到的难点

解决途径

不了解IDEA如何实现代码覆盖率测试

查阅网上资料后发现相应解决方案因IDE版本迭代已发生改变。自己摸索解决了实现代码覆盖率测试插件切换的问题。

对面向test的编程思想理解不够深入

复习《软件构造》课程的PPT,上网查阅了相关资料并咨询了同学,完成了对测试代码的编写。

对规约的要求不够理解

通过学习模仿,尝试自己编写相应规约并实现之。

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

经验:

加深了自己对于泛型的理解和认识,提高了代码编写、ADT设计的能力。编写test测试文件时,有些方法的测试也能覆盖到其他的方法,避免重复测试增加工作量。

教训:

在设计多个类并使之互相配合的方面做得不好,编写代码的逻辑性有待提高。

6.2 针对以下方面的感受

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

我感觉,对于面向ADT的编程,类是其主要特点,程序执行过程中,先由主函数进入,定义一些类,根据需要,执行类的成员函数,过程的概念被淡化了。而直接面向应用场景编程的抽象程度不高,虽然逻辑清晰但是代码思路混乱,不利于实现。

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

泛型编程可以使代码被很多不同类型的对象所重用,并使代码具有更好的可读性。

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

优势是不考虑代码的内部实现,只需考虑是否完成了规约中指定的功能。作为java语言的初学者来说我很不适应这种测试方式。

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

提高了代码的利用率,减轻编程工作量。

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

使编写的代码更加安全和可读性更强。愿意这么做。

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

我认为,考虑到实验时间,与其他专业课的复习时间与考试时间有大量冲突,因此显得本实验工作量十分巨大,难度也很高,deadline十分紧张。

(7)《软件构造》课程进展到目前,你对该课程有何体会和建议?

希望减少工作量,增加课时安排,增加动手实验分数,减少笔试考试分数,合理安排课程开展时间,可以在刚开学时开课或者在小学期开课,更有利于学生能力的提升。

Original: https://www.cnblogs.com/kalesky/p/16327501.html
Author: 何以牵尘
Title: 哈工大软件构造Lab2(2022)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/593547/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • SpringMVC笔记

    SpringMVC 一、SpringMVC介绍 1.什么是MVC 是一种软件架构的思想,将软件按照模型、视图、控制器来划分 M:Model,模型层,指工程中的JavaBean,作用…

    Java 2023年6月16日
    092
  • Tomcat最全乱码问题解决方案(保姆教程)

    概述 原因 解决方法 1. idea乱码和startup.bat启动控制台日志乱码(Tomcat日志乱码) 2. 浏览器乱码 概述 tomcat乱码问题相信大家肯定都遇见过,本篇将…

    Java 2023年6月15日
    089
  • Spring:容器

    容器 控制对象关系,进行依赖注入,分两类: Bean工厂: org.springframework.beans.factory.BeanFactory接口定义 上下文: org.s…

    Java 2023年5月30日
    084
  • Java核心技术-内部类(上)

    Day7 内部类 定义在另一个类中的类 内部类🙆‍可以都对同一个包中的其他类隐藏 内部类可以访问定义这个内部类的作用域中的属性,包括私有属性 内部类总有一个隐式引用,指向创建它的外…

    Java 2023年6月5日
    075
  • springboot打包war部署到weblogic,涉及Filter以及Filter中的@Value处理

    基于Maven构建。 1:修改pom.xml配置文件 <packaging>warpackaging> <dependency> <groupI…

    Java 2023年5月30日
    0100
  • 七牛云 融合CDN测试域名 -> 融合CDN加速域名

    七牛云 融合CDN测试域名 -> 融合CDN加速域名 本篇主要讲解 如何将七牛云融合CDN测试域名 切换到自定义的加速域名上去, 为什么会写这篇是因为我收到了一封 【七牛云】…

    Java 2023年6月9日
    087
  • 初识jdbc

    jdbc的概念,优势,和模拟jdbc 作用 连接数据库 与图形画界面的差别 功能与Navicat、SQLyog,一样都是用来操作数据库,但是jdbc是用编码来操作数据库,而Navi…

    Java 2023年6月5日
    073
  • 6. 软件架构的演进过程

    1.软件架构的演进过程 1.1单一应用架构(ORM) &#x5F53;&#x7F51;&#x7AD9;&#x6D41;&#x91CF;&am…

    Java 2023年6月5日
    089
  • 搬砖_kafka的一些命令

    有关kafka的一些命令 kafka查看当前消费组消费到哪个offset cd /opt/kafka/bin #&#x5373;&#x8FDB;&#x516…

    Java 2023年6月7日
    0105
  • Maven 依赖调解源码解析(四):传递依赖,第一声明者优先

    本文是系列文章《Maven 源码解析:依赖调解是如何实现的?》第四篇,主要介绍依赖调解的第二条原则:传递依赖,第一声明者优先。请按顺序阅读其他系列文章,系列文章总目录参见:http…

    Java 2023年6月16日
    0105
  • Java如何去除字符串中的HTML标签

    Java如何去除字符串中的HTML标签 使用爬虫爬取网站数据,有时会将HTML相关的标签也一并获取,如何将这些无关的标签去除呢,往下看: 直接写个Test类: @Test void…

    Java 2023年6月15日
    087
  • SpringMVC前置复习以及扩展

    ssm:mybatis+Spring+SpringMVC javaSE javaweb 框架 理解的DAO层和Service层 先简单来讲下Dao层,和Service层的概念: S…

    Java 2023年6月14日
    090
  • SpringBoot启动过程(生命周期及事件)概述

    总结: ======================== 详见正文:SpringBoot生命周期事件——BAT的乌托邦 正文 本文将以 SpringApplication的启动流程…

    Java 2023年5月30日
    096
  • Java两种核心机制

    1.Java虚拟机 2.垃圾回收 posted @2017-02-26 23:13 Big_Foot 阅读(250 ) 评论() 编辑 Original: https://www….

    Java 2023年5月29日
    088
  • C# 线程手册 第五章 扩展多线程应用程序 系列

    到目前为止我们使用多线程应用程序的目的是尽可能多地使用计算机处理器资源。所以,看起来我们仅需要为每个独立的任务分配一个不同的线程,并让处理器确定在任何时间它总会处理其中的某一个任务…

    Java 2023年5月29日
    079
  • SpringBoot使用Filter

    对全部请求进行Filter Filter: package com.example.demo.filter; import org.springframework.core.Ord…

    Java 2023年6月9日
    076
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球