想象你是一家米其林餐厅的后厨总管,每天要品尝上百道新来的厨师做的菜。你定下规矩:每道菜都要用同一张“品鉴清单”逐项核验,咸淡、火候、装盘一目了然;试菜必须在独立的小隔间里进行,即便菜品变质也污染不了大厨房;最后还要认真写下评语,告诉厨师是盐放多了还是火候欠了。这套流程,本质上就是一套“试菜评测系统”。AI 自动批改编程作业的代码评测系统,干的就是这位“后厨总管”的活儿:学生提交代码,系统自动运行测试用例、在沙箱中隔离执行、生成针对性反馈,顺便查一查有没有人请“外援”代做。本文用“品菜试吃”这个比喻贯穿始终,把评测系统的测试用例生成、运行沙箱、反馈生成、公平性保障、安全性防护和防作弊机制一次讲明白。

岗位关联

掌握代码评测系统的设计,是从业者构建 AI 教育产品和在线编程平台的核心技能。它能直接支撑自动批改、在线判题、考试防作弊等真实业务,让你从“只会写代码”升级为“能设计教学闭环”。

目录
  1. 可视化演示
  2. 评测系统概览
  3. 测试用例生成
  4. 运行沙箱设计
  5. 反馈生成策略
  6. 公平性保障
  7. 安全性防护
  8. 防作弊机制
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示代码评测系统五步流水线与沙箱安全隔离架构,支持模式切换查看评测全流程。

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

五步评测流水线 代码提交:学生上传源代码至评测队列 用例生成:AI自动生成边界测试数据 沙箱执行:隔离环境运行代码采集输出 score = passed / total × 100 评分通过率决定最终成绩。

评测系统概览

想象那位“AI 副厨”正式上岗:它面前排着几百道学生的菜,每道菜都附了一张小纸条,写着“期望的味道”。它要做的事情只有三件——把菜端进小隔间、对照品鉴清单尝一口、写下点评。这三件事合起来,就是代码评测系统的全部:接收学生提交的代码,用预先设计好的测试用例(品鉴清单)去运行它,再根据运行结果判定对错并生成反馈。

从概念上说,评测系统要解决的第一个问题是“什么叫作对”。代码的行为是黑箱,能观察到的只有输入和输出。于是评测系统采用最朴素也最可靠的策略:给定同样的输入,学生程序的输出必须和老师预先算好的期望输出完全一致。就像副厨不会问厨师做菜时心里怎么想,只看端上来的菜咸淡是否正好。

整个评测流水线可以拆成四步:提交阶段接收代码并启动独立进程;测试阶段把测试用例逐个喂给程序;判定阶段把实际输出与期望输出逐字符比对;反馈阶段把编译错误、运行异常和输出差异加工成学生看得懂的语言。这套流程在教育领域遍地开花:在线编程平台用它批改课后作业,OJ 系统用它做竞赛判题,高校实验课用它完成大班作业的自动评分。

1. 期望输出:老师预先给出的标准答案,代表“这道菜应该有的味道”

expected=标准输出\text{expected} = \text{标准输出}

2. 实际输出:学生代码运行后产生的真实结果

out=程序打印的内容\text{out} = \text{程序打印的内容}

3. 精确比对:逐字符比较两段文本是否完全一致

out=expected  ⟺  逐字符相等\text{out} = \text{expected} \iff \text{逐字符相等}

4. 结论分支:相等则判通过,不相等则判失败

通过vs失败\text{通过} \quad \text{vs} \quad \text{失败}

副厨的这套做法告诉我们:评测的前提是把“对”定义清楚。定义得越明确,后面的自动化才越有底气。而这把尺子究竟量得准不准,取决于下一节讲的品鉴清单设计得够不够刁钻。

text1 行
\text{结果} = \begin{cases} \text{通过} & \text{out} = \text{expected} \\ \text{失败} & \text{out} \neq \text{expected} \end{cases}

测试用例生成

副厨的品鉴清单可不是随便写的。它知道一道菜要在“刚好及格”“极端火候”“食材临界”这些位置重点考察:焯水 30 秒和 31 秒的区别,可能就决定了一盘菜是脆嫩还是软烂。测试用例设计的第一招,就是盯住输入范围的边界值。如果题目约定输入是 0 到 100 的整数,那么 0、100 是合法边界,-1、101 是越界边界,这四个值往往最能暴露程序漏洞。

第二招叫等价类划分:把所有可能的输入按行为特征分成几组,每组抽一个代表即可。比如把输入分为“正整数、零、负整数、非数字”,每类选一两个典型值,就能用很少的用例覆盖很大的输入空间。第三招是随机数据生成:用程序批量制造随机输入,配合一个“参照解”来判断学生输出对不对,专门用来捕捉边界值和等价类都没想到的盲区。

但测试用例再多也怕“没测到位”。衡量到位程度的指标叫分支覆盖率——程序里每个 if/else 都是一条分岔路,覆盖率看的是测试用例到底把多少条岔路都走了一遍。走完的分支越多,说明品鉴清单覆盖的“做法流派”越全,对“这道菜合格”的结论就越有信心。

菜要一道一道尝,路要一条一条走。品鉴清单每点亮一条新的分支路径,副厨对这道菜的把握就多了一分。然而清单再完备,也不能让菜在备餐间里“裸奔”——下一节,副厨要给自己搭一个安全的隔离厨房。

覆盖率=已执行的分支数全部分支数×100%\text{覆盖率} = \frac{\text{已执行的分支数}}{\text{全部分支数}} \times 100\%
覆盖率不是“正确率”,它只回答“测了多少”,不回答“坏没坏”。但覆盖率越高,漏掉问题分支的概率就越低。
  1. 全部分支数程序所有判断路口(if/else/循环出口)的总数
    全部分支=判断点×2\text{全部分支} = \text{判断点} \times 2
  2. 已执行分支数测试用例运行中真实走过的路口数量
    已执行=∣{b∣b 被走过}∣\text{已执行} = |\{ b \mid b \text{ 被走过} \}|
  3. 比例计算已执行数除以全部数,得到 0 到 1 之间的小数
    已执行全部∈[0,1]\frac{\text{已执行}}{\text{全部}} \in [0, 1]
  4. 百分化乘以 100 得到直观百分比,便于设定质量标准
    ×100%⇒85%\times 100\% \Rightarrow 85\%

运行沙箱设计

副厨试菜有个铁规矩:必须在小隔间里进行。为什么?因为新厨师做的菜有可能是“毒蘑菇”——代码里可能藏着删除服务器文件、疯狂占用内存、甚至联网攻击的恶意指令。评测系统同样需要一个铁规矩:学生代码必须在运行沙箱内执行。沙箱就像一个全封闭的小厨房,菜在里面被翻来覆去地检查,但厨房外的一切都碰不到。

沙箱要管住的第一件事是时间。万一学生写了个死循环,程序永远跑不完怎么办?所以要设置时间预算。做法很简单:先用老师的参考解法跑一遍,记下基准时间,再乘上一个安全系数作为学生代码的“最多可运行时间”,超时就判超时并强行终止。

除了时间,沙箱还要限制内存峰值、切断网络访问、把文件系统设为只读,并限制子进程的创建数量。这就像隔离厨房不仅限时,还要提前清走所有易燃易爆品、封死对外窗户。菜再怎么折腾,也翻不出这个小隔间。但副厨的活儿还没完——尝出了问题,还得把话说清楚。

Tbudget=Treference×k(2≤k≤5)T_{budget} = T_{reference} \times k \quad (2 \le k \le 5)
时间预算 = 参考解运行时间 × 安全系数。乘 2 到 5 倍,既给正常优化留足空间,又能快速干掉死循环。
  1. 基准时间参考解法在评测机上的单次运行耗时
    Treference=参考解实测值T_{reference} = \text{参考解实测值}
  2. 安全系数2 到 5 之间的余量因子,容忍机器波动
    k∈[2,5]k \in [2, 5]
  3. 生成预算两者相乘得到学生代码的时间硬上限
    Tbudget=Treference×kT_{budget} = T_{reference} \times k
  4. 超时熔断超过预算立即终止进程并判超时
    运行时间>Tbudget⇒超时\text{运行时间} > T_{budget} \Rightarrow \text{超时}

反馈生成策略

副厨最拿手的一项技能,是“尝一口就告诉你哪里不对”。它不是含糊地说“这菜不行”,而是具体到“盐多了一克”“火大了 30 秒”。反馈生成策略要做的,就是把编译器和运行时的错误信息,翻译成学生能立刻行动的语言。

反馈分成三个层次。第一层是编译错误:学生代码根本跑不起来,系统就要截取编译器报错的关键行,把“SyntaxError: unexpected indent”这种天书翻译成“第 5 行缩进有问题”。第二层是运行异常:程序跑起来但中途崩溃,要识别出“除零错误”“数组越界”“空指针”这些常见异常,并指出崩溃发生在哪一行。第三层是逻辑错误:程序能跑但结果不对,这时候系统把期望输出和实际输出并排对比,找出从哪个位置开始分叉。

定位输出差异用的是编辑距离的思想:“期望输出”和“实际输出”都是字符串,把 A 变成 B 最少需要几次插入、删除、替换操作,这个次数就是差异的度量。编辑距离越小,输出越接近;编辑距离为 0,就说明两个文本完全一致。

点评要具体到“位置 + 原因 + 行动”。副厨不会替你重新炒一盘菜,但会精确指出你哪一步做错了——这样学生才能真正学会。不过话说回来,副厨的点评再准,如果每次尝菜时手都在抖,那评分也毫无意义。公平性保障就登场了。

ED(A,B)=min⁡{将 A 变换为 B 所需操作次数}\text{ED}(A, B) = \min \{ \text{将 A 变换为 B 所需操作次数} \}
编辑距离量化两段文本的差异程度。它能告诉副厨“从第几个字符开始,菜的味道跑偏了”,从而生成精准的定位提示。
  1. 文本 A 与 B分别代表期望输出与实际输出
    A=期望输出,B=实际输出A = \text{期望输出}, \quad B = \text{实际输出}
  2. 三种编辑操作允许插入、删除、替换单个字符
    {插入,删除,替换}\{ \text{插入}, \text{删除}, \text{替换} \}
  3. 寻找最短路径在所有编辑方案中取次数最少的一种
    min⁡{… }\min \{ \dots \}
  4. 相似度含义编辑次数越少越接近,为 0 时完全一致
    ED=0  ⟺  A=B\text{ED} = 0 \iff A = B

公平性保障

副厨试菜最怕两件事:手抖和心乱。同一道菜第一次尝觉得咸,第二次尝又觉得刚好——这不是菜的问题,是味蕾的随机波动。评测系统也有类似的“手抖”,叫环境抖动:CPU 调度、缓存命中率、后台进程抢占,都会让同一段代码在不同时刻运行时间差出一大截。

为了公平,评测系统先做“统一环境”:所有学生的代码都在同一套镜像、同一个编译器版本、同样的 CPU 配额里运行,从根上消除软硬件差异。但环境再统一,抖动也无法完全消灭,所以性能类评测要“多次运行”:同一份代码连跑 n 次,每次记录运行时间,然后取中位数作为最终成绩。

统一环境是“同一把尺子”,中位数是“多量几次再读数”。两者合起来,才保证副厨不会因为自己打了个喷嚏,就冤枉了一道好菜。但公平是对好人说的,对故意使坏的人,副厨还得有另一手准备——把危险分子挡在门外。

Tfinal=median(T1,T2,…,Tn)T_{final} = \text{median}(T_1, T_2, \ldots, T_n)
多次运行后取中位数。中位数不像平均数那样会被一次极端卡顿拉偏,是抵抗环境抖动的稳健统计量。
  1. 多次采样同一代码在相同环境跑 n 次,得到 n 个时间样本
    {T1,T2,…,Tn}\{ T_1, T_2, \ldots, T_n \}
  2. 排序取中按大小排序后取中间位置的数值作为代表
    T(⌈n/2⌉)T_{(\lceil n/2 \rceil)}
  3. 抗噪特性中位数不受个别极端值影响,比均值更稳
    medianvsmean\text{median} \quad \text{vs} \quad \text{mean}
  4. 作为最终成绩用稳定值参与打分,避免误伤学生
    Tfinal=median(… )T_{final} = \text{median}(\dots)

安全性防护

后厨有条铁律:副厨的隔间里只能用自己带来的那几把刀,谁都不许私自带刀进来。这条铁律的思想,在安全领域叫“白名单策略”。它默认一切操作都是危险的,只有被明确列入白名单的才允许执行。学生代码想读文件?白名单里只有“只读打开”这一项,写文件、删文件、联网、开子进程统统没有。

白名单之外,还有两道防线。第一道是容器资源隔离:用 Linux 的命名空间和控制组,把学生代码装进食谱受限的“独立车间”,CPU、内存、进程数都被限死,互相之间看不见也抢不到。第二道是只读文件系统:学生代码运行在只读根目录里,改不了系统文件,删不了重要数据。三管齐下,副厨的小隔间就固若金汤了。

安全这道门守住了,副厨还剩最后一个心结:两个学生端上来的菜一模一样,连葱花数量都分毫不差,这绝对不正常。怎么抓出这种“代做”行为,是防作弊机制要解决的问题。

allow(op)={1op∈W0op∉W\text{allow}(op) = \begin{cases} 1 & op \in \mathcal{W} \\ 0 & op \notin \mathcal{W} \end{cases}
白名单是“默认拒绝”:系统调用 op 属于白名单集合 W 就放行(记为 1),否则一律拒绝(记为 0)。它比黑名单更安全,因为不需要认识每个坏人,只要锁定几个好人。
  1. 系统调用程序请求操作系统执行的一次操作,如打开文件、联网、分配内存
    op=如打开文件 / 联网 / 读内存op = \text{如打开文件 / 联网 / 读内存}
  2. 白名单集合被评测系统明确允许的安全调用集合
    W={只读文件, 纯计算, 标准输出}\mathcal{W} = \{ \text{只读文件, 纯计算, 标准输出} \}
  3. 判断决策在白名单内则放行,否则拒绝
    allow(op)={1op∈W0op∉W\text{allow}(op) = \begin{cases} 1 & op \in \mathcal{W} \\ 0 & op \notin \mathcal{W} \end{cases}

防作弊机制

副厨的鼻子很灵,它只要把两盘菜并排放,就能闻出是不是出自同一口锅。代码评测系统的防作弊机制,本质上就是训练这套“嗅觉”。最常用的手段是代码相似度检测:先把代码拆成一个个语法单元(token),再比较两段代码的 token 集合有多像。

衡量相似度最经典的工具是 Jaccard 相似系数:两段代码 A 和 B 的 token 集合,交集大小除以并集大小。相似度超过阈值(比如 0.8),系统就标记为疑似抄袭,交给老师人工复核。

不过副厨深知,两盘菜都用了餐厅统一配发的盘子,不能因此就断定是同一个人做的。所以评测系统在算相似度之前,会先把老师提供的公共模板代码剔除掉,避免误伤。同时还会分析提交模式:比如比赛刚开始 10 秒就交满分答案,或者两台“不同”的机器提交时间间隔高度规律,这些异常信号都会触发人工复查。聪明的副厨既不冤枉好人,也不放过坏蛋。

J(A,B)=∣A∩B∣∣A∪B∣J(A, B) = \frac{|A \cap B|}{|A \cup B|}
Jaccard 相似系数 = 交集元素个数 ÷ 并集元素个数。值越接近 1,说明两段代码的“词汇”重合度越高。
  1. 两个集合代码 A 和 B 分别分词去重后得到的 token 集合
    A,B=token 集合A, B = \text{token 集合}
  2. 交集两边都出现过的 token 数量
    ∣A∩B∣|A \cap B|
  3. 并集两边所有不同 token 合并后的总量
    ∣A∪B∣|A \cup B|
  4. 比值判断交集占比越高,相似度越高,越可疑
    J(A,B)→1⇒疑似抄袭J(A,B) \to 1 \Rightarrow \text{疑似抄袭}

AI 应用场景

在线编程训练平台自动批改

阶段:智能评测与反馈闭环

解决的问题:传统模式下,学生每提交一次作业,老师都要手动运行代码、肉眼比对输出、逐条回复问题。一个班 200 人,每次作业要花掉一整天,而且不同老师判分标准还不统一。

具体案例:某编程学习 App 每天产生上万份代码提交。平台部署评测系统后,每份提交自动进入沙箱,跑完测试用例即刻给出“通过/失败”结论,并将输出差异用 diff 形式展示给学生。学生根据提示反复迭代,只有重复失败 3 次以上的作业才转给真人老师——老师只处理疑难杂症。

高校程序设计课程的 OJ 系统

阶段:教学辅助与学情分析

解决的问题:大班编程课作业批改标准难统一,且经常出现“复制粘贴”的作业。人工查重费时费力,还容易看漏。

具体案例:某高校在课程平台上接入评测系统,学生每次提交在容器沙箱中运行,系统自动记录编译错误、运行状态和耗时,并计算代码相似度标记可疑提交。课程结束后,老师按题目的错误类型统计出高频难点,针对“数组越界”和“边界条件遗漏”专门出了两次习题课。

在线编程竞赛的实时判题

阶段:评测决策与防作弊

解决的问题:编程竞赛提交量大、并发高,且不同选手的代码可能来自同一份“赛前模板”,公平性和安全性压力巨大。

具体案例:某算法竞赛平台高峰期每分钟收到数千次提交。评测系统用容器化沙箱横向扩容,把每次提交的判定时间压到 2 秒内;同题性能评测取中位数计分,保证不同机器评分一致;相似度检测剔除公共模板后给出可疑名单,由组委会人工复核。

Python 演示

python67 行
"""迷你代码评测系统 MiniJudge"""import osimport subprocessimport sysimport tempfile  class MiniJudge:    """像 AI 主厨助理一样试菜打分"""    def __init__(self, time_limit=2):        self.time_limit = time_limit     def run_solution(self, code, test_input):        """在独立子进程中运行学生代码"""        script = code + "\nprint(solution(" + repr(test_input) + "))"        fd, tmp = tempfile.mkstemp(suffix=".py")        with os.fdopen(fd, "w") as f:            f.write(script)        try:            result = subprocess.run(                [sys.executable, tmp],                capture_output=True,                text=True,                timeout=self.time_limit,            )        except subprocess.TimeoutExpired:            return None, "超时: 运行时间超过限制"        finally:            os.unlink(tmp)        if result.returncode != 0:            return None, result.stderr.strip()[:200]        return result.stdout.strip(), None     def judge(self, code, cases):        """逐个运行测试用例并输出结果"""        passed = 0        failed = 0        for i, (inp, expected) in enumerate(cases, 1):            out, err = self.run_solution(code, inp)            if err is not None:                print(f"[用例 {i}] 运行失败 -> {err}")                failed += 1            elif out == str(expected):                print(f"[用例 {i}] 通过")                passed += 1            else:                print(f"[用例 {i}] 失败 -> 期望 {expected} 实际 {out}")                failed += 1        print(f"评测完成: {passed} 通过 / {failed} 失败")  if __name__ == "__main__":    student_code = """def solution(x):    if x > 100:        return -1    return x * 2"""    test_cases = [        (1, 2),        (-1, -2),        (0, 0),        (100, 200),        (101, -1),    ]    judge = MiniJudge(time_limit=2)    judge.judge(student_code, test_cases)
目的:这是一个缩略版评测系统,演示了“在子进程沙箱中运行学生代码、限制超时、比对输出、给出反馈”的完整闭环。
  1. subprocess.run([sys.executable, tmp], capture_output=True, text=True, timeout=self.time_limit)把学生代码放进独立子进程运行,“隔离厨房”的关键一步
  2. timeout=self.time_limit超时熔断,死循环代码会被强行终止
  3. result.returncode != 0判断编译或运行是否出错
  4. out == str(expected)实际输出与期望输出逐字符比对,判定对错
  5. result.stderr.strip()[:200]截取报错信息并截断长度,避免反馈淹没关键信息
  6. tempfile.mkstemp(suffix=".py")为学生代码创建独立临时文件,互不干扰

常见误区

误区“测试用例覆盖率到了 100%,学生代码肯定是对的,可以放心给满分。”

正解覆盖率只回答“测了多少”,不回答“是不是都对”。就像品鉴清单把每道工序都尝了一遍,但可能漏掉了“先放盐再放糖”的顺序 bug。100% 覆盖率只能说明每行代码都被执行过,不代表所有输入都被验证过。评测系统必须结合边界值、等价类和随机数据,才能把盲区压到最低。

为何常见:覆盖率是有数字的硬指标,人容易把“都测到了”偷换成“都对”,忽略了测试用例本身的质量。

误区“沙箱嘛,就是设置一个超时,时间到了就把进程杀掉。”

正解超时只是沙箱的一个维度。真正的沙箱还要限制内存峰值、把文件系统设为只读、切断网络、限制子进程数量,防止学生代码读取服务器密码、发起 DDoS 攻击或者写满磁盘。就像隔离厨房不只限时,还要提前清走易燃易爆品、封死窗户,一样都不能少。

为何常见:超时是评测中最高频的失败类型,初学者最容易只看到这一层,而忽略了更隐蔽的安全威胁。

误区“性能评测多跑几次取平均,环境抖动就被消除了。”

正解平均数会被极端值拉偏——某次系统卡顿导致运行了 3 秒,平均值被整体抬高,成绩反而失真。中位数或最小值更稳健。就像试菜时有一口被烫到了,不能因为这一口就判定整道菜过咸,要看多数时候的真实味道。

为何常见:平均数在数学课上学过,直觉上觉得“多测几次取平均”最科学,但忽略了异常值对均值的破坏力。

误区“两段代码相似度 90%,绝对是抄的,直接判零分。”

正解老师发了公共模板或者题目限制太死时,所有学生代码天然会很相似。评测系统会先剔除模板部分再算相似度,同时结合提交时间、代码结构等多个维度综合判断。就像两盘菜都用了餐厅统一发的小碟子摆盘,不能直接断定出自同一人之手。

为何常见:用一个数字作判断最省事,但忽略了任务本身给所有学生施加的“共性约束”。

误区“反馈系统把标准答案发给学生,学生照着写,效果最好。”

正解评测的初衷是帮学生理解错在哪,而不是替代学生思考。好的反馈只指出差异位置和可能原因,比如“第 3 个字符开始与期望不符,可能边界条件写错”,引导学生自己修复。直接给答案短期看似高效,长期剥夺了学生调试能力训练的机会。

为何常见:教学同理心用错了方向——想着“帮学生省时间”,实际却损害了学习效果。

面试题

Q1. 代码评测系统判定对错最直接的依据是?

单选题 · easy

  • A.代码风格是否美观
  • B.运行输出与期望是否一致← 正确答案
  • C.代码行数是否精简
  • D.变量命名是否规范

答案:B。评测的核心是行为验证,就像副厨只关心菜的味道而不关心厨师切菜的姿势。运行输出与期望输出完全一致才算通过,风格、行数、命名都是辅助参考,不构成判定依据。

Q2. 测试用例中的“边界值”最典型的代表是?

单选题 · easy

  • A.取模运算的余数大小
  • B.输入范围的端点取值← 正确答案
  • C.代码注释的边界文字
  • D.数组的最大长度限制

答案:B。边界值是输入范围的端点,比如题目限定 0 到 100 时,0、100 以及越界的 -1、101 都是重点考察对象。就像菜的咸淡在极限处最容易失控,程序在边界处也最容易写错。

Q3. 运行沙箱最核心的目标是?

单选题 · easy

  • A.提升代码运行速度
  • B.隔离不安全操作保护环境← 正确答案
  • C.美化代码排版方式
  • D.自动修复编译错误

答案:B。沙箱就像副厨的隔离厨房,防止学生代码读取服务器文件、联网攻击或耗尽资源。它的目标是安全隔离而非性能优化,更不负责排版和自动修复。

Q4. 给评测设置时间预算最合理的方式是?

单选题 · mid

  • A.固定 1 秒不管什么题
  • B.参考解运行时间乘安全系数← 正确答案
  • C.学生运行多久就等多久
  • D.随机设定一个超时时间

答案:B。先跑参考解法得到基准时间,再乘上 2 到 5 倍的安全系数作为预算。这就像副厨按自己试菜的时间给新手留足余量,既不会误杀正常代码,又能快速干掉死循环,而固定时间和随机时间都无法适应不同题目的复杂度差异。

Q5. “评测抖动”最直接的表现是?

单选题 · mid

  • A.代码编译速度突然变快
  • B.同一代码多次运行结果不一致← 正确答案
  • C.测试用例数量自动翻倍
  • D.学生代码莫名其妙被加注释

答案:B。CPU 调度、缓存命中率、后台进程都会让同一段代码在不同时刻的运行时间出现波动,这叫“环境抖动”。抖动让“一次运行定胜负”变得不公平,因此性能评测要多次运行取中位数。

Q6. 白名单安全策略的本质是?

单选题 · hard

  • A.拦截所有已知的危险调用
  • B.只放行被明确允许的调用← 正确答案
  • C.让代码自己决定是否允许运行
  • D.编译时直接删除所有系统函数

答案:B。白名单默认拒绝一切,只放行明确安全的操作,这相当于“用自己的刀才放心”。黑名单要不停更新才能跟上新攻击方式,而白名单从一开始就把门锁死,安全性更高。

Q7. 哪些属于防止作弊的常见手段?

多选题 · mid

  • A.采用代码相似度比对← 正确答案
  • B.分析提交时间变化模式← 正确答案
  • C.检测变量名混淆与重排← 正确答案
  • D.强制学生使用同一台电脑

答案:ABC。相似度比对抓“菜谱雷同”,提交时间模式分析抓“开局秒交”或“均匀代打”,变量名混淆检测抓“换个马甲再交”的变名攻击。强制统一电脑既不现实也不公平,不是评测系统的职责。

Q8. 关于测试覆盖率,正确的说法是?

多选题 · mid

  • A.覆盖率 100% 代表代码没有缺陷
  • B.覆盖率衡量测试用例执行了多少代码← 正确答案
  • C.高覆盖率能降低遗漏 bug 的概率← 正确答案
  • D.覆盖率与测试用例质量完全无关

答案:BC。覆盖率只统计测试用例走过了多少代码,100% 也不能保证没有缺陷,就像把所有工序尝一遍也不等于尝出了所有 bug。但覆盖率越高,漏掉问题路径的概率越低,所以覆盖率与测试质量密切相关。

Q9. 设计反馈生成时应遵循哪些原则?

多选题 · hard

  • A.报错信息指明具体差异位置← 正确答案
  • B.信息越多越好不论是否有用
  • C.错误信息使用学生易懂的语言← 正确答案
  • D.直接给出正确代码让抄写

答案:AC。好的反馈像试吃点评:位置具体、语言易懂。信息过载会淹没关键问题,直接给答案剥夺了学生自己修改的机会。明确指出“从第几个字符开始对不上”比堆砌一大段报错日志有用得多。

Q10. 多次运行性能评测时,最稳健的统计量是?

单选题 · extreme

  • A.单次计时结果
  • B.多次运行取中位数← 正确答案
  • C.多次运行取最大值
  • D.目测估算运行时间

答案:B。中位数不受极端抖动影响,是抗噪声的最佳选择。单次计时太随机,最大值会被偶发卡顿拉到离谱水平,目测更是毫无公平性可言。就像试菜被烫了一口不能否定整道菜,要用多数情况下的味道做判断。