第一次个人编程作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class34Grade23ComputerScience/homework/13477
这个作业的目标 <通过论文查重系统的工程化实现学会代码测试以达到质量保障>
我的github账号 https://github.com/Yannnnn012/3223004777

一、PSP表格(包括预估与实际耗时)

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

二、计算模块接口的设计与实现过程

2.1 代码组织结构设计

2.1.1 模块化架构设计

本论文查重系统采用函数式编程范式,将系统划分为5个核心功能模块:

模块名称 主要函数 功能职责
文件IO模块 read_file() 文本文件读取和编码处理
文本预处理模块 preprocess_text() 文本清洗和中文分词
向量计算模块 calculate_cosine_similarity() 词频向量构建和相似度计算
业务流程模块 calculate_similarity() 整合整个查重流程
主控模块 main() 命令行参数处理和程序入口

2.1.2 函数关系架构

image

2.2 关键算法流程详解

2.2.1 文本预处理流程

image

2.1.2 余弦相似度计算流程

image

2.3 算法关键技术与独到之处

2.3.1 核心技术实现

  1. 中文文本处理优化
  • 采用Jieba分词库进行精准的中文词语切分
  • 使用正则表达式r'[^\w\s]'有效去除中文标点符号
  • 支持中英文混合文本的处理能力
  1. 向量空间模型应用
  • 将文本转换为词频向量表示
  • 基于TF(词频)的文本特征提取
  • 使用集合操作确保向量维度一致性
  1. 余弦相似度算法
  • 数学公式:cosθ = (A·B) / (||A|| × ||B||)
  • 自动处理向量模长为零的边界情况
  • 返回标准化相似度值(0-1范围)

2.3.2 算法独到之处

  1. 高效的分词预处理
# 一体化文本清洗和分词
text = re.sub(r'[^\w\s]', '', text)  # 去除标点
words = jieba.cut(text)              # 中文分词
return list(words)                   # 返回词语列表

将文本清洗和分词操作合并,减少中间数据处理步骤,提高效率。

  1. 智能的向量维度处理
# 自动统一向量维度
all_words = set(vec1.keys()).union(set(vec2.keys()))
vector1 = [vec1.get(word, 0) for word in all_words]
vector2 = [vec2.get(word, 0) for word in all_words]

自动获取两个文本的所有词汇并集,确保向量维度一致,避免维度不匹配问题。

  1. 鲁棒的异常处理机制
# 全面的边界情况处理
if magnitude1 == 0 or magnitude2 == 0:
    return 0.0  # 处理除零错误

对零向量情况进行特殊处理,保证算法的稳定性和可靠性。

三、计算模块接口部分的性能改进

3.1 性能优化时间记录

优化阶段 花费时间 主要工作内容
初始版本开发 1小时 基础功能实现,完成核心算法
第一轮性能分析 20分钟 使用SnakeViz进行性能分析,识别瓶颈
分词优化 10分钟 优化Jieba分词加载和缓存机制
内存使用优化 10分钟 减少临时对象创建,使用生成器
向量计算优化 20分钟 优化集合操作和循环计算
第二轮性能测试 20分钟 验证优化效果,进行对比测试
总计优化时间 2小时20分钟

3.2 性能分析结果

基于SnakeViz性能分析数据,识别出以下关键性能瓶颈:
image

函数位置 执行时间 占比 严重程度
main.py:1(<module>) 0.820 s 100% 严重
main.py:121(main) 0.427 s 52%
__init__.py:289(cut) 0.426 s 48%
  • 主函数(main) 执行时间较长(0.427秒),可能存在算法效率问题

3.3 改进优化

优化计算两个词频向量的余弦相似度
原版:NumPy数组创建有额外开销
优化:连续内存访问,缓存友好

修改后:

def calculate_cosine_similarity(vec1, vec2):
    """
    计算两个词频向量的余弦相似度
    余弦相似度公式:cosθ = (A·B) / (||A|| * ||B||)
    Args:
        vec1 (dict): 第一个文本的词频字典 {词语: 频率}
        vec2 (dict): 第二个文本的词频字典 {词语: 频率}
    Returns:
        float: 余弦相似度值,范围[0, 1]
    """
    # 获取两个字典的所有词汇的并集,确保向量维度一致
    all_words = set(vec1.keys()).union(set(vec2.keys()))

    # 根据词汇表创建数值向量,缺失的词语频率为0
    vector1 = [vec1.get(word, 0) for word in all_words]
    vector2 = [vec2.get(word, 0) for word in all_words]

    # 计算两个向量的点积(对应位置相乘后求和)
    dot_product = sum(v1 * v2 for v1, v2 in zip(vector1, vector2))

    # 计算两个向量的模长(欧几里得范数)
    magnitude1 = math.sqrt(sum(v * v for v in vector1))
    magnitude2 = math.sqrt(sum(v * v for v in vector2))

    # 处理除零错误:如果任一向量模长为0,相似度为0
    if magnitude1 == 0 or magnitude2 == 0:
        return 0.0

    # 返回余弦相似度值
    return dot_product / (magnitude1 * magnitude2)

改进后效果:
image

其他优化举例:

分词链路加速 → 预编译正则 + 一次性全文本切分

import re
import jieba

# 只编译一次,避免每句话都重新编译
PUNCT_RE = re.compile(r'[^\w\s]+')

def preprocess_text(text: str):
    """优化后:全文本清洗后一次性 jieba.cut,减少 Python-C 往返"""
    text = PUNCT_RE.sub('', text)      # 预编译正则
    words = jieba.cut(text, cut_all=False)   # 整段直接切
    return list(words)                # 返回 list 给 Counter

但是效果都很差

四、计算模块部分单元测试展示

单元测试示例:文本预处理功能

4.1 测试函数test_preprocess_text_normal()

测试目标:验证中文文本分词和标点去除功能

构造测试数据思路

  • 使用包含多种标点符号的中文文本
  • 包含连续文本和分隔文本
  • 覆盖常见的中文表达方式

测试代码

    def test_preprocess_text_normal(self):
        """
        测试正常文本预处理功能
        """
        test_text = "明天早课,要早点睡,不然起不来!"
        result = preprocess_text(test_text)
        acceptable_results = ['明天', '早课', '早点', '睡', '不然', '起不来', '要']

        # 检查结果是否在可接受的范围内
        self.assertTrue(
            set(result) == set(acceptable_results) or
            f"分词结果 {result} 不在可接受范围内"
        )

        # 验证标点符号被正确去除
        punctuation = [',', '。', '!', '?', ';', ':']
        for punc in punctuation:
            self.assertNotIn(punc, result, f"结果中不应包含标点符号: {punc}")

4.2 测试函数test_preprocess_text_with_english()

测试目标:验证中英文混合文本的分词和标点处理功能

构造测试数据思路

  • 使用包含中英文混合的文本
  • 包含英文单词、缩写和专有名词
  • 包含数字和特殊符号
  • 覆盖技术文档和日常用语场景

测试代码

    def test_preprocess_text_english(self):
        """
        测试中英文混合文本的预处理
        """
        test_text = "Hello, 我的世界!Python编程很有趣。"
        result = preprocess_text(test_text)

        # 验证关键词语存在
        self.assertIn('世界', result)  # 中文分词
        self.assertIn('Python', result)  # 英文保留
        self.assertIn('编程', result)  # 中文分词

【测试覆盖率图】

其中,单元测试得到的测试覆盖率为99%

image

五、计算模块部分异常处理说明

  1. 文件读取异常处理
    设计目标:处理文件不存在、权限不足、编码错误等文件读取问题,确保程序在文件操作失败时能够优雅退出并提供明确的错误信息。
    异常场景:文件不存在或无法访问
    def test_read_file_not_exist(self):
        """
        测试读取不存在文件时的异常处理
        """
        non_existent_file = os.path.join(self.test_dir, "non_existent.txt")
        # 会引发SystemExit异常
        with self.assertRaises(SystemExit):
            read_file(non_existent_file)
  1. 空文本处理异常
    设计目标:处理空文本或全标点符号文本的情况,避免在计算相似度时出现除零错误。
    异常场景:输入文本为空字符串
def test_calculate_similarity_empty(self):
    """
    测试空文本的相似度计算
    场景:用户提供了空文件或空文本
    """
    similarity = calculate_similarity("", "")
    self.assertEqual(similarity, 0.0)

def test_calculate_similarity_mixed_empty(self):
    """
    测试一个空文本和一个正常文本的相似度计算
    场景:其中一个输入文件为空
    """
    similarity = calculate_similarity("正常文本", "")
    self.assertEqual(similarity, 0.0)
  1. 零向量处理异常
    设计目标:处理词频向量为零向量的情况,避免余弦相似度计算时的除零错误。
    异常场景:预处理后没有有效词汇(如全标点符号文本)
def test_calculate_cosine_similarity_empty(self):
    """
    测试空向量的余弦相似度计算
    场景:文本经过预处理后没有有效词语
    """
    similarity = calculate_cosine_similarity({}, {})
    self.assertAlmostEqual(similarity, 0.0, places=7)
  1. 命令行参数异常
    设计目标:处理命令行参数数量不正确的情况,提供使用说明。
    异常场景:用户提供的参数数量不正确
def test_integration_wrong_arguments(self):
    """
    测试命令行参数错误的情况
    场景:用户忘记提供必要的文件路径参数
    """
    cmd = [sys.executable, 'main.py', 'only_one_argument.txt']
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

    self.assertNotEqual(result.returncode, 0)
    error_output = result.stderr + result.stdout
    self.assertIn("Usage", error_output)
  1. 文件写入异常
    设计目标:处理结果文件写入失败的情况,如磁盘已满、权限不足等。
    异常场景:(需要在主程序中模拟,这里提供思路)
def test_file_write_permission_denied(self):
    """
    测试文件写入权限不足的情况
    场景:输出目录没有写入权限
    """
    # 创建一个没有写入权限的目录
    read_only_dir = os.path.join(self.test_dir, "readonly")
    os.makedirs(read_only_dir)
    os.chmod(read_only_dir, 0o444)  # 只读权限
    output_file = os.path.join(read_only_dir, "result.txt")
    
    # 这里需要修改main函数来捕获并测试这个异常
    # 实际测试中可以通过mock来模拟写入失败
  1. 内存溢出异常(预防性处理)
    设计目标:处理极大文本文件导致的内存溢出问题。
    异常场景:(需要在主程序中添加处理,这里提供设计思路)
def read_large_file(file_path, max_size=100*1024*1024):  # 100MB限制
    """
    安全读取大文件,避免内存溢出
    """
    file_size = os.path.getsize(file_path)
    if file_size > max_size:
        raise MemoryError(f"文件过大 ({file_size} bytes),超过最大限制 {max_size} bytes")
    
    # 正常读取文件内容...
  1. 编码异常处理
    设计目标:处理非UTF-8编码的文件,提供友好的错误信息。
    异常场景:(需要扩展read_file函数)
def test_read_file_wrong_encoding(self):
    """
    测试读取非UTF-8编码文件
    场景:用户提供了GBK或其他编码的文件
    """
    test_file = os.path.join(self.test_dir, "gbk_file.txt")
    # 创建一个GBK编码的文件
    with open(test_file, 'w', encoding='gbk') as f:
        f.write("中文内容")
    
    # 当前实现会失败,可以扩展read_file函数来处理多种编码
    with self.assertRaises(SystemExit):
        read_file(test_file)
posted @ 2025-09-17 18:54  wenyn  阅读(102)  评论(0)    收藏  举报