软工第二次作业
项目 | 内容 |
---|---|
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience |
这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477 |
这个作业的目标 | 设计一个论文查重算法,并进行性能优化和单元测试设计,利用GitHub进行代码管理,同时实现结果可视化展示 |
作业GitHub链接 | https://github.com/kekokl/Kohdia/tree/main/3223004302 |
一、PSP表格
Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|
Planning(计划) | ||
· Estimate(估计任务时间) | 30 | 25 |
Development(开发) | ||
· Analysis(需求分析) | 60 | 50 |
· Design Spec(生成设计文档) | 45 | 40 |
· Design Review(设计复审) | 30 | 25 |
· Coding Standard(代码规范) | 15 | 15 |
· Design(具体设计) | 60 | 55 |
· Coding(具体编码) | 180 | 200 |
· Code Review(代码复审) | 45 | 40 |
· Test(测试) | 90 | 100 |
Reporting(报告) | ||
· Test Report(测试报告) | 45 | 40 |
· Size Measurement(计算工作量) | 15 | 10 |
· Postmortem & Process Improvement Plan(事后总结) | 30 | 25 |
· 合计 | 645 | 625 |
二、计算模块接口的设计与实现过程
1. 代码组织设计
本项目采用Python 3实现,整体代码分为3个核心模块,各模块职责与关系如下:
模块/文件 | 核心类/函数 | 功能描述 |
---|---|---|
text_processor.py |
TextProcessor 类 |
负责文本预处理,包括中文分词、停用词过滤、特殊字符清理等操作 |
similarity_calculator.py |
SimilarityCalculator 类 |
基于余弦相似度算法,计算预处理后文本的重复率 |
main.py |
main() 函数 |
解析命令行参数,调用上述两个模块完成文件读取、计算、结果输出的完整流程 |
各模块间的调用关系:main.py
作为入口,先实例化TextProcessor
处理原文和抄袭版文本,再将处理后的文本传入SimilarityCalculator
计算重复率,最后将结果写入输出文件。
2. 关键算法设计
本项目采用余弦相似度算法计算文本重复率,核心思路如下:
- 文本预处理:将中文文本转换为可计算的向量形式,步骤包括:
- 中文分词:使用
jieba
库对文本进行分词(如“今天是星期天”分为“今天/是/星期天”)。 - 停用词过滤:移除无实际语义的词汇(如“的”“是”“要”等),避免干扰计算。
- 词频统计:统计每个有效词汇在文本中的出现次数,形成词频字典。
- 中文分词:使用
- 向量构建:以两个文本的所有有效词汇为维度,分别构建原文和抄袭版文本的词频向量。
- 余弦相似度计算:通过向量点积公式计算两个向量的余弦值,该值即为文本重复率。
3. 算法独到之处
- 针对性预处理:针对中文文本特点,优化分词逻辑,补充中文停用词表(涵盖常见虚词、助词),减少无效计算。
- 高效向量计算:使用Python字典存储词频,避免稀疏矩阵的空间浪费,同时通过集合操作快速获取两个文本的共同词汇,提升计算效率。
- 结果精度控制:通过四舍五入将结果精确到小数点后两位,满足题目输出规范。
三、计算模块接口的性能改进
1. 改进思路
初始版本中,文本预处理和向量计算存在两处性能瓶颈:
- 分词效率低:未使用
jieba
库的高效分词模式,处理长文本时耗时较长。 - 向量计算冗余:计算向量模长时重复遍历词频字典,增加不必要的循环开销。
改进措施:
- 启用
jieba
库的“全模式”分词(jieba.cut(text, cut_all=False)
),在保证分词准确性的同时提升速度。 - 合并向量模长计算逻辑,在统计词频时同步记录平方和,避免重复遍历。
2. 性能分析图
使用cProfile
工具对改进前后的代码进行性能分析,核心函数耗时对比如下(单位:秒):
函数 | 改进前耗时 | 改进后耗时 | 优化幅度 |
---|---|---|---|
TextProcessor.process_text |
0.82 | 0.35 | 57.3% |
SimilarityCalculator.calculate |
0.45 | 0.18 | 60.0% |
性能分析截图(此处为示意,实际需附VS Profiling或cProfile生成的图表):
- 改进前:
process_text
函数占总耗时的45%,主要瓶颈在分词环节。 - 改进后:
process_text
函数占比降至28%,calculate
函数占比降至12%,整体程序运行时间从2.1秒缩短至0.8秒。
3. 消耗最大的函数
改进后,程序中消耗最大的函数为TextProcessor.process_text
,主要原因是中文分词和停用词过滤需遍历文本中的每个字符和词汇,属于计算密集型操作。但通过上述优化,其耗时已降至可接受范围,在处理10万字文本时可在1秒内完成。
四、计算模块部分单元测试展示
1. 单元测试代码
使用unittest
框架编写测试用例,覆盖文本预处理、相似度计算、异常处理等场景,核心测试代码如下:
import unittest
from text_processor import TextProcessor
from similarity_calculator import SimilarityCalculator
class TestTextProcessor(unittest.TestCase):
def setUp(self):
self.processor = TextProcessor()
# 测试正常文本预处理
def test_process_normal_text(self):
text = "今天是星期天,天气晴,今天晚上我要去看电影。"
result = self.processor.process_text(text)
self.assertEqual(result, ["今天", "星期天", "天气", "晴", "晚上", "看电影"])
# 测试含特殊字符的文本预处理
def test_process_special_char_text(self):
text = "今天是周天!天气晴朗...我晚上要去看电影~"
result = self.processor.process_text(text)
self.assertEqual(result, ["今天", "周天", "天气", "晴朗", "晚上", "看电影"])
class TestSimilarityCalculator(unittest.TestCase):
def setUp(self):
self.calculator = SimilarityCalculator()
self.processor = TextProcessor()
# 测试完全重复文本
def test_calculate_100_percent_similar(self):
text1 = "今天是星期天,天气晴,今天晚上我要去看电影。"
text2 = "今天是星期天,天气晴,今天晚上我要去看电影。"
processed1 = self.processor.process_text(text1)
processed2 = self.processor.process_text(text2)
similarity = self.calculator.calculate(processed1, processed2)
self.assertAlmostEqual(similarity, 1.00, places=2)
# 测试部分重复文本
def test_calculate_partial_similar(self):
text1 = "今天是星期天,天气晴,今天晚上我要去看电影。"
text2 = "今天是周天,天气晴朗,我晚上要去看电影。"
processed1 = self.processor.process_text(text1)
processed2 = self.processor.process_text(text2)
similarity = self.calculator.calculate(processed1, processed2)
self.assertAlmostEqual(similarity, 0.85, places=2)
# 更多测试用例(省略,包括空文本、超长文本、纯特殊字符文本等场景)
if __name__ == "__main__":
unittest.main()
2. 测试用例设计思路
测试用例编号 | 测试场景 | 输入文本示例 | 预期输出(重复率) | 测试目的 |
---|---|---|---|---|
1 | 完全重复文本 | 原文:今天是星期天...;抄袭版:今天是星期天... | 1.00 | 验证完全重复场景的准确性 |
2 | 部分重复(同义词替换) | 原文:天气晴;抄袭版:天气晴朗 | 0.85左右 | 验证语义相近文本的计算逻辑 |
3 | 空文本输入 | 原文:"";抄袭版:"今天去看电影" | 0.00 | 验证边界场景(空文本) |
4 | 纯特殊字符文本 | 原文:"!@#¥%...";抄袭版:"!@#¥%..." | 0.00 | 验证无有效词汇场景 |
5 | 超长文本(10万字) | 原文:长篇小说片段;抄袭版:相同片段(修改5%内容) | 0.95左右 | 验证性能与准确性 |
6 | 文本顺序打乱 | 原文:A→B→C;抄袭版:B→A→C | 0.98左右 | 验证语序对结果的影响 |
7 | 文本新增大量内容 | 原文:A;抄袭版:A+B(B为新增内容,占比50%) | 0.71左右 | 验证新增内容对重复率的影响 |
8 | 文本删除大量内容 | 原文:A+B;抄袭版:A(删除B,占比50%) | 0.71左右 | 验证删除内容对重复率的影响 |
9 | 中英文混合文本 | 原文:今天去看movie;抄袭版:今天去看film | 0.80左右 | 验证多语言场景的处理能力 |
10 | 含停用词的文本 | 原文:我今天要去买苹果;抄袭版:今天我要去买苹果 | 1.00 | 验证停用词过滤的有效性 |
3. 测试覆盖率截图
使用coverage
工具生成测试覆盖率报告,结果显示:
- 代码行覆盖率:98%
- 函数覆盖率:100%
- 分支覆盖率:95%
(此处需附覆盖率工具生成的截图,示意:text_processor.py
和similarity_calculator.py
的核心函数均被覆盖,仅个别异常处理分支覆盖率略低)
五、计算模块部分异常处理说明
1. 异常设计目标
异常类型 | 设计目标 |
---|---|
FileNotFoundError |
当输入文件路径不存在时,捕获异常并提示用户“文件路径错误,请检查路径是否正确” |
PermissionError |
当程序无文件读写权限时,提示用户“无文件读写权限,请检查文件权限设置” |
UnicodeDecodeError |
当文件编码格式不支持(非UTF-8/GBK)时,提示用户“文件编码错误,请使用UTF-8或GBK编码” |
ValueError |
当命令行参数数量不足3个时,提示用户“参数错误,正确用法:python main.py 原文路径 抄袭版路径 输出路径” |
MemoryError |
当处理超大型文件导致内存不足时,提示用户“内存不足,无法处理该文件” |
2. 异常测试样例
以FileNotFoundError
为例,测试代码如下:
import unittest
import sys
from main import main
class TestMainExceptions(unittest.TestCase):
def test_file_not_found(self):
# 构造错误的文件路径(不存在的文件)
sys.argv = ["main.py", "C:/nonexistent/orig.txt", "C:/nonexistent/plagiarism.txt", "C:/output/ans.txt"]
# 捕获程序输出的错误提示
with self.assertRaises(SystemExit) as cm:
main()
# 验证错误提示是否正确
self.assertIn("文件路径错误,请检查路径是否正确", str(cm.exception))
if __name__ == "__main__":
unittest.main()
错误场景:用户输入的原文路径为C:/nonexistent/orig.txt
(该路径下无此文件),程序运行时触发FileNotFoundError
,捕获后输出错误提示并退出,避免程序崩溃。