摘要:
人们常常对源代码会错意,错误理解代码本身的意图,导致对代码的输出判断有失偏颇。而引起误解的源头可能是代码里特别细微而又独立的pattern,也正是这些pattern可能引起运行时错误。这些patterns有时会用在大型、流行的软件工程中,甚至会被推荐成一种代码风格。为了证明我们的观点:这些patterns确实是会造成误解的,我们从已知的容易产生混淆的代码中抽取了“造成混乱的基本结构”集,通过对73名参与者进行实验,与正常代码对比证明了这些代码patterns是会极大加剧明显的误解率的。然后,我们用更大的容易造成混淆的程序再对43名参与者进行与正常程序的对比实验。我们在本文中所使用到的方法、测试代码和数据都可以在网上获得。在原文中提供相关链接。
关键词:编程语言、程序理解
1、介绍:
源代码是起双向作用的,给机器下达指令的同时供人们理解。但不幸的是,人们和机器总是对同一代码片段有不同的理解。究其原因,就是存在某些pattern作祟,也正是它们导致了低下的生产效率、漏洞百出的产品和高额的代价。
在过去50年软件产品百花齐放,但随之接踵而来的也有各种千奇百怪的bugs,具有代表性的例子就有Apple’s ‘goto fail’ SSL bug, Ariane 5’s floating point overflow bug, and AT&T’s cascading network failure。每个这种bug的产生都是由语法层面、词法层面一些小小的错误造成的,而不是算法或者系统层面的问题。与此同时,代码的可读性、可维护性也是尤为重要的。
本文中,我们将寻找出代码中最小的但却又能造成最大影响的误解代码片段,并且将这些不可再分的、引起误解的patterns称为“atoms of confusion”,“造成混乱的基本结构”。通过这些基本结构我们可以了解到底是什么会使代码变得难以理解。在实验中,我们选用the International Obfuscated C Code Contest的冠军代码,将它分成许多小片段,请73位参与者来判断哪几段是最迷惑人的(指的是程序中含有这些片段将会和原本代码的意思不相同),然后我们将这些小片段转换成简单易懂的形式重新注入程序。接着再请43位新的参与者浏览代码,看看他们对代码的理解是否正确,从而判断出哪些小片段是最能迷惑人的。最后我们通过找出并声明这些造成混乱的代码片段作用,将统计得到到底这对我们的理解提升有多大。
贡献:
a)通过有数据支持的实验得到造成混乱的代码片段
b)大型的、公开的数据集
c)15个“造成混乱的基本结构”
d)找到与研究结论相悖的一些流行的C语言风格指导
e)深层次的实验分析:开发者们的准确率和回答时间的关系
2、相关工作:略
3、定义:
confusion(困惑):指的是人与机器对同一片段代码将输出什么结果有不同的观点。
atom(原子):指不可再分的最小的代码片段。
根据以上定义,我们实验目的是要找到atom of confusion,即造成困惑的最小代码片段。
举个例子,假设一段“困惑”的代码中含有这样一行代码 a = b++; 那么为了测试这一行到底是不是所谓的atom,我们将它拆成 a = b; b++; 然后再进行重复测试,如果机器和人同样会有不同观点,那么它就是atom的,否则就不是。
另外,为了使造成困惑代码的原因是误解,我们排除(或用其他形式表示)以下几种代码:
a)非决定性函数:例如rand()
b)没有定义的/不统一规范的:例如 a = a++
c)计算量大的
d)与api相关联的
3.1、预处理:
将所有注释删除,将所有有意义的变量名、宏改为无意义的例如v1,v2等,将所有字符串的值改成无意义的。
3.2、转换:
将所有可再分的代码转换。例如 V1 && F2(); 我们将它转换为 if(V1) { F2(); } 这样做的目的是:通过这个实验得到的atom of confusion具有泛化性,可以被学习、研究、使用。
4、atom初始集:
首先了解下什么是IOCCC比赛,它的全称是International Obfuscated C Code Contest,是一项国际编程大赛,目的是写出最有创意的最让人难以理解的C语言代码。而本文就使用了这个比赛的冠军作品,并请两位程序员将它细分成了实验用的atom of confusion初始集。
5、atom存在性实验:
为了保证最小性原则,每次测试只包含一对源代码,分别是困惑的和非困惑的代码。每个源代码大概8行,除开空白格、声明、输出等无关紧要的代码,所谓的atom of confusion大约只有1.9行。
我们请了73位来自北美大学的学生,他们至少有超过3个月的C/C++编程经验。大部分参与者的实验形式是线上的,有一些是线下的,但他们的题目都没有任何的语法提示或高亮。
5.1、实验条件:
为防止参与者在答题过程中学习到其中的规律,我们通过以下方面控制学习率:
a)题目乱序
b)每对源代码之间隔着大约11道题(源代码)
c)随机赋值源代码中的常量
同时,我们将正确输出和错误输出区分开,意思是它们的输出不会相同,否则将无法判断参与者是否正确理解式子。
举个例子,假设我们想测试参与者是否理解取模符号,那么我们不会采用8%3这样的式子,因为参与者们如果将%当做/除号理解,那么将也会得到相同的答案2。因此我们可以用8%2来代替上述式子。
5.2、统计分析:
McNemar’s test(麦克尼马尔)、Durkalski correction。其中麦克尼马尔测试可以提供一个“卡方检验”统计,包含了p值和影响大小。
p值:用来反映候选atom是否与正常代码无异,p值越小说明看上去越像正常代码。
影响大小:顾名思义用来描述候选atom在正常编程中的影响程度。
5.3、结果
atoms of confusion对我们的实验参与人员造成了较大的影响。
5.3.1、哪些候选atom可以作为最终结果atom?
选用p < 0.05的。且根据影响大小可以看出这些atom不仅确实很迷惑人,也频繁出现在大部分编程中。另外,由正确率变化△,代表删除atom前后的正确率变化,可以证明这些atom很大程度影响了开发者们的判断,使他们混淆。
根据表2,有4个没有被选用的候选atom,它们分别是:
a)死亡、不可到达、重复
b)逻辑运算
c)指针运算
d)常量
究其原因,是因为不管有没有删除或转换这些atom,它们的正确率都没有明显提升,要么正确率都很低要么都很高。而且由于数据量还不足够庞大,本文还没有细致对它们会造成怎样的影响做分类,这个留到以后的研究中。
5.3.2、是否参与者们都会犯同样的错误?
对每个atom都不尽相同,视乎它们的影响大小。
第一个例子:
printf(“%d\n”, 013);
正确的输出结果是11,因为有0开头,编译器会把整个数当做八进制数来输出。而很多参与者都忽略了开头的0,大部分认为会输出13.
第二个例子:
int V1 = 3;
int V2 = (V1 *= 2, V1 += 1);
printf(“%d %d\n”, V1, V2);
正确的输出结果是7 7,括号里的表达式是从左到右进行。而这道题就有五花八门的答案。
5.3.3、问题的先后次序对正确率有怎样的影响?
一对源代码中后面出现的那个正确率较高一点,不过对与对之间的问题次序影响不大。
5.3.4、速度与准确率是否存在某种关系?
结果显示回答速度与正确率是正相关关系,回答速度越快,正确率越高。这可能是因为难的题目需要思考更久,而简单的参与者们驾轻就熟。
6、atom影响力实验:
为了了解造成混乱的基本结构会造成多大的影响,我们用比之前实验代码规模更大一点的新代码和新参与者来进行新的实验。
6.1、设计:
6.2、分析:
我们使用的评分系统会自动阶段性评分,而不会因为参与者某一步得出错误答案而导致后面的答案都错。
6.3、结果:
6.3.1、是否清晰的代码比混乱的代码正确率高?
清晰的代码正确率大约比混乱的代码高一倍。
6.3.2、还有什么其他衡量参与者表现的方法?
放弃的题数和耗费的时间。
6.3.3、在清晰的程序中有哪些代码仍然会造成混乱?
可以通过同样的方法得到:从清晰程序中摘出或转换部分代码并让参与者们对“相对清晰”
和“相对混乱”的程序再做实验对比。
6.3.4、参与者的自我评价怎样?
7、讨论:
我们的得到atoms of confusion是否专家的看法会不一样?我们的工作是否足够泛化?
7.1、有哪些存在于流行风格指导中的atoms被忽略?
大部分我们得到的atoms都在这些风格指导中,它们往往被这些教程所忽视。
7.2、我们的结果与风格指导推荐有那些不同?
以下几类在流行风格教程中常被忽略:
a)赋值操作:例如在while语句中赋值
b)指针运算
c)大括号
d)条件操作:例如?:
7.3、参与者的编程经验会对结果有影响吗?
经验越丰富,正确率越高。
8、误差:
内部误差:在指针运算方面依然存在atoms of confusion没有被找出来,我们应该进一步将问题设置得简单一点才能找到隐藏的混乱片段。初始候选atoms的选取可能掺杂了一些主观意念,如果让另一个团队来挑选,可能结果也会不一样。这些也都可以在未来的实验里进一步去完善。
外部误差:由于我们的测试代码本来就是从“混乱的比赛”中挑选出来的,其中有可能存在部分代码在正常编程中根本不会用到。而且我们实验的代码可能不是参与者所熟悉的内容,也有可能是他们平常经常使用的模块,所以参与者的熟悉程度也或多或少有影响,我们也观察不出来。另外,经转换后的代码可能在高层语义上对参与者有影响。最后,我们所选的参与者大部分是大学生而非各行各业的从业编程人员,所以我们的结果可能不适用于所有人。
9、总结:
我们通过两个实验去评判找出代码中容易使人混乱的片段,并且通过统计学的方法得出了人们相较于正常代码,更难以理解含有混乱片段的代码。我们也证明了如果移除这些“混乱的基本结构”将对人们对代码的理解有多大的帮助。我们在为一些现有的编程教程给出支持它们和反对它们的证据的同时,也提出了一些能改善教程的方法。除此之外,在探索的过程中我们得出了以下几条有趣的结论:
a)可以通过迭代我们的影响力实验不断挖掘出潜在的atoms of confusion。
b)一些大型开源项目可以为它们的atoms of confusion加些注释或做些修改。
c)我们得出的结果不仅可以应用在C语言上,甚至在其他语言上可以找到相似的影子。
d)可以使用HCI技术探索为什么编程人员会难以理解这些atoms of confusion、产生这些片段的原因以及可以怎么预防产生等等。