代码抄袭:那些让985学生沉默,211学生流泪的真相

代码抄袭:那些让985学生沉默,211学生流泪的真相

这是我们在TURC’18 (SIGCSE China)的论文Needle: Detecting Code Plagiarism on Student Submissions [1]的科普版本。

“我们不生产代码,我们只是互联网的搬运工”——佚名

在平均学历是985的知乎,考不上个好学校都没脸出来冒泡。然而,到底是我们上了大学,还是大学上了我们?这篇文章来谈谈一个985学生知道了会沉默,211学生知道了会流泪的真相:广泛存在的抄作业问题。我想大部分读者朋友们看到这里的感受都是:中枪了。

你还记得上大学的时候,把“大腿”的作业拿来抄抄改改,然后去打游戏的日子吗?或者如果你就是同学们心目中的“大腿”,有没有慷慨相助同学们的经历?也许这类事情的确没有发生在你身上,但在计算机学科里,我们严肃的研究发现:

  • 某985大学的一次实验大作业中,若不对代码抄袭控制,有超过82%的同学涉嫌拷贝代码(抄袭或被抄袭),并且抄袭者会对代码做出不同程度的修改。大家也都知道,计算机专业课程光说不练课可就白上了,也就是说,在一届同学中有超过4/5这门课都白上了。考虑到还有同学选择不交作业,这个比例还会再高一些……
  • 在一所985大学的一门课程中,即便使用了抄袭检测工具、制定严格的惩罚措施、给有困难的同学提供“诚信分”,一门课程整个学期下来,依然有约20%的同学涉嫌拷贝代码。甚至有同学被逮到以后还会再抄、再被逮到以后再抄代码,铤而走险,屡教不改。

(以上结论仅针对我们调研的编程实验有效,而且导致同学们抄代码的原因是多种多样的,整体的代码抄袭情况我们尚未完全统计。)

所以各位985 、211的同学们,你们中枪了吗?如果中枪的话,有没有因此感到羞愧、脸红过?代码比纸质的作业更容易拷贝,只需要从QQ上接收文件,然后改改交上去就完事了。通常,老师(助教)检查作业的方法是看着你运行程序,然后问你两个问题——对,助教并没有问什么代码的细节,于是小手一抖,分数到手,nice。

作为一个有理想的码农,今天我们就谈谈代码抄袭,哦不,怎么当个狠角色——自动把大家抄的代码给找出来。这篇文章并不是研究抄袭是怎样发生的,而是研究这样做这样一个自动工具,从一堆提交的作业里有效地把抄的代码给找出来。这个问题挺好玩的,其实可以当做大家算法课的作业:给你两份C代码,给它们之间的相似度打个分呗?


花式抄代码

江山代有才人出。这么些年来,我到底身在985,也是遇到了不少厉害的江湖高手,比如大腿同学(上)和小腿同学(下)的代码:

嗯,真聪明。正常人绝对不会这么写代码,但小腿同学知道我们会检测代码的相似度,于是故意把代码给改复杂了(也对代码的其他部分做了类似的改动),试图不被我们发现。

也有一些人把代码改得挺不一样的(真的看起来完全不一样),人工鉴定费时费力,以下是一些例子(左-右对比):

更不用提那些直接把代码拷贝过来提交的同学们啦。其实抄代码的心态完全可以理解:花最少的代价,得到足够多的分数,毕竟不劳而获是人的天性嘛。所以抄作业不能怪同学们,还是要怪老师:实验设计比较枯燥,大家没兴趣;管控不严,大家有空子可钻……所以这件事还是要老师从自己身上找原因,不如弄个工具吧?

如果能自动找出花式抄代码的人,岂不妙哉?还能起到杀鸡儆猴的作用,大家看到老师严查代码抄袭,还一抓一个准,自然就不怎么敢乱来了。脑补抱大腿同学的心理阴影面积。


代码相似度检测

问题分析

从本质上来说,检查抄代码的问题其实是给定两份代码 P_1P_2 ,你要求 d(P_1,P_2) ——它们之间的“距离”,也就是有多大可能是抄的,距离越小则抄袭嫌疑越大。度量代码的相似度还真是一个软件工程问题,例如大家都知道copy-paste代码不是个好习惯,如果一个地方改了,另一个地方就很可能忘记改,从而在软件中埋下bug。

检查代码抄袭也有很多人研究过。即便用一个n-gram模型的词频统计,也能非常有效地找出抄袭的代码(例如著名的MOSS工具[2])。不过既然发专栏文章了,咱们这回总要有点技术进步吧。

我们先来看看同学们都用哪些手段抄袭。已经有人做了总结[3]:

  • [T1] 修改注释、变量名、大小写等无关信息。
  • [T2] 代码重排、风格修改等,例如用indent工具过滤一下代码。每个人都有自己的编程风格,一般这么一来,肉眼看起来就大不一样了。
  • [T3] 增加或删除代码中的冗余成分,例如增加一些没用的代码,或者删掉一些打印的调试信息。很多同学在抄作业的时候都这么做。
  • [T4] 对数据结构、循环、局部代码做等价的重写;拆分、合并函数……这事情代价很大,但高风险高收益嘛,万一就躲过去了呢!

看到这里,也许你已经感觉中枪了,可恶的老师们早就算计到了。

网络流求解

为了解决这个问题,我们做的第一件事就是用优化的编译器编译代码(优化很重要,会把很多手工改过的等价代码优化成同样的二进制代码),并且取得每个函数的指令序列。这样一来,T1、T2和部分T3类型的修改就完全没用了,在编译器眼里,甭管你怎么缩进,怎么调整,函数编译出来都是一个样子。

然后我们把程序的相似度建模成一个二分图的最大权匹配问题,二分图左边的每个节点 u 代表 P_1 的一个函数;右边的每个节点 v 代表 P_2 的一个函数。一单位 uv 的流量代表了把 u 中的一条指令“匹配”到 v 中的一条指令,而流量带来的收益正比于函数的相似度(用带窗口的LCS计算)。

大致来说,这个网络流模型近似了程序指令序列的编辑距离(相当于对一个Integer Linear Programming做的Relaxation),细节大家可以参考我们的论文。这个模型做对T3和T4都有一定程度的抵御(即T3/T4类型的修改不会大幅降低相似度),尤其对大函数代码的拆分和合并有很不错的效果。

在实际中,我们比较每一对同学的程序,然后把那些相似度最高的同学叫来面谈就好啦……问几个代码相关的问题,或者让他们现场改一下代码就露馅了。


部署抄袭检测

985学生会沉默,211学生会流泪的真相

我们拿来了某985学校的某二年级下学期某课程某作业的全部79份提交(感谢相关任课老师/助教的大力支持)。这个作业正常实现大约有500行代码,如果独立实现,每个人的代码肯定设计得完全不一样,相似度很低。

然后跑的结果(加以人工判定)是:

  • 65/79 (82%)的提交是被确定为抄袭,即一份作业由另一份作业做少量修改得到。
  • 42/79 (53%)的提交相似度>99%,大约相当差了几个printf语句。

这只是个保守估计:我们按照所有代码对的相似度排序,一个一个看下去,直到看到第一对(人工)认为不是抄袭的代码就停止。再考虑一些没有提交作业的同学……嗯……你懂的,事情比想象还要严重。


看到这个数据,说明有大约82%的同学存在代码抄袭的问题(中枪)。实话说,我们对这个数字还是感到很吃惊的,毕竟我们做这个实验的方式相当保守。于是我们特意采访了当事的同学和助教,同学是这么说的:

“我们不生产代码,我们只是互联网的搬运工。”网上的代码太好找了,拿下来稍微改改就能运行了,何乐而不为呢?

给分的助教是这么说的:

我心里也苦啊,我一看他们的实验报告,也知道他们抄作业啊,去问他们也问不出什么,所以我也没办法管啊……总不能让大家都挂了,老师教学事故背大锅吧?只能酌情扣分了事了。

我们也询问了友校的同学们,大家对这个数字表示毫不诧异,985的同学们纷纷沉默,211的同学们流下了悔恨的泪水。

实施反抄袭

所以在这些年南京大学的《编译原理》课上,我们费尽心机(主要是许畅老师和他痛不欲生的助教们)开展了反作弊的行动。每次助教都会对代码进行抄袭检查,被判定为抄袭的同学被叫过来面谈(代码面试),最终他们都招供了,一般都是直接招供了,我们也给他们重新来过的机会。

从数据上看,每一年,基本都有一个实验会达到抄袭的高峰(15%),从一整个学期看来,仍有20%左右的同学涉嫌抄袭。当然这个数据比起没有控制的实验来说好看太多了。

还有群里堂口的兄弟先把自己吹爆然(T1/T2修改)后又被锤爆的美丽画面(真实聊天记录,有删节):


这不是终点

故事并没有结束。这个技术中的算法后来被我们在Android应用重打包检测中,欢迎参考我们专栏的另一篇文章

为什么我们要公开这个的方法呢?公开了不就靠不住了么?一方面,改代码总是很麻烦的事情(拷贝一下只要一秒钟,改得抄袭检测检查不出来,同时看起来还正常,那可费大事了),另一方面,这个工具也有局限:它对中等规模的课程项目效果不错,但对小代码(比如编程实验里几十行的OJ题)效果明显不佳。当然,公开的最主要原因还是因为后来我们开发了一劳永逸的解决抄代码的办法,而且非常灵光,屡试不爽。有时候觉得做软件研究挺好玩,有时候也觉得自己简直就是禽兽,连让大家搞事情的机会都不给。如果你有幸上过这几年南京大学的《计算机系统基础》,做过其中的PA实验,一定知道其中的厉害,嘿嘿。

留个悬念,敬请期待我们未来的文章。


参考文献

[1] Yanyan Jiang, and Chang Xu. Needle: Detecting Code Plagiarism on Student Submissions. In SIGCSE China, 2018.

[2] Saul Schleimer, Daniel S. Wilkerson, and Alex Aiken. Winnowing: Local algorithms for document fingerprinting. In SIGMOD, 2003.

[3] Neal R. Wagner. Plagiarism by student programmers. cs.utsa.edu/~wagner/pub.

论文信息:“Needle: Detecting code plagiarism on student submissions”的论文。Needle是南京大学计算机系统方向实验教学体系(Project-N)中的教学辅助工具之一,旨在帮助本科生从零开始构造完整的计算机系统(系统模拟器、处理器、操作系统、编译器等),敬请期待我们的后续工作。

作者简介:本文作者是来自南京大学的蒋炎岩博士、许畅教授。

友情感谢被锤爆的二五仔对本文提出的改进意见。

编辑于 2018-07-26

文章被以下专栏收录

    不仅是职业码农,越来越多的人都学会了编写程序来解决日常生活中的各种问题,但大家一致认为编程并不是一件非常简单的事儿。如何让编程更高效、写出的程序质量更好、实现自动维护?能“写程序找到程序里的问题”吗?甚至能“自动帮用户写点程序”吗?这些就是软件工程研究的问题,也是我们关注的面向编程者的科普内容。