java查重代码_雷池蹦迪:妈妈再也不怕我被代码查重

据统(hu)计(che),90%以上的学生都抄过作业,也许是摸鱼太久来不及写,也许干脆是自己做不来又学不会,也许是因为作业太无趣,全都是重复工作——总之,抄作业使每个学生生活不可分割的一部分,也是一项非常实用的技能。

不过,俗话说魔高一尺,道高一丈,老师在对付抄作业上也算是下足了功夫,在这信息化和互联网的发展大潮中,不少老师都选择使用软件对作业进行查重,试图让抄作业的学生无处遁形——尤其是在各类编程课上,代码查重成为了检测作弊的首要手段,并且也在实践中取得了相当的效果。

我们今天所要介绍的,正是对抗代码查重的手段:具体地说,是在不改变代码运行方式的前提下,对代码进行修改,以避免被查重的方法,这能让你在最短的时间中完成作业,可能在关键时刻救你一命。

那么,让我们开始战(yue)斗(hui)吧!

注:本文中,我们主要使用C语言代码进行说明,主要是因为某三本学校有一大半编程课都是要求写C,积累的经验比较丰富。当然,相信各位聪慧的读者也能轻易举一反三,将这些技巧应用到其他语言中去。

一、重命名

毋庸多言,这一条可谓基础中的基础,改改函数名,换换变量名,这种操作最大的好处是简单,连许多IDE都能完成。另一方面,重命名是之后很多修改的基础,让老师看不懂你的代码,更加没有心思来猜测你是不是抄了作业。不过,如果你不仅想要完成作业,还想分数好看一点的话,新的名字也要画上一点心思,如果过于混乱的话可能因为代码风格而被扣分。

总之,常见的命名方法如下:

1. 字母表

int a = 0;
char *b = "foo";
for (int c = 1; c <= n; ++c) {}

OI选手的标准做法,挨个字母轮下去就好,也有比如aaaaaa或者a1a2a3的变体,总之是没有特征,也不提供任何有价值的信息。

混乱指数:★★★☆☆

呕吐指数:★★★☆☆

2. 啊,我的眼睛!

int IIllIIlllllIIlII = 0;
char *OO00O00OOO000O = "foo";
for (int lII1Illll111IlI1 = 1; lII1Illll111IlI1 <= O00OO000OOO0; ++lII1Illll111IlI1) {}

某个Java代码混淆器的重命名方案,为阅读反编译后的代码的程序员造成极大困扰,可能导致视觉障碍,如果出现上述症状,请立即前往最近的医院就医。

混乱指数:★★★★☆

呕吐指数:★★★★★

3. 超详细!

int number_of_cars = 0;
char *current_user_name = "foo";
for (int student_index = 0; student_index <= number_of_students; ++student_index) {}

又长又详细的名字,一看就是认真完成的作业,大概能有加分(尤其当老师是Java程序员的时候),不过可能需要你先读懂代码,耗时较长。另一种方法是使用长而无意义的名字,不过比较容易露馅就是。

混乱指数:★☆☆☆☆

呕吐指数:☆☆☆☆☆

4. 语言学带师

void get_number_of_students(); // void get_students_number(); 
int balance = 0; // int money = 0;
struct vehicle_data {}; // struct car_info {};

调整语序、近义词、不同的描述角度,字典和翻译软件是你的好帮手。如果哪一天代码查重连这种东西都能查出来了,那图灵测试就离通过不远了。(人类也离灭亡不远了)

混乱指数:★★☆☆☆

呕吐指数:★☆☆☆☆

另外,有一些代码是公开允许抄的,比如用于描述某个文件头的struct之类的,通常来自于老师的某一份讲义,如果你把这些也重命名掉了可能造成不必要的迷惑。 总之,重命名是基础中的基础,能够有效地提高代码的区分度,但单独使用还是比较危险。


二、交换顺序

研表究明,汉字的序顺并不一定能影阅响读,比如当你看完这句话后,才发这现里的字全都是乱的。

不过,如果你把这句话丢进翻译软件的话,多半会得到一个狗屁不通的结果。——这告诉我们,人类可以轻松理解的东西,对于计算机来说可能并不是这样。因此,将代码中的函数、定义等部分调整顺序,能够有效地欺骗各种查重软件。

具体地说,最保险的做法是利用C语言declaration(声明)和definition(定义)分离的特性,在保证正确性的前提下调整代码的顺序,比如将:

struct foo {
  int n;
};
void bar(struct foo *f) {};
int main() {
  bar(NULL);
}

改成:

struct foo;
void bar(struct foo *);
int main() {
  bar(NULL);
}
struct foo {
  int n;
};
void bar(struct foo *f) {};

也就是说,把原先代码中声明和定义分开的地方改成一起的,把原先一起的改成分开的,能够大幅度调整代码的结构,并且不会修改程序的运行方式。

当然,如果你确定两个函数之间没有依赖关系的话,也可以直接交换他们的定义顺序,甚至做更大幅度的调整,总之在花费的时间和效果之间取一个平衡。

其他包括结构体定义、全局变量声明、#include等等都可以进行顺序交换,我一般将这种操作称作shuffle,因为这个词也是洗牌的意思。理想的情况下,可以在交换时保持一定的逻辑关系,比如main()函数放在最后,结构体和相关的函数定义放在一起等等,这些不仅让你的代码看起来富有智慧,并且也能给分数带来一定的加成。

更高级的交换顺序是交换结构体成员或者代码行的顺序,比如将:

struct foo {
  int a;
  char b;
};
void bar() {
  struct foo f;
  f.a = 1;
  f.b = '2';
} 

改成:

struct foo {
  char b;
  int a;
};
void bar() {
  struct foo f;
  f.b = '2';
  f.a = 1;
}

请注意,这种做法有一定的风险,比较容易不小心改变程序的运行方式,浪费大量时间用于debug(而这是与我们抄作业的初衷相悖的),因此建议仅在确定这样做不会有问题时使用。常见的情况包括给一个结构体的多个成员赋值、或者明显并列的几句代码等,在这种地方交换顺序比较保险,也不需要认真阅读代码。


三、同义转换

所谓的同义转换,就是保证代码工作方式不变的前提下,改变代码的书写方式——

——这话是不是之前说过了?

有一说一,的确如此,不过我们这里指的是识别出源代码种的固定模式,根据一定的套路进行改写,这样既能有效防止查重,也不会消耗太多精力。废话不多说,直接看代码:

// original
if (a < b) { /* XXX */ }
else { /* YYY */ }

// modified
if (a >= b) { /* YYY */ }
else { /* XXX */ }

// original
for (int i = 0; i < 100; ++i) { /* XXX */ }

// modified
int i = 0;
while (i < 100) {
  // XXX
  ++i;
}

// original
while (/* something */) {
  if (a > 0) {
    if (b > 0) {
      // XXX
    }
  }
}

// modified
while (/* something */) {
  if (a <= 0) continue;
  if (b <= 0) continue;
  // XXX
}

// original
void foo() {
  if (a > 0) {
    // XXX
  } else {
    // YYY
  }
}

// modified
void foo() {
  if (a > 0) {
    // XXX
    return;
  }
  // YYY
}
 
// original
int a = 0, b = 0;

// modified
int a;
int b;
a = b = 0;

// original
void foo(int a, char *b) {}
foo(c, d);

// modified
void foo(char *b, int a) {}
foo(d, c);

// original
a[0] = 0;
a[1] = 1;
a[2] = 2;

// modified
#define SET(A, N) (A)[N] = (N)
SET(a, 0);
SET(a, 1);
SET(a, 2);

当然,这里举得都是一些非常基础的例子,主要是展示一下思路,实际上随着使用者的水平增加,也可以自己发现更多的模式进行改写,以达到更好的效果。

总的来说,技术无罪,学习防止查重的技巧某种程度上也是在促进查重相关技术的发展。不过,在抄作业的同时,还请始终保持愧疚的心态吧。

以上。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值