第一次个人编程作业
一、PSP表格
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 10 | 15 |
| · Estimate | · 估计这个任务需要多少时间 | 10 | 15 |
| Development | 开发 | 420 | 500 |
| · Analysis | · 需求分析 (包括学习新技术) | 70 | 90 |
| · Design Spec | · 生成设计文档 | 25 | 30 |
| · Design Review | · 设计复审 | 20 | 20 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 15 | 30 |
| · Design | · 具体设计 | 50 | 80 |
| · Coding | · 具体编码 | 160 | 160 |
| · Code Review | · 代码复审 | 30 | 30 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 50 | 60 |
| Reporting | 报告 | 190 | 188 |
| · Test Repor | · 测试报告 | 60 | 70 |
| · Size Measurement | · 计算工作量 | 10 | 8 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 120 | 110 |
| · 合计 | 620 | 703 |
二、计算模块接口的设计与实现过程
1.代码组织架构
1.1模块结构
PlagiarismCheck/
├── main.py # 主程序入口
├── text_processor.py # 文本处理模块
├── similarity_calculator.py # 相似度计算模块
├── file_handler.py # 文件操作模块
└── test_main.py # 单元测试
1.2核心类与函数设计
1.2.1 TextProcessor 类 - --文本预处理
class TextProcessor:
def preprocess_text(text: str) -> List[str]
def remove_punctuation(text: str) -> str
def chinese_segmentation(text: str) -> List[str]
def english_tokenization(text: str) -> List[str]
1.2.2. SimilarityCalculator 类 - -相似度计算
class SimilarityCalculator:
def get_tfidf_vectors(text1: str, text2: str) -> Tuple[List[float], List[float]]
def cosine_similarity(vec1: List[float], vec2: List[float]) -> float
def calculate_similarity(original: str, copied: str) -> float
1.2.3. FileHandler 类 - -文件操作
class FileHandler:
def read_file(file_path: str) -> str
def write_result(result: float, output_path: str)
def validate_file_path(file_path: str) -> bool
1.3关键函数流程图

三、核心算法关键与独到之处
1. 算法关键
本模块核心采用 “TF-IDF 特征提取 + 余弦相似度度量” 的经典文本相似度算法,除此之外还具有以下算法核心创新点:
1.中英文混合分词:独创的中英文混合处理技术:中文字符按字分割,英文按单词分割;实时语言检测机制:无需预先指定文本语言类型;智能处理数字和特殊字符,保留有效信息
2.TF 计算和IDF 平滑处理:采用 “词频 / 文本总词数” 的归一化方式,避免长文本词频偏高的偏差;并且使用公式 log((N+1)/(DF+1)) + 1(N 为文档总数,DF 为含该词的文档数),避免 DF=0 时 IDF 无穷大的问题,同时增强低频词的区分度:
/平滑处理避免除零错误/
idf = math.log((N + 1) / (df + 1)) + 1
/归一化处理确保数值稳定性/
tf_idf = (tf / total_words) * idf
3.余弦相似度:通过向量点积与模长比值,量化两个文本的 “方向一致性”,值越接近 1 表示相似度越高,0 表示完全无关。
2. 独到设计
1.自适应文本清洗:除英文标点外,额外包含中文标点(如 “,。!”),解决中文文本预处理不彻底的问题;
2.空文本容错:在compute_text_similarity和calculate_tfidf_vectors中均添加空文本判断,避免因输入文本为空导致的除零错误或向量计算异常;
3.结果范围约束:通过max(0.0, min(1.0, similarity_score))确保相似度结果始终在 [0,1] 区间,符合业务预期。
四、计算模块接口的性能改进

通过性能分析图发现,preprocess_text 和 calculate_tfidf_vectors 是性能瓶颈,耗时最长。
性能优化
1.preprocess_text 函数优化
原始问题分析:原实现采用逐字符遍历 + 条件判断的方式进行分词,存在三个主要问题:
- 对每个字符进行多次类型判断(中文字符 / 英文字符 / 特殊字符)
- 频繁进行字符串拼接操作(current_word += char)
- 空白字符处理逻辑冗余
优化方案:
def preprocess_text(text): """优化后的文本预处理函数""" # 去除标点符号(保留原有逻辑) punctuation_chars = string.punctuation + ',。!?;:“”‘’【】()《》' text = text.translate(str.maketrans('', '', punctuation_chars)) text = text.lower() # 关键优化:使用正则表达式一次性匹配所有有效词汇 # 匹配规则:中文字符([\u4e00-\u9fff])或连续的英文/数字([a-zA-Z0-9]+) pattern = re.compile(r'[\u4e00-\u9fff]|[a-zA-Z0-9]+') return pattern.findall(text)
优化原理:
- 利用正则表达式引擎的 C 语言底层实现,替代 Python 原生循环
- 将多轮字符判断合并为一次正则匹配,时间复杂度从 O (n) 降至 O (1) 级
- 消除字符串拼接操作,减少内存分配开销
2.calculate_tfidf_vectors 函数优化
原始问题分析:
- 单独遍历词汇表计算文档频率(DF),存在二次遍历
- 每次计算 TF 时重复调用len(words_text1),产生冗余计算
- 词汇表构建方式低效(set(words_text1 + words_text2))
优化方案:
def calculate_tfidf_vectors(text1, text2): """优化后的TF-IDF向量计算函数""" words_text1 = preprocess_text(text1) words_text2 = preprocess_text(text2) # 优化1:预计算文本长度,避免重复调用len() len_text1 = len(words_text1) len_text2 = len(words_text2) # 优化2:合并词汇表构建与文档频率计算 vocabulary = set() document_frequency = Counter() # 处理第一个文本 for word in words_text1: vocabulary.add(word) document_frequency[word] = 1 # 标记至少在文本1中出现 # 处理第二个文本 for word in words_text2: vocabulary.add(word) if word in document_frequency: document_frequency[word] = 2 # 已在文本1中出现,现在文本2也出现 else: document_frequency[word] = 1 # 仅在文本2中出现 # 优化3:预计算IDF值并复用 total_documents = 2 idf_cache = {} for word in vocabulary: df = document_frequency[word] idf_cache[word] = math.log((total_documents + 1) / (df + 1)) + 1 # 计算TF-IDF向量 tf1 = Counter(words_text1) tf2 = Counter(words_text2) vector_text1 = [] vector_text2 = [] for word in vocabulary: idf = idf_cache[word] # 优化4:合并条件判断,减少分支跳转 vector_text1.append((tf1[word] / len_text1) * idf if len_text1 > 0 else 0) vector_text2.append((tf2[word] / len_text2) * idf if len_text2 > 0 else 0) return vector_text1, vector_text2
优化原理:
- 预计算文本长度(len_text1/len_text2),将 O (1) 操作从循环中移出
- 合并词汇表构建与 DF 计算,减少一次完整遍历(从 2 次遍历变为 2 次线性扫描)
- 增加 IDF 缓存(idf_cache),避免重复计算对数函数
五、计算模块部分单元测试展示
1. 文本预处理测试
def test_preprocess_text_chinese(self): """测试中文文本预处理 - 验证中文字符正确分割""" text = "你好,世界!这是一段中文文本。" result = preprocess_text(text) expected = ['你', '好', '世', '界', '这', '是', '一', '段', '中', '文', '文', '本'] self.assertEqual(result, expected)
测试数据构造思路:
- 使用包含中文标点的完整句子
- 验证分词算法对中文的处理准确性
2. 混合语言处理测试
def test_preprocess_text_mixed(self): """测试中英文混合文本预处理 - 验证多语言混合处理能力""" text = "Hello你好,World世界!Python编程" result = preprocess_text(text) expected = ['hello', '你', '好', 'world', '世', '界', 'python', '编', '程'] self.assertEqual(result, expected)
测试数据构造思路:
- 构造中英文交替出现的复杂文本
- 文本包含标点符号和大小写字母
3. 边界条件测试
def test_compute_text_similarity_empty(self): """测试空文本的相似度计算 - 验证边界条件处理""" similarity = compute_text_similarity("", "测试文本") self.assertEqual(similarity, 0.0) similarity = compute_text_similarity("测试文本", "") self.assertEqual(similarity, 0.0) similarity = compute_text_similarity("", "") self.assertEqual(similarity, 0.0)
测试数据构造思路:
- 测试空字符串输入的各种组合
- 确保空文本返回合理的默认值(0.0)
4. 数学计算准确性测试
def test_cosine_similarity_identical(self): """测试相同向量的余弦相似度 - 验证数学计算正确性""" vec1 = [1, 2, 3] vec2 = [1, 2, 3] result = calculate_cosine_similarity(vec1, vec2) self.assertAlmostEqual(result, 1.0, places=5)
测试数据构造思路:
- 使用简单的数值向量便于验证
- 相同向量应该得到相似度1.0
- 测试数学计算的精确度
测试覆盖率报告分析
1.总体覆盖率统计

2.详细覆盖率分析
main.py 文件覆盖率:86%

1. preprocess_text(文本预处理函数)
覆盖率:87%
未覆盖原因推测:该函数需处理 “中英文混合、含特殊字符、长文本” 等多种场景,可能部分边缘场景(如极长文本的特殊分割、罕见标点组合)的测试用例缺失,导致 3 行代码未被执行。
2. calculate_tfidf_vectors(TF-IDF 向量计算函数)
覆盖率:93%
未覆盖原因推测:函数涉及 “词汇表构建、TF 计算、IDF 平滑” 等多步骤,可能在 “文档频率(DF)为 0 的极端情况” 或 “单文本全重复词汇” 场景下,存在 2 行代码未被测试覆盖。
3. calculate_cosine_similarity(余弦相似度计算函数)
覆盖率:100%
分析:测试用例覆盖了 “相同向量(相似度 1.0)、正交向量(相似度 0.0)、含零向量” 等核心场景,所有 6 行代码均被执行,逻辑验证充分。
4. compute_text_similarity(文本相似度计算函数)
覆盖率:100%
分析:测试用例覆盖了 “完全相同、完全不同、部分相似、空文本” 等场景,5 行核心逻辑均被执行,验证了从 “文本输入→向量生成→相似度计算” 的完整链路。
5. main(主函数)
覆盖率:82%
未覆盖原因推测:主函数涉及 “命令行参数解析、多文件 IO 调度、结果打印” 等流程,可能在 “参数数量异常(如 5 个参数)” 或 “文件读取中途失败” 等边缘流程中,存在 2 行代码未被测试覆盖。
test_main.py 文件覆盖率:99%
test_main.py是单元测试文件,大部分测试用例的覆盖率为 100%,仅最后一行(no function)显示有 1 行未覆盖,推测是测试文件中某段辅助代码(如临时变量清理、导入逻辑)未被完全执行,整体对核心测试逻辑影响极小。

六、异常处理设计
本模块围绕 “文件 IO 操作”“文本数据有效性”“命令行参数” 三大核心场景,设计了 6 类针对性异常处理机制。所有异常均遵循 “精准捕获 - 清晰提示 - 安全退出” 原则,确保程序在异常输入下不崩溃、用户能快速定位问题,同时通过单元测试覆盖所有异常场景,验证处理逻辑的有效性。
异常分类与详细说明
1. 文件不存在异常(FileNotFoundError)
设计目标
当用户指定的原文 / 抄袭文 / 输出文件路径错误(如文件被删除、路径拼写错误)时,主动捕获异常并提示 “文件不存在”,避免程序因 “找不到文件” 抛出未处理的系统异常,同时以退出码1正常退出,符合命令行工具的错误码规范。
触发场景
用户输入命令python main.py original.txt copied.txt output.txt,但copied.txt已被手动删除;
用户误写文件路径,如将data/original.txt写成data/orignial.txt(拼写错误)。
单元测试样例
def test_read_file_content_not_exists(self): with self.assertRaises(SystemExit): read_file_content("nonexistent_file.txt")
异常处理核心代码(read_file_content函数)
def read_file_content(file_path): try: with open(file_path, 'r', encoding='utf-8') as file: return file.read() except FileNotFoundError: print(f"错误:文件 {file_path} 不存在") # 清晰提示错误原因 sys.exit(1) # 退出码1表示“文件相关错误”
2. 文件读取通用异常(Exception)
设计目标
覆盖除 “文件不存在” 外的所有文件读取错误(如权限不足、文件损坏、编码错误),捕获未知异常并打印具体错误信息,帮助用户排查非路径问题的读取故障(如 “权限被拒绝”“文件是二进制而非文本”)。
触发场景
- 用户试图读取系统保护文件(如 Windows 的C:\Windows\System32\config\SAM),因权限不足无法打开;
- 用户误将图片文件(如image.png)作为 “原文文件” 输入,读取时因编码不兼容(非 UTF-8)抛出UnicodeDecodeError;
- 文件因磁盘错误损坏,打开时抛出IOError。
异常处理核心代码(write_similarity_result函数)
def write_similarity_result(similarity_score, output_path): try: with open(output_path, 'w', encoding='utf-8') as file: file.write(f"{similarity_score:.2f}") except Exception as error: print(f"写入文件时出错:{error}") # 提示具体错误(如“Permission denied”“No space left on device”) sys.exit(1)
3.命令行参数数量异常(逻辑判断)
设计目标
当用户输入的命令行参数数量不等于 4 个(正确格式:python main.py [原文] [抄袭文] [输出])时,主动提示正确用法,避免因参数缺失 / 多余导致程序后续读取路径时抛出 “索引越界” 异常。
触发场景
用户忘记输入输出文件路径,命令为python main.py original.txt copied.txt(仅 3 个参数);
用户多输入一个冗余参数,命令为python main.py original.txt copied.txt output.txt extra.txt(5 个参数)。
异常处理核心代码(main函数)
def main(): # 检查命令行参数数量(sys.argv[0]为脚本名,实际参数需3个,总长度4) if len(sys.argv) != 4: print("用法: python main.py [原文文件] [抄袭版论文文件] [答案文件]") # 清晰提示正确用法 sys.exit(1)
4. 零向量异常(逻辑判断)
设计目标
当文本预处理后无有效词汇(如文件内容全为标点符号、特殊字符),导致 TF-IDF 向量为 “零向量”(所有维度值均为 0)时,避免余弦相似度计算中出现 “除以零错误“,直接返回相似度0.0。
触发场景
用户输入的文件内容全为标点符号(如 “!@#¥%……&*()”);
文件内容仅含特殊字符,无中英文 / 数字有效词汇。
单元测试样例
def test_edge_case_special_characters(self): """测试特殊字符处理:验证零向量异常处理逻辑""" # 输入全为特殊字符,预处理后无有效词汇 text = "!@#$%^&*()_+{}|:\"<>?[]\\;',./" processed = preprocess_text(text) self.assertEqual(processed, []) # 预处理后为空列表(无有效词汇) # 计算相似度:两个全特殊字符文本,预期返回0.0 similarity = compute_text_similarity(text, text) self.assertEqual(similarity, 0.0)
异常处理核心代码(calculate_cosine_similarity函数)
def calculate_cosine_similarity(vector1, vector2): dot_product = sum(c1 * c2 for c1, c2 in zip(vector1, vector2)) magnitude_vector1 = math.sqrt(sum(c**2 for c in vector1)) magnitude_vector2 = math.sqrt(sum(c**2 for c in vector2)) # 处理零向量:避免除以零错误,返回0.0 if magnitude_vector1 == 0 or magnitude_vector2 == 0: return 0.0 return dot_product / (magnitude_vector1 * magnitude_vector2)

浙公网安备 33010602011771号