OO - 第三单元总结 - 博客作业- 79066020 路凯

单元题目介绍和说明

在第三单元中,我们开始学习JML(Java建模语言)及其在开发和测试社交网络中的应用。本单元的主要重点是理解和实现使用JML编写的精确软件规范,并确保我们的代码严格遵循这些规范。在每个作业中,我们都有一个官方包,其中包含一个处理数据输入的Runner类,以及我们需要实现的Person、Network和Tag等类的接口(我们命名为MyPerson、MyNetwork、MyTag等)。我们还需要处理各种类型的异常。第一个作业相对简单,只要求我们实现Network和Person类。第二个作业引入了Tag类,第三个作业增加了Message类及其派生类,包括RedEnvelopeMessage和NoticeMessage。下面,我将详细解释本单元的测试、数据处理、架构设计和性能改进策略。

单元的测试过程

代码开发中测试的重要性

测试是软件开发中的关键部分。它确保代码按预期运行,符合需求,并且没有漏洞。有效的测试提高了软件的可靠性、性能和安全性,对于交付高质量的应用程序至关重要。在我们的OO课程中,测试非常重要,这样我们可以确保代码在提交平台之前没有错误并且正常工作。强有力的测试还能确保我们获得更好的成绩。

JML和测试的介绍

JML(Java建模语言)是一种用于定义Java模块行为的规范语言。通过使用JML,我们可以指定类和方法的前置条件、后置条件和不变量。这对于团队协作非常方便;首先可以编写JML规范,然后不同的人可以分别实现不同的类,确保它们遵循该规范,这样我们就可以确保整个代码的协同工作。

让我们看看第三单元中的一个JML规范:

// An highlighted block
/*@ public normal_behavior
  @ requires containsTag(id);
  @ ensures (\exists int i; 0 <= i && i < tags.length; tags[i].getId() == id &&
  @         \result == tags[i]);
  @ also
  @ public normal_behavior
  @ requires !containsTag(id);
  @ ensures \result == null;
  @*/
public /*@ pure @*/ Tag getTag(int id);

这个JML规范说明,如果tags数组中存在指定id的标签,方法getTag应该返回该标签。否则,它应该返回null。requires子句指定了前置条件,即对于第一个行为,方法containsTag(id)必须返回true,而对于第二个行为则必须返回false。ensures子句指定了后置条件,即如果存在指定id的标签,结果将是该标签,否则结果将是null。

使用JML,我们甚至可以进行TDD(测试驱动开发)。由于规范作为合同,我们可以在编写实际实现之前根据这些合同编写测试。这有助于确保代码从一开始就符合定义的规范。

例如,下面是Network的JML要求:

public interface Network {
   
    /*@ public instance model non_null Person[] persons;
      @ public instance model non_null Message[] messages;
      @ public instance model non_null int[] emojiIdList;
      @ public instance model non_null int[] emojiHeatList;
      @*/

这些JML规范定义了四个模型数组来表示Network接口的不同数据。所有数组都使用了public instance model声明,并且使用non_null关键字保证它们及其元素不能为null。通过这些定义,Network接口保证了在实现和使用过程中对象数据的一致性和完整性。不仅TDD与JML结合更加方便,而且团队协作也更加高效。因为我们已经有了类的规范,如果其他人正在开发一个将与Network类交互的类,他们已经知道使用的数据类型,并且在这种情况下,它们都是non_null的。

测试类型及其重要性

  • 黑盒测试: 种软件测试方法,测试人员不知道被测试项目的内部结构/设计/实现。只测试外部设计和结构。
  • 白盒测试: 种测试技术,其中测试人员了解软件的内部工作原理,并可以测试单个代码片段、算法和方法。
  • 单元测试: 一个代码块,用于验证较小的、独立的应用程序代码块(通常是函数或方法)的准确性。
  • 功能测试: 种测试,旨在确定每个应用程序功能是否按照软件要求运行。将每个功能与相应的要求进行比较,以确定其输出是否符合最终用户的期望。
  • 集成测试: 种软件测试,其中将软件应用程序的不同单元、模块或组件作为组合实体进行测试
  • 压力测试: 评估软件在极端条件下的性能,识别突破点,确保其在高负载下的稳定性。
  • 回归测试: 软件开发周期中的一种测试,在每次更改后运行,以确保更改不会引入意外中断。

第三单元的架构设计

在这个单元中,主要的挑战是处理JML(Java建模语言)规范中指定的数据类型。这些类型通常不是最方便或性能最优的,但由于它们在JML中被明确规定了,所以我们不能更改它们。

例如,在我的例子中,我添加了一个HashMap<Integer, Person>来与Network类中的Person数组同步。这使得一些方法更容易实现。然而,我和许多同学都意识到,由于作业的截止日期非常紧迫,最好的策略是先以最简单的方式实现这些方法,然后如果时间允许,再对它们进行重构以提高性能。对于我来说,这种方法尤其重要,特别是考虑到我在前两个单元中遇到的困难,以确保我能通过这个单元的测试。

以下是我如何实现Person和Network接口的一个例子:请添加图片描述

public class MyNetwork implements Network {

	 /*@ public instance model non_null Person[] persons;
      @ public instance model non_null Message[] messages;
      @ public instance model non_null int[] emojiIdList;
      @ public instance model non_null int[] emojiHeatList;
      @*/
    private Person [] persons = new Person[0];
    private Message [] messages = new Message[0];
    private int [] emojiIds = new int[0];
    private int [] emojiHeatList = new int[0];
    private final Map<Integer, Person> personMap = new HashMap<>();
    private int tripleRelationCounter;

下面是我基本Exception的处理方法:

public class MyEmojiIdNotFoundException extends EmojiIdNotFoundException {
    private int id;
    private static int staticCounter = 0;
    private static HashMap<Integer, Integer> EqualCountPerId = new HashMap<>();

    public MyEmojiIdNotFoundException(int id) {
        staticCounter++;
        this.id = id;
        EqualCountPerId.put(id, EqualCountPerId.getOrDefault(id, 0) + 1);
    }

    @Override
    public void print() {
        System.out.println("einf-" + staticCounter + ", " + id + "-" + EqualCountPerId.get(id));
    }
}

在这中exception类,我通过构造函数创建对象,并使用EqualCountPerId的HashMap来控制每个ID异常的数量,同时还使用一个static int staticCounter计数器来获取同类型异常的发生次数。

下面是Message类的结构, 官方包的Message, NoticeMessage,EmojiMessage和RedEnvelopeMessage的结构上我实现了MyMessage,MyNoticeMessage,MyEmojiMessage和MyRedEnvelopeMessage。 这里最大的挑战是处理tag的Message(算群消息)。

请添加图片描述

@Override
    public void sendMessage(int id)
        throws RelationNotFoundException, MessageIdNotFoundException, TagIdNotFoundException {
        ValidationUtils.checkMessageExists(id, messages);
        Message message = getMessage(id);
        MyPerson person1 = (MyPerson) message.getPerson1();
        if (message.getType() == 0) {
            MyPerson person2 = (MyPerson) message.getPerson2();
            ValidationUtils.areLinked(getPerson(person1.getId()),
                getPerson(person2.getId()));
            processType0Message(message, person1, person2);
        } else {
            Tag tag = message.getTag();
            ValidationUtils.containsTag(tag.getId(), person1);
            processType1Message(message, person1, tag);

        }
        deleteMessage(id);
    }

在我的sendMessage方法中,我通过多个processMessage方法单独处理每种类型的消息。我原本打算将所有processMessage方法分开放在另一个类中,但在实现这个想法的过程中,代码变得越来越复杂,最后因为时间紧迫,未能完成这个计划。

性能问题及其修复情况

在这三个作业的过程中性能出问题的主要是: queryBlockSum, queryTripleSum, queryCoupleSum三个方法。 我第一次实现了这些方法有约了O(n^3)的时间复杂度, 但是因为当时最怕的是代码提交的ddl, 先按最简答的方法做, 再修改。

    /*@ ensures \result ==
      @         (\sum int i; 0 <= i && i < persons.length &&
      @         (\forall int j; 0 <= j && j < i; !isCircle(persons[i].getId(), persons[j].getId()));
      @         1);
      @*/
    public /*@ pure @*/ int queryBlockSum();

    /*@ ensures \result ==
      @         (\sum int i; 0 <= i && i < persons.length;
      @             (\sum int j; i < j && j < persons.length;
      @                 (\sum int k; j < k && k < persons.length
      @                     && getPerson(persons[i].getId()).isLinked(getPerson(persons[j].getId()))
      @                     && getPerson(persons[j].getId()).isLinked(getPerson(persons[k].getId()))
      @                     && getPerson(persons[k].getId()).isLinked(getPerson(persons[i].getId()));
      @                     1)));
      @*/
    public /*@ pure @*/ int queryTripleSum();
    	
	/*@ ensures \result ==
      @         (\sum int i, j; 0 <= i && i < j && j < persons.length
      @                         && persons[i].acquaintance.length > 0 && queryBestAcquaintance(persons[i].getId()) == persons[j].getId()
      @                         && persons[j].acquaintance.length > 0 && queryBestAcquaintance(persons[j].getId()) == persons[i].getId();
      @                         1);
      @*/
    public /*@ pure @*/ int queryCoupleSum();

我在MyNetwork加了一个private int tripleRelationCounter, 然后通过了了一个在updateTripleSumOnAdd同步tripleRelationCounter, 需要返回数据的时候直接返回tripleRelationCounter。 queryBlockSum我最终通过了UnionFind实现了, 在性能问题有很大的帮助。其他方法我还是通过了一个和Array同步的int Id和Person的Hash Map减少时间复杂度。

private void updateTripleSumOnAdd(MyPerson person1, MyPerson person2) {
        Set<Person> commonAcquaintances = new HashSet<>(Arrays.asList(person1.getAcquaintances()));
        commonAcquaintances.retainAll(Arrays.asList(person2.getAcquaintances()));
        for (Person common : commonAcquaintances) {
            MyPerson commonPerson = (MyPerson) common;
            if (commonPerson.isLinked(person1) && commonPerson.isLinked(person2)) {
                tripleRelationCounter++;  // Only increment if all three are mutually connected
            }
        }
    }

总结和收获

在完成单元 1 和单元 2 的作业后,我发现自己的编码能力有了很大的提高。最初,我在处理复杂的代码逻辑和数据结构时遇到了很多困难,但通过不断的练习和努力,我逐渐掌握了这些技能。在这个单元中,尽管面临着紧迫的截止日期和繁重的任务,我学会了如何在有限的时间内有效地实现功能,并逐步优化代码以提高性能。例如,我在实现sendMessage方法时,原本打算将所有processMessage方法分开放在另一个类中,但由于时间紧迫和实现过程中的复杂性,我选择了更简洁的方案来完成任务。

此外,在这个单元中,我不仅学习了 JML 和测试的重要性,还学到了如何解决实际编码中的问题。 我相信,通过这些经验和积累的知识,我将在单元 4 中表现得更加出色,继续提高自己的编程能力和问题解决能力。

  • 32
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值