设想一家出版社收到了一部整整一千页的悬疑小说手稿。主编小林读到第 312 页时忽然觉得眼熟——这句话在第 87 页出现过!再往后翻,“既视感”越来越多:有的段落一字不差,有的只是换了人物名字,还有的改头换面但讲的依然是同一个情节。如果靠人眼逐页比对,三个月都看不完;而读者一旦发现重复,就会觉得作者在注水、在偷懒。于是,出版社请来了一位 AI“克隆稽查员”。本文要讲的,就是这位稽查员如何工作:它把代码当成小说手稿,用从粗到细的四层手段——直接比对文字、比对改写后的符号、比对句子结构、比对故事情节——把重复写下的段落一个个揪出来,最后还能量化重复程度,给作者开出一份技术债清单。

岗位关联

对 AI 从业者而言,克隆检测是代码质量审计与智能重构的入口技能:理解它,你就知道 AI 如何在大型代码库里自动发现重复、量化技术债,并支撑起真实世界的代码复用与维护优化。

目录
  1. 可视化演示
  2. 克隆识别概念
  3. 四种克隆类型
  4. 文本比对法
  5. 词法符号法
  6. 语法树法
  7. 依赖图法
  8. 工具与评估
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

克隆识别概念

在小林看来,“重复”不是一种非黑即白的状态。稽查员上岗第一件事,就是和主编确认“什么才算重复”:是必须一个字不差,还是意思相同也算?反复讨论后,他们定下一条朴素而可操作的规则——只要两段文字相似到“改一处必须同步改另一处”的程度,就算一次克隆。有了这条规则,稽查才能开始。

在软件工程里,这种“必须同步维护”的重复片段被称为代码克隆,英文叫 Code Clone。它们大多来自复制粘贴、框架模板,或是开发者“照着上一段写法再来一遍”的习惯。统计表明,大型项目里通常有 5% 到 15% 的代码属于重复片段。这些重复会悄悄推高维护成本:修复一个 bug 时要在每处副本上重复修改,漏改一处就埋下隐患,这正是技术债的重要来源。

因此,团队需要克隆检测工具自动扫描整个代码库,先把“哪些地方重复、重复了多少”量化出来。最基础的量化指标是重复率,它把重复代码行数除以总代码行数,得到一个可以横向比较的百分比:

对小林来说,重复率就像手稿的定级表:如果一千页里有两百页是前面抄来的,这本书就得上“重大修改”清单。对软件开发而言,重复率越高的模块,越是重构时优先瞄准的目标。

Rdup=LdupLtotal×100%R_{dup} = \frac{L_{dup}}{L_{total}} \times 100\%
重复率就好比手稿的“灌水率”:数值越高,代码库里的“注水段落”越多,越需要优先安排重构。
  1. 重复代码行数代码库中被判定为克隆的代码行的总和,它只统计那些“重复的部分”。
    LdupL_{dup}
  2. 总代码行数整个代码库的有效代码行数,是计算比例的基准。
    LtotalL_{total}
  3. 相除取百分比把重复部分占总量的比例转化为百分数,方便在不同项目之间对比。
    Rdup=LdupLtotal×100%R_{dup} = \frac{L_{dup}}{L_{total}} \times 100\%

四种克隆类型

小林把挑出来的重复段落分成了四类,方便逐一处理。第一类最省心:作者把段落原封不动地粘过来,只改了回车和注释;第二类稍稍动了手脚,把“王小明”换成“李小红”,可句子结构一模一样;第三类抄完还添删了几句话;最难缠的是第四类——字面上完全不同,可讲的故事、传递的信息完全一致。

代码克隆研究者也按“改动的程度”把克隆分成四种经典类型。Type 1 是完全复制,仅空白和注释不同;Type 2 是改名复制,变量名、函数名换了,但结构不变;Type 3 是在 Type 2 的基础上继续增删改语句,属于“越改越走样”的复制;Type 4 是语义克隆,实现方式完全不同,功能却等价。它们之间有一条清晰的递进关系:

理解类型最大的价值在于选工具:只想抓 Type 1,文本比对就够;想抓 Type 2,需要词法归一化;想抓 Type 3,往往要靠语法树;想抓 Type 4,就得上升到程序依赖图。小林把这些类型称为“抄袭的四个段位”——段位越高,抓起来越费劲,但也越不能放过,因为第四类恰恰是最隐蔽、最消耗维护精力的重复。

T1⊂T2⊂T3,T4⊄T3T_{1} \subset T_{2} \subset T_{3}, \quad T_{4} \not\subset T_{3}
这条式子说明:克隆类型不是四个互不相干的抽屉,而是从“完全照抄”到“深度改写”的递进链;语义克隆则跳出了这条链,单列一类。
  1. 第一类属于第二类只改空白和注释的 Type 1,结构上完全保留,自然也算一种“换汤不换药”的 Type 2。
    T1⊂T2T_{1} \subset T_{2}
  2. 第二类属于第三类Type 2 换名之后若再被增删语句,就升级成 Type 3,因此它是 Type 3 的子集。
    T2⊂T3T_{2} \subset T_{3}
  3. 传递性推得包含由前两步可知,Type 1 也必然包含在 Type 3 之中。
    T1⊂T3T_{1} \subset T_{3}
  4. 语义克隆独立Type 4 实现完全不同,不在“结构相似”的 Type 3 范畴里。
    T4⊄T3T_{4} \not\subset T_{3}

文本比对法

最简单的稽查手段,是像用 Ctrl+F 那样直接比对文字。小林先把两份手稿按行拆开,一行行地找完全相同的句子;找不到整句,就看连续的几行是否构成一个可比的窗口。这个方法笨拙,但它是所有高级方法的起点。

技术世界里,这就是文本克隆检测(Text-Based Detection)。它把代码当普通文本处理,用滑动窗口切出固定长度的代码行序列,再用哈希或字符串匹配比较行与行是否一致。判断两个片段像不像,常用 Dice 系数:把每个片段看作一个“行的集合”,交集就是完全相同的那些行,相似度是两个交集大小之和与集合大小的比值:

文本法实现简单、速度快、没有任何语法开销,但只会抓 Type 1。只要开发者把变量名换一换,哪怕结构完全一样,“行集合”也会立刻失真,Dice 系数骤然下降。这就像一位只会查字面的校对员:作者一换名字,他就认不出来了,所以文本法只能当第一道粗筛。

S=2×∣A∩B∣∣A∣+∣B∣S = \frac{2 \times |A \cap B|}{|A| + |B|}
这个公式可以理解为“共同拥有的行数”占“两段总行数”的比例。完全相同的两段代码,相似度为 1。
  1. 两个行集合把两个代码片段各拆成行,分别构成集合 A 和 B。
    A, BA,\ B
  2. 交集行数两段中文字完全相同的行的数量,记为 A 与 B 交集的元素个数。
    ∣A∩B∣|A \cap B|
  3. 分子翻倍乘以 2 是为了让完全相同(A 等于 B)时相似度恰好为 1,方便直观理解。
    2×∣A∩B∣2 \times |A \cap B|
  4. 分母归一化用两个片段的行数之和做分母,把相似度压缩在 0 到 1 之间。
    ∣A∣+∣B∣|A| + |B|

词法符号法

为了抓住“换了名字”的抄袭,小林升级了工具。他不再盯着原始字符,而是先把句子拆成一个个词,再把“人名、地名”全部替换成统一的“某某”。这样一来,王小明和李小红写出来的句子,在符号层面就变得一模一样。

这正是词法符号法(Token-Based Detection)的核心思路,代表作是著名的 CCFinder。它先把代码切成 token 流——也就是代码中最小的语法单位,比如变量名、数字、运算符;然后做归一化:把所有标识符统一替换成占位符,把所有数字替换成统一的数字符号;最后在归一化后的符号串里寻找尽量长的公共子序列,这一步骤通常借助后缀树这种精巧的数据结构高效完成。

CCFinder 的流程可以概括为三步:词法分析(切 token)、归一化(替换标识符)、后缀树匹配(找最长公共序列)。为了衡量重复程度,工程上常用最长公共子序列的长度除以两个片段中较短者的长度:

对小林而言,符号法相当于一位“会认人但更看重骨架”的校对员:你换名字无所谓,句子骨架一样就躲不过。它比文本法多抓住 Type 2 克隆,但对“抄完还改写几行”的 Type 3 已有些力不从心。

Rmatch=∣LCS(A,B)∣min⁡(∣A∣,∣B∣)R_{match} = \frac{|LCS(A, B)|}{\min(|A|, |B|)}
它的含义是“两段代码在归一化后,公共骨架占了较短那段的多少”。骨架越长,克隆嫌疑越大。
  1. 最长公共子序列在两条 token 序列 A、B 中,按原顺序出现的最长公共符号串的长度。
    ∣LCS(A,B)∣|LCS(A, B)|
  2. 取较短者克隆片段不可能比两个来源中更短的那个还长,因此用较短者做基准最合理。
    min⁡(∣A∣,∣B∣)\min(|A|, |B|)
  3. 匹配覆盖率公共部分占较短片段的比例,代表重复在“小片段”中的覆盖程度。
    Rmatch=∣LCS(A,B)∣min⁡(∣A∣,∣B∣)R_{match} = \frac{|LCS(A, B)|}{\min(|A|, |B|)}

语法树法

随着作者“抄术”升级,小林发现光看词汇已经不够。他请来了懂语法的专家,把每句话都画成主谓宾结构图;只要两张结构图的主体框架相同,哪怕用词完全不同,也算重复。这就像语文老师批改作文时说的:“你这篇和那篇,骨架是同一个。”

代码世界里的“结构图”就是抽象语法树(Abstract Syntax Tree,AST)。编译器把源码解析成一棵表达语法结构的树:根是函数定义,往下依次是语句、表达式、变量。AST 方法把两个片段的树拿出来,寻找同构的子树(Common Subtree),子树越大、越相似,克隆嫌疑越高。典型工具如 Deckard 就基于这种思路。

AST 方法天然对标识符改名免疫——因为在语法树里,变量名只是叶子节点的标签,真正决定相似度的是树形结构。比较两棵树的相似度,常用节点规模来度量:

对小林而言,语法树法像是“看句子骨架”的资深编辑:只要句法框架雷同,换了再华丽的词也躲不过。它能覆盖 Type 2 和相当一部分 Type 3,但对“换一种写法讲同一个故事”的 Type 4 依旧没有头绪。

Stree=2×NcommonNA+NBS_{tree} = \frac{2 \times N_{common}}{N_{A} + N_{B}}
这个公式把“公共子树节点数”折算成两棵树总规模的比例,完全相同的两棵树,相似度为 1。
  1. 两棵树节点数分别统计两个 AST 的节点总数,代表语法结构的丰富程度。
    NA, NBN_{A},\ N_{B}
  2. 公共子树的节点数两棵树中结构同构的最大子树所包含的节点个数。
    NcommonN_{common}
  3. 归一化相似度公共子树占两棵树总体规模的比例,值越高说明结构越接近。
    Stree=2×NcommonNA+NBS_{tree} = \frac{2 \times N_{common}}{N_{A} + N_{B}}

依赖图法

真正难抓的是第四类:作者完全换了写法,连句子结构都不一样,但传递的信息相同。小林最后请来了“理解情节”的审稿人,把段落里的因果关系画成一张网——谁依赖谁、什么条件触发什么结果——再去比对两张网的形状。就算一段用倒叙、一段用顺叙,只要因果链一致,就判定为重复。

软件工程里的这张网就是程序依赖图(Program Dependence Graph,PDG)。它把代码表示成节点和边:节点是语句或谓词,边有两种关键类型——数据依赖(变量在哪儿被定义、在哪儿被使用)和控制依赖(哪条分支控制着哪条语句的执行)。PDG 方法比较两个片段的依赖子图,如果找到足够大的同构子图,就判定为语义克隆。

因为 PDG 丢开了“怎么写”,只保留“做了什么”,Type 4 克隆在这里终于露出马脚。相似度可以定义为匹配上的节点数占两个图并集节点数的比例:

对小林来说,依赖图法是从“看作文”升级到“看大纲”:字句全换没关系,只要情节走向、因果关系一致,就认定是重复。它是对付 Type 4 的最强手段,但构建依赖图的成本较高,通常只在核心模块上使用。

Sdep=∣Vmatch∣∣VA∪VB∣S_{dep} = \frac{|V_{match}|}{|V_{A} \cup V_{B}|}
匹配节点越多、并集越小,说明两张“因果网”的形状越接近,语义也就越等价。
  1. 匹配节点集两个程序依赖图之间满足同构映射的节点集合,也就是“对得上号”的语句。
    VmatchV_{match}
  2. 并集节点数两个图的节点合在一起后的总数,用于消除规模差异。
    ∣VA∪VB∣|V_{A} \cup V_{B}|
  3. 图相似度匹配节点占并集的比例越高,说明两个片段在依赖结构上越接近。
    Sdep=∣Vmatch∣∣VA∪VB∣S_{dep} = \frac{|V_{match}|}{|V_{A} \cup V_{B}|}

工具与评估

稽查员上岗前,主编要先考核它的“业绩”:不能冤枉好人,也不能放过坏人。小林把已知的重复段落整理成标准答案,让工具去比对,统计它找对了多少、找错了多少——这套考核制度,就是克隆检测的评估体系。

对应的两大指标是精确率(Precision)和召回率(Recall)。精确率回答“工具报告出来的克隆里,有多少是真克隆”,召回率回答“真实存在的克隆里,工具抓住了多少”。两者常常此消彼长:阈值调高,精确率升高但召回率下降;阈值调低则相反。为了综合比较不同工具的优劣,人们把两者用调和平均压成一个分数,即 F1:

落地时,不同层级的方法各有代表工具:文本法有 NiCad 之类的轻量粗筛器,词法法有 CCFinder,语法树法有 Deckard,依赖图法有近年基于深度学习的语义检测模型。实践中的常见流程是先用轻量工具做全库扫描,再对重点模块用 AST 或 PDG 做二次确认。对小林来说,这套考核保证了查重报告的含金量;同样地,团队在重构前拿到克隆报告,就能按风险分级处理——重复不是原罪,把重复的代价变得可见、可控,才是克隆检测真正的价值。

F1=2×Precision×RecallPrecision+RecallF_{1} = \frac{2 \times \text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}
F1 越接近 1,说明稽查员在“不冤枉好人”和“不放走坏人”之间平衡得越好。
  1. 精确率工具报告的克隆对中真实克隆所占的比例,衡量“抓得准不准”。
    Precision=TPTP+FP\text{Precision} = \frac{TP}{TP + FP}
  2. 召回率真实克隆中被工具找出的比例,衡量“抓得全不全”。
    Recall=TPTP+FN\text{Recall} = \frac{TP}{TP + FN}
  3. F1 调和平均把精确率和召回率压缩成一个综合得分,F1 越高说明稽查越可靠。
    F1=2×Precision×RecallPrecision+RecallF_{1} = \frac{2 \times \text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}}

AI 应用场景

大型代码库技术债审计

阶段:代码质量分析 / 技术债务管理

解决的问题:传统方式靠 Code Review 人工记忆“哪里有过类似代码”,百万行规模根本看不全,技术债长期隐身。AI 用克隆检测全库扫描,输出重复率排名,让债主显形。

具体案例:某支付平台在重构风控模块之前,用 CCFinder 扫描五十万行 Java 代码,找出 1400 组 Type 2/3 克隆,其中 60 组共享同一个历史 bug,优先修复后避免了潜在的线上故障。

大规模重构的安全评估

阶段:代码重构 / 变更影响分析

解决的问题:重构一个函数时不知道它有多少“分身”,改一处漏一处;克隆检测先把所有副本映射成清单,重构范围一目了然。

具体案例:数据团队想统一升级旧版日志接口,先用 AST 克隆检测找出所有结构相似但名称各异的调用片段,生成重构清单,再批量替换,回归测试一次通过。

学术代码查重与合规审查

阶段:代码合规 / 教学评估

解决的问题:人工比对上百份作业或开源项目授权片段效率极低;克隆检测按类型分级输出相似度报告,辅助判断抄袭或授权风险。

具体案例:高校助教用“文本法 + 词法法”两级克隆检测扫描 200 份编程作业,筛出 23 组疑似抄袭,再人工复核确认 9 组,整个流程从两周压缩到一天。

Python 演示

python61 行
# -*- coding: utf-8 -*-"""简化版克隆检测器, 模仿 CCFinder 的词法归一化思路""" import refrom difflib import SequenceMatcher SPLIT = re.compile(r'([A-Za-z_][A-Za-z0-9_]*|[0-9]+|\W)') def tokenize(code):    """把代码切成 token 序列, 标识符统一归一化为 ID"""    tokens = []    for t in SPLIT.findall(code):        if not t:            continue        if t.isidentifier():            tokens.append('ID')        elif t.isdigit():            tokens.append('NUM')        elif not t.isspace():            tokens.append(t)    return tokens def similarity(code_a, code_b):    """比较两段代码的 token 流相似度"""    seq_a, seq_b = tokenize(code_a), tokenize(code_b)    return SequenceMatcher(None, seq_a, seq_b).ratio() def find_clones(snippets, threshold=0.8):    """两两比较, 返回相似度达到阈值的克隆对"""    clones = []    for i in range(len(snippets)):        for j in range(i + 1, len(snippets)):            left, right = snippets[i], snippets[j]            ratio = similarity(left[1], right[1])            if ratio >= threshold:                clones.append((left[0], right[0], round(ratio, 2)))    return clones if __name__ == '__main__':    code_a = """def calc_total(items):    price = 0    for item in items:        price = price + item.price    return price"""    code_b = """def compute_sum(goods):    total = 0    for good in goods:        total = total + good.price    return total"""    code_c = """def hello():    return "hi""""    snippets = [('A', code_a), ('B', code_b), ('C', code_c)]    for left, right, ratio in find_clones(snippets):        print('clone pair: %s - %s similarity %.2f' % (left, right, ratio))    print('done')
目的:实现了 CCFinder 最核心的“词法归一化”思路——把变量名、数字都替换成占位符后,再用最长公共子序列求相似度,轻松识破“只换名字”的 Type 2 克隆。
  1. SPLIT = re.compile(r'([A-Za-z_][A-Za-z0-9_]*|[0-9]+|\W)')一个正则表达式,把源码切成“标识符、数字、标点”三类 token。
  2. tokens.append('ID')所有标识符归一化成同一个占位符,换了名字也能匹配,这是仿 CCFinder 的精髓。
  3. tokens.append('NUM')数字也归一化,避免 price = 0 和 total = 0 因字面量不同而漏配。
  4. seq_a, seq_b = tokenize(code_a), tokenize(code_b)两段代码先转成统一的 token 流,再做序列级比对。
  5. return SequenceMatcher(None, seq_a, seq_b).ratio()用动态规划求最长公共子序列,输出 0 到 1 的相似度。
  6. if ratio >= threshold:相似度超过阈值就记一组克隆,阈值越高,报告越严格。

常见误区

误区代码必须一模一样才算重复,稍微不同的两段代码工具肯定认不出来;那我手动搜索复制粘贴的痕迹就够了,何必上 AI?

正解克隆检测的精髓恰恰是“不完全相同也能识别”。按类型分,Type 1 只是最表层的复制;换名的 Type 2、改写的 Type 3、甚至语义等价的 Type 4 都能被相应层级的工具抓出。CCFinder 做归一化之后,“看起来不同”的代码在符号层会原形毕露。就像编辑小林不只会查一模一样的句子,还会看换名、看结构、看情节。

为何常见:初学者最早接触的都是文本比对工具,容易把“查重”理解成“完全相等”,忽略了归一化、AST、依赖图这些更深的手段。

误区工具报告的一长串克隆清单就是“代码垃圾清单”,我照着全部重写一遍,代码质量就上去了。

正解克隆不总是坏味道。框架模板、单元测试样板、跨语言协议封装等结构相似的代码是合理复用,硬重构反而引入新风险。正确做法是把克隆报告当作风险列表,结合业务重要性、变更频率、是否共享 bug 来分级处理:高危重复优先改,低频模板可以保留。工具的角色是“量化风险”,不是“道德审判”。

为何常见:把“重复”天然等同于“坏”,忽略了重复在真实工程里常常是成本与收益权衡后的选择。

误区既然文本法和词法法都太浅,那我直接上抽象语法树,AST 一定能识别 Type 4 语义克隆。

正解AST 是语法层工具,能很好地覆盖 Type 2 和部分 Type 3,但它描述的仍然是“怎么写的”而非“做了什么”。Type 4 是语义等价而语法完全不同,比如用 for 循环和用 while 循环实现同样的遍历,AST 长相就完全不同;要抓它需要程序依赖图、切片或行为级别的分析。

为何常见:“越高级越万能”的直觉很普遍,但工具的能力边界由它的表示方式决定,与技术是否新颖无关。

误区CCFinder 名声大、匹配能力强,它一定是基于语法树甚至是深度学习的方法。

正解CCFinder 是典型的词法级工具:先把源码切成 token,做标识符归一化,再用后缀树找最长公共子序列。它并不解析语法结构,也正因如此速度快,适合全库粗扫,但不擅长 Type 3/4。

为何常见:工具“出名”与“技术层级高”被画上等号,实际上 CCFinder 的成名在于工程效率,而非语义理解深度。

面试题

Q1. 克隆检测要解决的核心痛点是什么?

单选题 · easy

  • A.降低重复代码维护成本← 正确答案
  • B.提升程序运行的性能
  • C.自动生成单元测试用例
  • D.把代码翻译成其他语言

答案:A。重复代码的最大危害是“改一处要同步改多处,漏一处就埋 bug”,克隆检测自动定位重复片段,从而降低维护成本。选 A。提升性能、生成测试、翻译语言都不是克隆检测的目标。

Q2. 下列哪项属于 Type 1 克隆?

单选题 · easy

  • A.只改空格与注释的复制← 正确答案
  • B.修改变量名后的复制
  • C.增删若干语句的复制
  • D.实现完全不同但语义相同

答案:A。Type 1 指除空白和注释外完全相同的复制,相当于“原封不动照抄”。改名是 Type 2,增删语句是 Type 3,语义等价是 Type 4。

Q3. CCFinder 采用哪一类核心算法思路?

单选题 · mid

  • A.纯文本逐行哈希比对
  • B.词法归一化加后缀树匹配← 正确答案
  • C.抽象语法树子树同构
  • D.程序依赖图的子图同构

答案:B。CCFinder 是词法级工具,先把代码切成 token 并归一化标识符,再用后缀树寻找最长公共子序列。它不是文本哈希、AST 或依赖图方法。

Q4. 借助 token 归一化,以下哪些克隆类型可能被识别?

多选题 · mid

  • A.Type 1 完全复制← 正确答案
  • B.Type 2 换名克隆← 正确答案
  • C.Type 3 少量改动克隆← 正确答案
  • D.Type 4 语义等价克隆

答案:ABC。归一化后 Type 1 完全一致,Type 2 名字虽异但符号序列相同,Type 3 若改动不大仍有很长公共子序列可被抓到;而 Type 4 代码形态完全不同,token 层匹配不上。

Q5. AST 方法相对文本方法最主要的优势是什么?

单选题 · mid

  • A.运行速度更快
  • B.能看见语法结构相似← 正确答案
  • C.无需解析源代码
  • D.可直接识别语义等价

答案:B。AST 把代码解析成语法树,比较的是结构而非字符,因此对换名克隆免疫。它比文本法更慢,需要解析源码,且无法直接识别 Type 4 语义等价。

Q6. 精确率高而召回率低,说明克隆检测工具?

单选题 · mid

  • A.找得准但漏得多← 正确答案
  • B.找得全但误报多
  • C.运行速度快但占用高
  • D.无法处理大型代码库

答案:A。精确率高表示报出来的克隆大多是真的,召回率低表示大量真克隆没被抓到,即“找得准但漏得多”。这正是高阈值扫描的典型表现。

Q7. 要识别 Type 4 语义克隆,合适的手段有哪些?

多选题 · hard

  • A.逐行文本哈希比对
  • B.程序依赖图子图同构← 正确答案
  • C.抽象语法树直接比对
  • D.行为切片与语义分析← 正确答案

答案:BD。Type 4 克隆语法不同但语义等价,只有分析“做什么”的依赖图和语义方法能识别。文本哈希和 AST 都停留在“怎么写”的层面,必然漏掉。

Q8. 相似度阈值从 0.9 调低到 0.7,通常会观察到?

单选题 · hard

  • A.召回率上升,精确率下降← 正确答案
  • B.精确率上升,召回率下降
  • C.精确率与召回率同升
  • D.精确率与召回率同降

答案:A。调低阈值等于放宽“重复”标准,更多候选会被报告,漏报变少(召回率升),但误报也变多(精确率降)。两者此消彼长,需要 F1 平衡。

Q9. 关于四种克隆类型的关系,以下正确的是?

多选题 · mid

  • A.Type1 属于 Type2 的子集← 正确答案
  • B.Type2 属于 Type3 的子集← 正确答案
  • C.Type4 属于 Type3 的子集
  • D.Type1 属于 Type3 的子集← 正确答案

答案:ABD。由 T1 ⊂ T2 ⊂ T3 可知 A、B、D 都正确。Type 4 是语义克隆,跳出结构相似链,不属于 Type 3。

Q10. 为什么重复代码被看作技术债?

单选题 · easy

  • A.让代码体积变得更大
  • B.让代码风格变得混乱
  • C.导致编译器运行变慢
  • D.改一处需同步多处易漏← 正确答案

答案:D。重复代码让同一份逻辑散落多处,修改时需同步维护,漏改一处就埋下隐患。体积变大是表象,风格混乱是次要,真正的“债”在维护时偿还。