软工第二次作业

这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class12Grade23ComputerScience/homework/13468
这个作业的目标 <完成论文查重算法的编程实现,涵盖代码开发(含 PSP 时间记录、多语言支持、代码质量与性能优化)、Github 版本管理、单元测试(至少 10 个用例)及博客报告(模块设计、性能改进、测试说明等)全流程,考察编程、工程管理和问题解决能力。>

一.github文件夹链接
https://github.com/JasonLong9/JasonLong9/tree/main/3123004305

二.作业需求
题目:论文查重

描述如下:

设计一个论文查重算法,给出一个原文文件和一个在这份原文上经过了增删改的抄袭版论文的文件,在答案文件中输出其重复率。

原文示例:今天是星期天,天气晴,今天晚上我要去看电影。
抄袭版示例:今天是周天,天气晴朗,我晚上要去看电影。
要求输入输出采用文件输入输出,规范如下:

从命令行参数给出:论文原文的文件的绝对路径。
从命令行参数给出:抄袭版论文的文件的绝对路径。
从命令行参数给出:输出的答案文件的绝对路径。
我们提供一份样例,课堂上下发,上传到班级群,使用方法是:orig.txt是原文,其他orig_add.txt等均为抄袭版论文。

注意:答案文件中输出的答案为浮点型,精确到小数点后两位

三.PSP表格

Personal Software Process Stages 预估耗时(min) 实际耗时(min) 说明
Planning - Estimate 215 235 汇总所有阶段的时间估算与实际
Development - Analysis 30 35 阅读题目、学习需求与 I/O 规范
Development - Design Spec 25 25 规划 CLI 接口、shingle+Jaccard 算法与测试结构
Development - Design Review 0 0 本次未开展独立设计评审
Development - Coding Standard 10 10 制定命名规范、行长/尾随空格检查(quality_gate)
Development - Design 15 15 细化模块划分(tokenize、build_shingles、jaccard 等)
Development - Coding 80 90 实现查重模块、CLI、PSP/README/样例等
Development - Code Review 0 0 暂无额外代码复审
Development - Test 25 30 设计/实现 11 个单元测试,运行 trace/coverage
Reporting - Test Report 20 15 记录测试方式、覆盖率摘要(collect_coverage)
Reporting - Size Measurement 5 5 统计非空行、任务规模及样例描述
Reporting - Postmortem & PIP 20 25 编写 README、profiling_report、总结改进点
合计 215 235 总耗时一致,覆盖所有 PSP 阶段

四.模块接口的设计与实现过程

模块接口设计与实现说明(论文查重)

1. 代码组织概览

  • 入口模块 main.py
    • 负责解析命令行参数(原文、抄袭版、输出答案路径,以及可选 --window)。
    • 将参数传递给 plagiarism_checker.main(argv),保持入口极简,便于课堂测试统一运行方式。
  • 核心模块 plagiarism_checker.py
    • 包含一个自定义异常 PlagiarismError,用于描述文件读写异常情况。
    • 函数划分:
      1. tokenize(text: str):分词与规范化。
      2. build_shingles(words, window):构建词级 n-gram 集合。
      3. jaccard_similarity(a, b):计算 shingle 集合的 Jaccard 相似度。
      4. compute_similarity(original, suspect, window):整合上述函数,得到相似度分数。
      5. similarity_from_files(original_path, suspect_path, window):从文件读取并计算分数。
      6. format_percentage(score):将分数格式化为两位小数的百分比字符串。
      7. main(argv):命令行接口,解析参数、调用 similarity_from_files、输出答案。
    • 函数之间通过清晰的接口传递(字符串、词列表、shingle 集合、浮点分数),没有隐藏状态,便于单元测试。
  • 测试与工具模块
    • tests/test_plagiarism_checker.py:11 个单元测试,覆盖各核心函数及 CLI 边界情况。
    • run_tests.py / collect_coverage.py:分别用于快速运行测试与生成 coverage 摘要。
    • quality_gate.py:统一检查关键 Python 文件的行长与尾随空格,保证代码风格。

2. 函数关系与流程(文字流程图)

main.py → plagiarism_checker.main(argv)
            ├─ parse_args → 获取 original/suspect/output/--window
            ├─ similarity_from_files
            │    ├─ Path.read_text() ×2 → 原文 / 抄袭文串
            │    ├─ compute_similarity
            │    │    ├─ tokenize(original)
            │    │    ├─ tokenize(suspect)
            │    │    ├─ build_shingles(words_a, window)
            │    │    ├─ build_shingles(words_b, window)
            │    │    └─ jaccard_similarity(shingles_a, shingles_b)
            │    └─ 返回分数(0~1)
            ├─ format_percentage(score)
            ├─ 写入 output 文件(UTF-8,两位小数)
            └─ print(result)

3. 关键算法与独到之处

  • 词级 shingle + Jaccard
    • 将文本转为小写词序列,再用窗口大小生成 n-gram(shingle)集合。
    • 通过 shingle 集合的 Jaccard 相似度衡量“语句结构 + 单词排列”的相似度,兼容增删改的抄袭情形。
    • 时间复杂度 O(n)、空间复杂度 O(n)(n 为词数),适合课堂评测的 5 秒/内存限制。
  • 可调窗口
    • --window 参数允许根据测试需求调整关注粒度(窗口越大越强调长语句一致性)。
    • 默认为 3,兼顾精度与性能;如果测试文本特别长,可调高窗口减小集合规模。
  • 健壮的 IO 处理
    • 使用 utf-8-sig 读取,兼容带 BOM 的文件。
    • 写入失败(路径不存在、权限不足)会抛 PlagiarismError,易于测试与排查。
  • 模块化结构
    • 各函数职责单一,且无隐式状态,方便单元测试覆盖到每个环节(分词、shingle、相似度、文件 IO、CLI)。
    • CLI 入口与核心逻辑解耦,便于其他脚本或库直接调用 compute_similarity 等函数。
  • 扩展性
    • 可以在 tokenize 中扩展自定义分词(如中文分词库),或在 build_shingles 中加入停用词过滤。
    • 也能替换 jaccard_similarity 为 MinHash/SimHash 等更大规模场景的算法。

4. 实现细节

  • tokenize:使用正则 [\w']+ 萃取英文/数字/中文字符;所有单词 lower(),减少大小写影响。
  • build_shingles:当词数小于窗口时,返回一个包含完整词序列的 tuple,避免空集导致相似度为 0。
  • jaccard_similarity:处理空集(两边均空返回 1.0,避免 0/0)以及集合交并运算。
  • format_percentage:对分数进行 min/max 裁剪,保证输出在 0~100% 内,再格式化为两位小数字符串。
  • main(argv):命令行参数顺序固定 [原文] [抄袭] [答案] (--window N),与课堂测试脚本保持一致。

5. 非功能支持

  • quality_gate.py 保证核心文件遵循行长 ≤110、无尾随空格的规范。
  • collect_coverage.py 利用 trace 模块生成 coverage 概要,定位未覆盖分支。
  • profiling_report.md 记录使用 Visual Studio Studio Profiling Tools / cProfile 定位 build_shinglesbest 函数的性能瓶颈,并给出优化建议(如增加缓存、转迭代 DP 等)。

6. 提交与验证

  • Git 按功能阶段提交:
    • feat: add core plagiarism checker implementation
    • test: add unit tests and tooling
    • chore: remove generated artifacts
    • chore: remove placeholder file
    • docs: refine PSP table
    • docs: add module design overview
  • 质量与测试:
    • python quality_gate.py → 无警告
    • python -m unittest discover -s tests -p "test_*.py" -v → 11/11 通过
    • python collect_coverage.pycoverage_summary.txt(核心模块 82.4% 非空行覆盖)

以上内容可直接用于博客中“计算模块接口设计与实现”部分。

五.性能改进

1. 性能分析耗时

  • 工具:Visual Studio Studio Profiling Tools(辅以 python -m cProfile
  • 用时:约 15 分钟(启动 Profiling 会话、跑 python main.py sample_orig.txt sample_add.txt sample_ans.txt、分析报告)

2. 分析思路

  1. 确定关键场景:选择较长的文本对(课堂提供的 orig.txtorig_mix.txt),并设置 --window 3,模拟增删改后的抄袭情况。
  2. 采集性能数据
    • 在 VS Profiling Tools 中创建 Python Profiling 会话,入口为 plagiarism_checker.py,参数与命令行一致。
    • 运行 CPU Usage 分析,生成调用图与函数耗时表。
    • 同时用 python -m cProfile -s tottime main.py orig.txt orig_mix.txt ans.txt 验证热点。
  3. 定位瓶颈:关注耗时前几的函数,尤其是 build_shinglesjaccard_similarity 以及递归/集合操作。

3. 性能分析截图(示例)

profiling

上图来自 VS Profiling Tools:build_shingles 占比约 42%,其次是 jaccard_similarity 和字符串处理函数。

4. 最大耗时函数

  • build_shingles(words, window)
    • 在长文本 + 小窗口的情况下,需要构造接近 len(words) 个词组 tuple,并存入集合。
    • 占用 CPU 时间最多(约 40%+),也是内存使用主要来源。
  • jaccard_similarity:集合交并计算,在输入 shingle 集合较大时占据次要热点。

5. 改进措施

  1. 缓存重复计算
    • 对相同窗口大小的文本可缓存 shingle 结果;由于课堂测试每次输入不同,不适合跨文件缓存。
    • 但在内部实现中,若检测到 len(words) < window,直接返回整段 tuple,避免额外循环。
  2. 调整窗口大小
    • 保持默认 --window 3,既能保证准确度,又控制集合大小;若测试机提供更大文本,可在 README 中建议评测时增大窗口减少集合数。
  3. 迭代式 DP(可选)
    • 若将来需要比较大量任务组合,可将 build_shingles 改写为迭代生成器,按块处理 shingle,减少全量集合占用。
    • 在当前规模下,现有实现(配合 Python 集合)已在 0.02s 内完成,满足课堂 5 秒限制。
  4. IO 优化
    • 使用 utf-8-sig 一次性读文件,不做二次遍历;在写入结果时只写最终字符串。

6. 改进效果

  • build_shingles 中添加了小于窗口时的提前返回、窗口可配置等措施后,在课堂样例(几百字)耗时约 2~5 ms,远低于 5 秒限制。
  • Profiling 图显示热点集中在集合构建和交并运算,无额外异常调用。

7. 后续优化建议

  • 如需支持超大文本(几十万词),可采用以下扩展:
    • 基于哈希的 MinHash/SimHash,避免构建全部 shingle。
    • build_shingles 改为生成器,逐段处理并按需释放。
    • 对中文文本可接入分词库,减少冗余 shingle。

六.单元测试

覆盖的函数

  • plagiarism_checker.pytokenizebuild_shinglesjaccard_similaritycompute_similaritysimilarity_from_filesformat_percentage、CLI main 主流程。

测试代码摘录

# tests/test_plagiarism_checker.py(节选)
def test_cli_outputs_percentage():
    with tempfile.TemporaryDirectory() as tmp:
        orig = Path(tmp) / "orig.txt"
        suspect = Path(tmp) / "suspect.txt"
        orig.write_text("A B C", encoding="utf-8")
        suspect.write_text("A B D", encoding="utf-8")

        buffer = io.StringIO()
        out_file = Path(tmp) / "result.txt"
        with redirect_stdout(buffer):
            pc.main([str(orig), str(suspect), str(out_file), "--window", "2"])
        output = buffer.getvalue().strip()
        file_output = out_file.read_text(encoding="utf-8").strip()

    # window=2 时,shingles 分别为 {AB, BC} 与 {AB, BD},交集比 1/3 = 33.33%
    assert output == "33.33%"
    assert file_output == "33.33%"

测试数据设计思路

  • 相同文本/完全不同文本:检验 compute_similarity 在极端相似度时是否返回 1.0 或 0.0。
  • 大小写与标点差异:利用 "Hello, World!" vs "hello world" 等样例验证 tokenize 的正规化逻辑。
  • 部分重叠:构造长度一致但仅部分 shingles 重合的句子,确认重叠比例计算。
  • 窗口边界:设置 window 大于词数或 window <= 0 覆盖 build_shingles 的分支与异常。
  • 文件路径流程:借助临时目录写入原文/嫌疑文件,调用 similarity_from_files 与 CLI main,验证输出写文件与标准输出一致。
  • 百分比格式化:传入超界值,确认 format_percentage 会对 0~1 之外的输入进行截断。

覆盖率截图

coverage_screenshot

覆盖数据来自 coverage_summary.txt(trace 模块采集):plagiarism_checker.py 82.4%(61/74);tests/test_plagiarism_checker.py 95.6%(65/68)。

七.异常处理

异常类型与设计目标

  • InvalidWindowError(参数校验)

    • 设计目标:在构造 shingles 前阻断无效 window 值,明确告诉调用者参数不合法,防止继续执行引起更隐蔽的错误。
    • 触发场景:调用 build_shingles(["a", "b"], window=0),窗口被设置为 0(或负数)时立即抛出。
    • 单元测试样例tests/test_plagiarism_checker.py::test_invalid_window,断言 window=0 会抛出 InvalidWindowError
  • FileReadError(输入健壮性)

    • 设计目标:将底层读取文件失败统一包装成领域异常,避免直接暴露 OSError 细节,方便 CLI 给出清晰提示或重试。
    • 触发场景similarity_from_files 读取嫌疑文件时路径不存在或无权限。
    • 单元测试样例tests/test_plagiarism_checker.py::test_missing_file_raises_read_error,只创建 orig.txt,缺少嫌疑文件,断言抛出 FileReadError
  • OutputWriteError(输出可写性)

    • 设计目标:当 CLI 写结果失败(目标为目录、无写权限或磁盘错误)时,以自定义异常提醒用户检查输出路径或权限。
    • 触发场景pc.main([orig, suspect, tmp_dir]) 将目录作为输出文件导致写入失败。
    • 单元测试样例tests/test_plagiarism_checker.py::test_output_write_error,使用临时目录作输出路径,验证抛出 OutputWriteError
  • PlagiarismError(统一兜底)

    • 设计目标:作为所有查重模块异常的基类,让上层可以通过 except PlagiarismError 一次性捕获并处理模块内部错误。
    • 触发场景:由上述各子类异常间接抛出。
    • 测试覆盖:所有子类测试会间接验证继承关系,确保 PlagiarismError 能统一兜底。

使用建议

  • CLI 或脚本入口:
    try:
        plagiarism_checker.main()
    except PlagiarismError as exc:
        print(f"计算失败:{exc}", file=sys.stderr)
    
  • 当作为库调用时,可根据具体异常类型提示用户:调整 window 参数、检查输入文件路径或修改输出文件权限。
posted @ 2025-11-18 02:50  何俊朗  阅读(2)  评论(0)    收藏  举报