个人项目

Posted on 2024-03-10 12:42  onezhan  阅读(243)  评论(0编辑  收藏  举报
作业所属班级 软件工程4班
作业的要求 个人项目
我理解的作业目标 用实践的方式加深对个人开发流程PSP的具体内容

这是我的GitHub仓库链接

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 9
Estimate* 估计这个任务需要多长时间 10 9
Development 开发 720 404
Analysis 需求分析(包括学习新技术) 30 24
Design Spec 生成设计文档 60 40
Design Review 设计复审 20 5
Coding Standard 代码规范 10 5
Design 具体设计 60 10
Coding 具体编码 300 180
Code Review 代码复审 20 5
Test 测试 100 60
Reporting 报告 40 30
Test Report 测试报告 20 10
Size Measurement 计算工作量 20 15
Postmortem & Process Improvment Plan 事后总结,并提出过程的改进计划 40 20
合计 730 413

计划

估计这个任务需要的时间

预估开发完成整个项目需要8个小时(480分钟)。

开发

需求分析

用户要求计算抄袭文件相对于原文文件重复率,并将这个重复率输出到答案文件上。

要求原文文件、抄袭文件和答案文件的路径由命令行参数给出。


用户的要求其实很简单满足,就是对用户传入的两份文件做一些算法上的处理,然后将处理结果输出到另一个文件上。

从用户需求方面考虑

但是需求是有很大概率会变动的,如果哪天用户不想传入文件,而是直接给我们字符串,我们又该如何处理呢?用户不想输出到文件上,他想输出到控制台中,怎么办?不计算查重率计算其他的,怎么办?我们应该采用一些措施,削弱需求的变动给项目带来的冲击。

用户只是希望计算一份原文文件和一份抄袭文件的重复率吗?用户可能是老师,他手下的学生不止有一个人是抄袭的。所以,对于文件的批处理也是应该考虑的一个需求,对于这一点,我们需要和用户做好具体的沟通,了解用户的真实需求。

用户对于查重的算法有什么具体的要求,查重的误差不得超过多少,还是只计算个大概就可以了?这些也需要更进一步的沟通。

具体技术来说

由于涉及到中文之间的比较,用户所提交的文件的字符集很可能是UTF-8。由于C++现有的库对中文的支持并不高,所以用Java作为开发语言。

但在此时此刻此场景,我认为需求可以当作是不变的。接下来要做的就是:完成用户现有的需求下,尽最大努力保证在用户需求发送变动时,原有的设计不会受到太大的冲击。

生成设计文档

通过分析需求,可以得出这样一个结论:

  1. 需要一个输入处理模块,用来对用户输入的数据(文件、字符串...)进行相关处理,才能让后续的计算模块更好地发挥作用
  2. 需要一个计算模块,对接收到的文章进行对比,计算得出重复率。
  3. 需要一个输出处理模块,将计算模块得出的查重率输出到用户指定的位置。

其交互逻辑图大概可以画成这样:

那对于各模块的接口的设计也可以得出来:

(1)输入处理模块

  1. 接收用户输入
  2. 让计算模块能获取到文章信息
  3. 让输出处理模块能获取用户期望的输出地点

(2)计算模块

  1. 接收文章信息
  2. 让输出模块获取查重率

(3)输出处理模块

  1. 接收输出地点
  2. 接收查重率

这样做的话如果未来想要改变与用户的交互方式(比如使用GUI界面)就可以直接对输入模块进行相应修改。

但如果和用户的交互方式发生了变化,那输出模块很可能要发生对应的变化

设计复审

从逻辑角度来说,设计是没问题的,但具体编码上可能会有所变化。

代码规范

尽可能的让项目文件结构清晰,并在代码中做必要的注释

具体设计

输入处理模块

该模块为外界提供三个接口,分别用于提供“原文”,“抄袭文”和“答案的输出目标”。

向外界提供的这些数据,一定是经过处理的用户输入数据。

计算模块

这个模块只用向外界提供一个计算的方法即可。若后续需要不同的方法,那就增加另外的方法。

输出处理模块

接收输入处理模块处理完后的答案目标。

确定好该目标后,就可以接收来自计算模块的计算结果,直接进行输出。

具体编码

输入处理模块

利用Java中文件处理的相关对象,将“原文”和“抄袭文”读取出来,并保存为字符串。

而“答案文件”按照此时的需求,不需要进行特殊的处理。

这部分工作在我的项目里,用类的构造函数实现:

public MyInput(File origin, File copy, File answer);
// 将原文文件origin转化为字符串originString,抄袭文件copy转化成copyString,并保存好答案的输出文件

该模块还得让输出处理模块能够获取输出文件,所以提供这样一个接口:

public File getAnswer();

计算模块要从输入处理模块获取原文文章和抄袭文章的字符串,所以该模块提供这样两个接口:

public String getOriginString();
public String getCopyString();

计算模块

package pers.calculate;

public class Calculate {

    // 统计原文文章和抄袭文章的相同部分,返回 相同部分字数 和 抄袭文章总字数 的比值
    public static double Ratio(String origin, String copy) throws Exception {
        // 1.找到两个文章第一个相同的部分
        // 2.统计相同部分的字数
        // 3.相同部分找完后,将原文不同部分的每一个字符,都在抄袭文章往后找20次,若超出这个范围,则认为原文不同部分确实是不同的部分。若在判断的过程中,抄袭文章出现了相同的字符,则找到了相同的部分,重新执行步骤2
        // 4.直到抄袭文章被遍历完,统计出全部相同部分的字数
        // 5.返回 相同部分的字数 / 抄袭文章的总字数
    }
}

将抄袭文章分为两部分:

  1. 与原文文章相同的部分。由于对原文文章进行了一部分的修改,这些部分一般都会无序地分散在抄袭文章的各个角落。
  2. 与原文文章不同的部分。

在我设计的算法中,将会统计抄袭文章中和原文文章雷同的部分,并记录下这部分的字数。

一般抄袭文章的修改程度都不会太高,所以对相同部分的判断是局部的,也就是说:当发现原文与抄袭文章有不同部分时,如果抄袭文章的后续一小部分都没有出现与原文的部分相同的出现,就可以认为抄袭文章并没有抄袭原文的这一部分。

最后,我们将统计好的相同部分的字数,与抄袭文章的总字数相除,得到抄袭字数占抄袭文章的比率,这个比率就当做是查重率。

输出处理模块

这个模块要做的可以这样理解:

我们写作业的时候都需要一张纸,有了这张纸之后,我们就可以依照自己的意愿往纸上写入内容。

输出处理模块就是一张纸,我们通过初始化准备好该模块之后,就可以把它当做纸来写入数据。

就像我们写东西在纸上的时候不会关心自己的笔是怎让写东西在纸上的,该模块也需要对上层写入的数据进行处理,并正确地写入到答案文件中。


(1)首先,该模块需要一个方法从输入处理模块获取答案文件,该功能用构造函数来实现:

public MyOutput(File answer);

(2)其次,要将来自计算模块的查重率输出至答案文件:

public void myWrite(double x);

代码复审

个人开发,我通过了边测试边复审的方式进行了代码的复审。

测试

采用Java的JUnit框架进行单元测试

输入处理模块

import org.junit.Test;
import pers.input.MyInput;

import java.io.File;

public class MyInputTest {

    MyInput input;

    @Test
    public void InputModuleTest() {
        // 用户正常输入
        File origin = new File("./Test/orig.txt");
        File copy = new File("./Test/orig_0.8_del.txt");
        File answer = new File("./Test/answer_0.8_del.txt");
        this.input = new MyInput(origin, copy, answer);
        System.out.println(input.getOriginString());
        System.out.println(input.getCopyString());

        // 用户输入不存在的文件路径
        origin = new File("H:/Test/orig.txt");
        copy = new File("H:/Test/orig_0.8_del.txt");
        answer = new File("H:/Test/answer_0.8_del.txt");
        this.input = new MyInput(origin, copy, answer);
        System.out.println(input.getOriginString());
        System.out.println(input.getCopyString());

        // 用户错误数据
        input = new MyInput(null, null, null);
        System.out.println(input.getOriginString());
        System.out.println(input.getCopyString());
    }
}

计算模块

import org.junit.Test;
import pers.calculate.Calculate;

public class CalRatioTest {

    @Test
    public void RatioTest() {
        try {
            // 正常情况
            String origin = "今天是星期天,天气晴,今天晚上我要去看电影。";
            String copy = "今天是周天,天气晴朗,我晚上要去看电影。";
            System.out.printf("%.2f", Calculate.Ratio(origin, copy));
		   // 语言不通
            origin = "Hello I am a student";
            copy = "你好,我是一个学生";
            System.out.printf("%.2f\n", Calculate.Ratio(origin, copy));
		   // 输入无效数据
            System.out.printf("%.2f\n", Calculate.Ratio(null, null));
        }
        catch (Exception e) {
            System.out.println(e.toString());
        }
    }
}

输出处理模块

import org.junit.Test;
import java.io.File;
import pers.output.MyOutput;

public class MyOutputTest {
    @Test
    public void OutputModuleTest() {
        // 输出目标正常
        File answer = new File("./Test/MyOutputTest.txt");
        MyOutput output = new MyOutput(answer);
        output.myWrite(3.1415926);
		// 没有输出目标
        output = new MyOutput(null);
        output.myWrite(3.1415926);
    }
}

测试报告

测试过程中发现

(1)计算模块的Ratio方法并没有考虑到传入数据为空的情况,并进行了相关的修改:在方法的首部,添加了处理逻辑,当要处理的数据为空时,抛出异常

(2)输出模块没有数据处理上的问题,但输出的格式并非保留两位小数,于是进行的小的修改:将 "%f" 改成了 "%.2f"

计算工作量

工作量占比:

30%:需求分析+生成设计文档

50%:编写代码+学习Java+熟悉IDEA

20%:编写文档

总结

本项目的最大难点在于查重算法的实现,如何进行高效地、准确地进行查重是这个项目最核心的点。

为此,我去试用了一些审阅功能网站,但各家对于老师所给的例子都给出了不同的答案,有些根本查不出来有相似,有些查出来只有20%。因此,我大胆做出猜测:在查重这方面,每家都有自己的算法,没有统一的标准,具体的查重审阅功能其实有很大缺陷:它们大部分只能对相似的字符进行判断,无法区别语义上的差别,无法对不同语言的文章进行比较......当然,我相信,如果利用AI等各种先进技术也确实能做到相对准确的查重判断。但结合自己能力,我决定只写一个有点局限性的查重算法,它只能做到有限度的查重。

在完成个人项目的过程中,我发现自己对于个人项目的开发流程还是不理解:

  1. 需求分析到底应该做到什么程度才算完成?我应该把这些分析写的尽可能简单易懂吗,但我觉得一些东西简单到根本没必要写上去,这也有必要作为说明写上需求分析里吗?说到底需求分析到底是一种分析后的结论,还是分析的过程?完全搞不懂好吧!
  2. 还有对于系统的设计,我感觉自己在进行总体设计的时候就把详细设计都搞完了,这会不会是因为项目实在太简单了,我在详细设计的时候感觉已经没什么好写的了。
  3. 对于单元测试,对于C++来说自动化的单元测试是有些困难的,但就Java来说,有成熟的Junit框架来进行自动化单元测试。

本项目是用C++写完之后,用Java进行重构的,原因是:C++中文字符的比较较为困难,而Java的char有两个字节支持Unicode编码,虽然C++的查重算法我也写出来了,但仅仅是将C++中string的3个char当成一个UTF-8字符,这个字符集一遍又不好搞了。

Copyright © 2024 onezhan
Powered by .NET 8.0 on Kubernetes