第一次个人编程作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class78-Grade2024-CS/homework/15702
这个作业的目标 本项目旨在通过实现一个论文查重程序,掌握个人软件工程从需求分析、算法设计、编码实现到测试验证的完整开发流程。在实现字符级 3-gram + Jaccard 相似度算法的过程中,熟悉单元测试、代码覆盖率、性能分析和 GitHub 版本管理等工程化工具的使用。最终目的是培养规范化的代码组织能力与工程实践意识,为后续团队协作与大型项目开发打下基础。

项目仓库https://github.com/Lincan-gdut/paper-check

学号:3124004251

开发语言:Java 100%

项目版本:v1.0

本次软件工程实践,我完成了一款轻量论文查重系统的开发,依托字符级3-gram与Jaccard相似度算法,实现文本读取、相似度计算、结果输出全流程功能。整个开发过程遵循PSP个人软件过程,完成需求分析、代码开发、性能优化、单元测试、异常处理等全流程工作,在此记录完整开发过程与实战收获。

一、开发前:预估耗时

PSP 是卡耐基梅隆大学(CMU)的专家们针对软件工程师所提出的一套模型:Personal Software Process (PSP,个人开发流程,或称个体软件过程)。开发前做耗时预估如下。

PSP2.1 Personal Software Process Stages 预估耗时(分钟)
Planning 计划 25
· Estimate · 估计这个任务需要多少时间 25
Development 开发 240
· Analysis · 需求分析 (包括学习新技术) 30
· Design Spec · 生成设计文档 25
· Design Review · 设计复审 15
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 15
· Design · 具体设计 35
· Coding · 具体编码 60
· Code Review · 代码复审 30
· Test · 测试(自我测试,修改代码,提交修改) 30
Reporting 报告 70
· Test Report · 测试报告 25
· Size Measurement · 计算工作量 15
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30
· 合计 335
  • 计划阶段(25分钟):完成任务整体耗时预估

  • 开发阶段(240分钟):包含需求分析、设计文档编写、设计复审、代码规范制定、程序设计、编码、代码复审、功能测试全流程

  • 报告阶段(70分钟):完成测试报告、工作量统计、项目总结与改进计划

二、系统核心模块设计与实现

本项目采用分层低耦合设计,全程遵循高内聚、低耦合原则,共设计4个核心Java类,统一收纳于com.student3124004251包下,所有工具类方法均为静态方法,无状态依赖,便于测试和调用。

2.1 代码结构与类职责

  • Main(程序入口类):核心职责为参数校验、整体流程调度,是整个程序的调度中心,负责串联所有功能模块

  • TextFileReader(文件读取工具类):实现文本文件读取功能,支持UTF-8/GBK编码自动探测,解决中文乱码问题

  • SimilarityChecker(核心算法类):相似度计算核心,包含文本预处理、n-gram切分、Jaccard系数计算核心方法

  • AnswerFileWriter(结果输出类):负责查重结果写入文件,统一保留两位小数,规范输出格式

类调用关系:Main主程序依次调用 TextFileReader → SimilarityChecker → AnswerFileWriter,三个工具类相互独立,无直接依赖,耦合度极低。

2.2 整体程序执行流程

整个程序执行逻辑清晰、线性推进,无冗余流程:

  1. 入口校验:Main方法校验命令行参数,必须传入3个参数(原文路径、抄袭文本路径、结果输出路径),参数异常直接提示用法并退出

  2. 文件读取:调用工具类读取原文、抄袭版文本内容,自动适配文件编码

  3. 相似度计算:通过核心算法完成文本预处理、3-gram切分、Jaccard相似度计算

  4. 结果输出:将计算结果保留两位小数,写入指定输出文件

2.3 核心算法原理

本项目采用字符级3-gram + Jaccard相似度算法,无需第三方分词库,兼容中英文文本,适配性极强。

1. 文本预处理

通过正则\s+清除文本中所有空格、换行、制表符等空白字符,提纯文本内容,避免无效字符干扰计算结果。

2. 3-gram滑动切分

采用长度为3的字符级滑动窗口对文本切分,生成字符片段集合。例如文本「甲乙丙丁」,切分结果为{"甲乙丙", "乙丙丁"},以短字符片段精准捕捉文本相似度特征。

3. Jaccard相似度计算

核心公式:相似度 = 交集元素数量 / 并集元素数量

A、B分别对应原文、抄袭文本的3-gram集合,交集越多、并集越少,文本相似度越高,最终计算结果介于0-1之间。

2.4 项目核心亮点与独到之处

  • 零第三方依赖:无需引入HanLP、jieba等分词工具,纯原生Java实现,中英文通用,轻量化、易部署

  • 高效时间复杂度:基于HashSet实现集合存取,核心操作均为O(1),整体算法线性时间复杂度O(n),响应速度快

  • 编码自适应兼容:优先UTF-8读取,乱码自动回退GBK编码,完美适配Windows中文文件环境

  • 高内聚零耦合:工具类无状态、纯静态方法,模块独立,便于单元测试和功能拓展

  • 异常健壮性强:全局统一异常捕获,精准提示错误信息,避免程序崩溃退出

2.5 关键函数SimilarityChecker.jaccardNgram 流程图

image

三、性能分析与优化改进

开发完成后,通过IntelliJ IDEA Profiler CPU性能分析工具对程序进行采样分析,定位性能瓶颈并针对性优化,大幅提升程序运行效率。

3.1 原始性能瓶颈分析

性能采样结果显示,程序CPU消耗集中在核心算法模块:

  • SimilarityChecker.jaccardNgram 占比92%(核心耗时模块)

  • 其中ngrams切分方法占比48%,密集调用String.substring是主要性能损耗点

  • HashSet交并集操作占比36%,双重遍历存在冗余开销

  • 文件读取、结果输出模块耗时占比极低,无优化必要

3.2 针对性优化方案

  1. 边界逻辑优化:文本长度小于3时直接返回空集合,避免无效的substring切分,减少空耗运算

  2. 集合运算优化:放弃原有双重遍历的retainAll/addAll方法,遍历较小集合匹配元素,将交集运算复杂度从O(m+n)降至O(min(m,n))

  3. 算法参数调优:对比n=2(相似度失真偏高)、n=5(文本区分度不足),最终确定n=3为最优参数,平衡精度与性能

  4. 编码读取优化:通过一次性字节分析替代多次try-catch解码,减少IO反复操作开销

3.3 优化效果对比

经过多轮针对性优化,不同规模文本的程序运行效率均有明显提升,优化前后耗时对比详情如下表:

文本规模 优化前耗时 优化后耗时 效率提升效果
小于10KB小文本 8ms 5ms 提升37.5%
500KB中大文本 400ms 320ms 提升20%

四、单元测试设计与结果

本次采用白盒测试思路,覆盖正常场景、边界场景、异常场景,共设计15个单元测试用例,全面保障程序稳定性与准确性。

4.1 测试覆盖场景

  • 相似度算法测试:覆盖完全相同、完全不同、部分相似、空文本等边界场景(5个用例)

  • 辅助函数测试:校验文本预处理、n-gram切分、算法对称性(5个用例)

  • 文件读写测试:正常文件读取、不存在文件异常、结果格式化输出(5个用例)

4.2 核心测试用例展示

// 用例1:完全相同文本,相似度应为1.0
@Test 
public void testIdentical() {    
    assertEquals(1.0, SimilarityChecker.jaccardNgram(            
            "今天是星期天", "今天是星期天", 3), 0.001); 
}

// 用例2:部分相似文本,相似度介于0-1之间
@Test 
public void testPartial() {    
    double s = SimilarityChecker.jaccardNgram(            
            "今天是星期天,天气晴", "今天是周天,天气晴朗", 3);    
    assertTrue(s > 0 && s < 1); 
}

// 用例3:读取不存在文件,应抛出指定异常
@Test 
public void testReadFileNotExist() {    
    try {        
        TextFileReader.read("不存在的文件_xyz.txt");        
        fail("应当抛出 FileNotFoundException");    
    } catch (Exception e) {        
        assertTrue(e instanceof FileNotFoundException);    
    } 
}

// 用例4:结果保留两位小数格式化测试
@Test 
public void testWriteAnswerFormat() throws Exception {    
    File tmp = File.createTempFile("test_ans_", ".txt");    
    tmp.deleteOnExit();    
    AnswerFileWriter.write(tmp.getAbsolutePath(), 0.5678);    
    String content = new String(Files.readAllBytes(tmp.toPath()),            
            StandardCharsets.UTF_8);    
    assertEquals("0.57", content); 
}

4.3 测试覆盖率结果

074c76207b6f9ba4216aa67816ba9889

通过IDEA Code Coverage工具运行全部15个测试用例,各模块测试覆盖率数据清晰、达标率高,详细覆盖率统计如下表:

测试类 类覆盖率 方法覆盖率 行覆盖率 分支覆盖率
SimilarityChecker 100% 100% (3/3) 100% (19/19) 85% (12/14)
TextFileReader 100% 100% (1/1) 85% (6/7) 75% (3/4)
AnswerFileWriter 100% 100% (1/1) 100% (2/2) 100% (0/0)
Main(豁免测试) 0% 0% (0/1) 0% (0/10) 0% (0/2)
项目整体 75% (3/6) 83% (5/6) 71% (27/38) 75% (15/20)

说明:Main入口类因包含System.exit()方法,执行测试会强制终止JVM,属于单元测试常规豁免场景。其余三大核心工具类均实现100%方法覆盖率,测试质量可靠。

五、全局异常处理机制

为避免程序崩溃、规避异常扣分规则,项目在Main主方法中设置全局统一异常捕获,所有异常统一拦截、打印友好提示,以退出码1正常退出,提升程序健壮性。

5.1 文件不存在异常

用户传入无效文件路径时,主动判定文件合法性,抛出带明确提示的FileNotFoundException,避免原生晦涩堆栈信息,对应专项测试用例覆盖。

5.2 编码兼容异常

默认UTF-8读取文件,检测到乱码字符(\uFFFD)时,自动切换GBK编码二次读取,完美兼容Windows系统中文文件,解决跨环境乱码问题。

5.3 命令行参数异常

校验命令行参数数量,非3个参数时,主动输出程序用法提示,直接退出程序,杜绝数组越界异常。

5.4 文件写入异常

通过try-with-resources语法实现流自动关闭,针对权限不足、磁盘已满等写入异常,统一上抛至全局异常处理器,给出友好错误提示。

六、项目版本管理与提交记录

项目全程使用Git进行版本控制,遵循「单一功能单一提交」原则,共4次规范commit,最终发布v1.0正式版本:

  1. Initial commit:仓库初始化,提交README文档

  2. chore: 创建学号文件夹:新建学号目录,规范项目文件结构

  3. feat: 实现基础查重功能:完成四大核心Java类开发,实现全流程查重功能

  4. test: 添加单元测试用例:新增15个测试用例,完善项目测试体系

最终Release v1.0发布,打包生成可直接运行的main.jar文件(4.61KB),支持命令行直接部署运行。

命令行运行示例

java -jar main.jar orig.txt copy.txt ans.txt 
# 输出:重复率: 0.27 
# ans.txt 最终内容:0.27

七、开发后:实际耗时

项目开发完成后,统计各阶段实际耗时,总耗时348分钟。耗时高于预估,核心原因是新增性能优化、边界测试和异常兼容处理等工作,详细实际耗时明细如下表:

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 25 30
· Estimate · 估计这个任务需要多少时间 25 30
Development 开发 240 380
· Analysis · 需求分析(包括学习新技术) 30 60
· Design Spec · 生成设计文档 25 35
· Design Review · 设计复审 15 20
· Coding Standard · 代码规范(为目前的开发制定合适的规范) 15 25
· Design · 具体设计 35 50
· Coding · 具体编码 60 90
· Code Review · 代码复审 30 40
· Test · 测试(自我测试,修改代码,提交修改) 30 60
Reporting 报告 70 100
· Test Report · 测试报告 25 35
· Size Measurement · 计算工作量 15 20
· Postmortem & Process Improvement Plan · 事后总结,并提出过程改进计划 30 45
· 合计 335 510
  • 计划阶段(30分钟):耗时略低于预估,前期任务规划高效

  • 开发阶段(380分钟):需求分析、编码、测试耗时明显增加,主要用于解决编码兼容问题、性能调优和多场景测试

  • 报告阶段(100分钟):新增性能分析、测试覆盖率复盘,耗时小幅增加

八、项目总结与实战收获

7.1 项目成果总结

本次实践完整落地了软件工程全流程,最终实现一款轻量、高效、高兼容、高健壮性的论文查重系统:算法层面依托3-gram+Jaccard实现精准查重,无第三方依赖、线性复杂度、运行高效;工程层面实现模块化分层设计、全局异常处理、全覆盖单元测试,代码规范、低耦合易拓展;流程层面严格遵循PSP过程,规范Git版本管理,完成从开发、优化、测试到发布的完整闭环。

7.2 开发遇到的问题与解决办法

  • API版本兼容问题:初期使用Java10+专属Scanner读取API,降级Java8环境后编译失败,替换为通用的Files.readAllBytes解决兼容问题

  • Java环境变量问题:本地PATH未配置Java环境变量,无法直接调用java命令,通过JDK完整路径临时调用,后续配置系统环境变量彻底解决

7.3 个人收获

通过本次项目,我熟练掌握了Java模块化开发思想、文本相似度算法落地、IDEA性能调优与覆盖率测试工具的使用,理解了PSP个人软件过程的实操意义,同时熟练掌握了Git版本管理、GitHub Release发布流程。更深刻体会到,软件工程不仅是实现功能,更要兼顾代码规范、性能优化、异常兼容和测试闭环,保障项目的稳定性和可用性。

posted @ 2026-09-13 23:21  计科7班林灿  阅读(7)  评论(0)    收藏  举报