阅读更多

4顶
0踩

编程语言

转载新闻 编程中的“末行效应”

2014-11-18 11:14 by 正式编辑 cao345657340 评论(2) 有5579人浏览
我研究过数百个因“拷贝-粘贴”导致的错误。可以肯定的是,程序员常常会在一大段代码的最后一段里犯错。好像还没有任何编程书讨论过这种现象,因此我决定自己写点什么。我称之为“末行效应”。

拷贝粘贴

我叫Andrey Karpov,我的工作有点不寻常:我借助静态分析工具研究各种应用程序代码,并描述从中找到的错误或者缺陷。我这么做既有实际效益也因为工作需要。使用的方法正是基于我们公司所推广的PVS-Studio和CppCat工具的原理。套路很简单:找bug,然后写文章分析bug,文章吸引到潜在用户的注意,接着就是收益。但今天这篇文章不是介绍这些工具的。

在分析各种软件项目的过程中,我把找到的bug以及相关代码存入一个特殊的数据库。顺便说一下,有兴趣的话各位可以看一看这个数据库。我们把它转换成网页格式并上传到了公司网站的“Detected errors”栏下。

这个数据库独一无二!目前它收录了1500块问题代码片,正等着程序员们去研究,从中总结出特定规律。为将来的研究,手册和文章奠定一个基础。

我还没认真地分析过目前搜集到的材料。但是过程中我发现有一个明显的模式反复出现,决定深入研究一下。你大概看到了,文中我反复使用短语“注意最后一行”。在我看来,这一定有某种规律。

末行效应

编程的时候,程序员常常需要写一系列相似的结构。逐行敲键盘输入无聊且低效。这就是为什么他们会使用奥义-“拷贝-粘贴”大法:一段代码被拷贝粘贴几次,然后修改。谁都知道这样做的坏处:你很容易在粘贴后忘记修改某些内容最后滋生出问题。不幸的是,常常找不到比这更好的方法。

那么我发现了什么规律呢?我发现错误常常发生在最后的一块粘贴代码里。

下面是一个简短的例子:
inline Vector3int32& operator+=(const Vector3int32& other) {
  x += other.x;
  y += other.y;
  z += other.y;
  return *this;
}

注意这一行:”z += other.y;”。程序员忘记把‘y’替换成‘z’了。

也许你以为这是个假设的例子,然后它其实来自一个真实的应用程序。接下来,我会让你相信这是高频常见的一种错误。程序员们经常在一连串相似操作的结尾犯这种错误。

我听说攀岩者常常在最后的几十米中滑落下来。并不是因为他们累了,而正是由于他们对即将到达的终点过于兴奋,他们想象着成功后的喜悦,变得疏忽大意,最后失足。我猜想程序员们也是这样的。

接下来看一组数据。

研究了数据库后,我分离出了84个代码段由“拷贝-粘贴”大法生成。其中41段中错误发生在中间的某些粘贴块。比如:
strncmp(argv[argidx], "CAT=", 4) &&
strncmp(argv[argidx], "DECOY=", 6) &&
strncmp(argv[argidx], "THREADS=", 6) &&
strncmp(argv[argidx], "MINPROB=", 8)) {

“THREADS=”字符串的长度是8个字符,而非6。

另外的43段代码中,错误发生在最后的粘贴块。

当然,43比41大不了多少。但是请注意,一段程序中,可能有很多类似的代码块,因此错误可能发生在第一,第二,第五甚至第十块中。因此在其他代码块中我们有一个相对均匀的分布,而最后一块却存在一个峰值。

平均而言,相似代码块总数为5。

于是前面4个代码块中均匀分布了41处错误,平均每块代码有10个错误。

然而最后一块代码中有43个错误!

下面的分布概图凸显出这个现象:

图1. 五块类似代码段中的错误分布概图

因此我们可以总结出一个规律:

在最末的粘贴代码块中出错的概率是其他代码块的4倍。

这个规律可能并没有普适性。它只是个有趣的发现,其实际效用在于:提醒在你写最后一块的时候保持警觉。

实例:

下面我要证明这并不是我的胡思乱想而是有真实的趋势的。请看下面的实例。

当然,我不会列出所有例子,仅列举简单而有代表性的。
[size=medium][b]Source Engine SDK[/b][/size]
inline void Init( float ix=0, float iy=0,
                  float iz=0, float iw = 0 ) 
{
  SetX( ix );
  SetY( iy );
  SetZ( iz );
  SetZ( iw );
}

最后一行应该是SetW()。

Chromium
if (access & FILE_WRITE_ATTRIBUTES)
  output.append(ASCIIToUTF16("\tFILE_WRITE_ATTRIBUTES\n"));
if (access & FILE_WRITE_DATA)
  output.append(ASCIIToUTF16("\tFILE_WRITE_DATA\n"));
if (access & FILE_WRITE_EA)
  output.append(ASCIIToUTF16("\tFILE_WRITE_EA\n"));
if (access & FILE_WRITE_EA)
  output.append(ASCIIToUTF16("\tFILE_WRITE_EA\n"));
break;

最后两行相同。

ReactOS
if (*ScanString == L'\"' ||
    *ScanString == L'^' ||
    *ScanString == L'\"')

Multi Theft Auto
class CWaterPolySAInterface
{
public:
    WORD m_wVertexIDs[3];
};
CWaterPoly* CWaterManagerSA::CreateQuad (....)
{
  ....
  pInterface->m_wVertexIDs [ 0 ] = pV1->GetID ();
  pInterface->m_wVertexIDs [ 1 ] = pV2->GetID ();
  pInterface->m_wVertexIDs [ 2 ] = pV3->GetID ();
  pInterface->m_wVertexIDs [ 3 ] = pV4->GetID ();
  ....
}

最后一行冗余代码来自于惯性粘贴。数组的大小是3。
Source Engine SDK
intens.x=OrSIMD(AndSIMD(BackgroundColor.x,no_hit_mask),
                AndNotSIMD(no_hit_mask,intens.x));
intens.y=OrSIMD(AndSIMD(BackgroundColor.y,no_hit_mask),
                AndNotSIMD(no_hit_mask,intens.y));
intens.z=OrSIMD(AndSIMD(BackgroundColor.y,no_hit_mask),
                AndNotSIMD(no_hit_mask,intens.z));

程序员忘记把最后一行的中的“BackgroundColor.y”改成“BackgroundColor.z”。

Trans-Proteomic Pipeline
void setPepMaxProb(....)
{  
  ....
  double max4 = 0.0;
  double max5 = 0.0;
  double max6 = 0.0;
  double max7 = 0.0;
  ....
  if ( pep3 ) { ... if ( use_joint_probs && prob > max3 ) ... }
  ....
  if ( pep4 ) { ... if ( use_joint_probs && prob > max4 ) ... }
  ....
  if ( pep5 ) { ... if ( use_joint_probs && prob > max5 ) ... }
  ....
  if ( pep6 ) { ... if ( use_joint_probs && prob > max6 ) ... }
  ....
  if ( pep7 ) { ... if ( use_joint_probs && prob > max6 ) ... }
  ....
}

程序员忘记把最后一个判断中的“prob > max6”改为“prob > max7”。

SeqAn
inline typename Value<Pipe>::Type const & operator*() {
  tmp.i1 = *in.in1;
  tmp.i2 = *in.in2;
  tmp.i3 = *in.in2;
  return tmp;
}

SlimDX
for( int i = 0; i < 2; i++ )
{
  sliders[i] = joystate.rglSlider[i];
  asliders[i] = joystate.rglASlider[i];
  vsliders[i] = joystate.rglVSlider[i];
  fsliders[i] = joystate.rglVSlider[i];
}

最后一行应该用rglFSlider。

Qt
if (repetition == QStringLiteral("repeat") ||
    repetition.isEmpty()) {
  pattern->patternRepeatX = true;
  pattern->patternRepeatY = true;
} else if (repetition == QStringLiteral("repeat-x")) {
  pattern->patternRepeatX = true;
} else if (repetition == QStringLiteral("repeat-y")) {
  pattern->patternRepeatY = true;
} else if (repetition == QStringLiteral("no-repeat")) {
  pattern->patternRepeatY = false;
  pattern->patternRepeatY = false;
} else {
  //TODO: exception: SYNTAX_ERR
}

最后一块少了‘patternRepeatX’。正确的代码应该是:
pattern->patternRepeatX = false;
pattern->patternRepeatY = false;

ReactOS
const int istride = sizeof(tmp[0]) / sizeof(tmp[0][0][0]);
const int jstride = sizeof(tmp[0][0]) / sizeof(tmp[0][0][0]);
const int mistride = sizeof(mag[0]) / sizeof(mag[0][0]);
const int mjstride = sizeof(mag[0][0]) / sizeof(mag[0][0]);

‘mjstride’永远等于1。最后一行应该是:
const int mjstride = sizeof(mag[0][0]) / sizeof(mag[0][0][0]);

Mozilla Firefox
if (protocol.EqualsIgnoreCase("http") ||
    protocol.EqualsIgnoreCase("https") ||
    protocol.EqualsIgnoreCase("news") ||
    protocol.EqualsIgnoreCase("ftp") ||          <<<---
    protocol.EqualsIgnoreCase("file") ||
    protocol.EqualsIgnoreCase("javascript") ||
    protocol.EqualsIgnoreCase("ftp")) {          <<<---

最后的“ftp”很可疑,它之前已经被比较过了。
Quake-III-Arena
if (fabs(dir[0]) > test->radius ||
    fabs(dir[1]) > test->radius ||
    fabs(dir[1]) > test->radius)

dir[2]的值忘记检查了。
Clang
return (ContainerBegLine <= ContaineeBegLine &&
        ContainerEndLine <= ContaineeEndLine &&
        (ContainerBegLine != ContaineeBegLine ||
         SM.getExpansionColumnNumber(ContainerRBeg) <=
         SM.getExpansionColumnNumber(ContaineeRBeg)) &&
        (ContainerEndLine != ContaineeEndLine ||
         SM.getExpansionColumnNumber(ContainerREnd) >=
         SM.getExpansionColumnNumber(ContainerREnd)));

最后一块,“SM.getExpansionColumnNumber(ContainerREnd)”表达式在跟自己比较大小。

MongoDB
bool operator==(const MemberCfg& r) const {
  ....
  return _id==r._id && votes == r.votes &&
         h == r.h && priority == r.priority &&
         arbiterOnly == r.arbiterOnly &&
         slaveDelay == r.slaveDelay &&
         hidden == r.hidden &&
         buildIndexes == buildIndexes;
}

程序员把最后一行的“r”忘记了。

Unreal Engine 4
static bool PositionIsInside(....)
{
  return
    Position.X >= Control.Center.X - BoxSize.X * 0.5f &&
    Position.X <= Control.Center.X + BoxSize.X * 0.5f &&
    Position.Y >= Control.Center.Y - BoxSize.Y * 0.5f &&
    Position.Y >= Control.Center.Y - BoxSize.Y * 0.5f;
}

最后一行中,程序员忘记了两个地方。首先,“>=”应改为“<=”,其次,减号应改为加号。

Qt
qreal x = ctx->callData->args[0].toNumber();
qreal y = ctx->callData->args[1].toNumber();
qreal w = ctx->callData->args[2].toNumber();
qreal h = ctx->callData->args[3].toNumber();
if (!qIsFinite(x) || !qIsFinite(y) ||
    !qIsFinite(w) || !qIsFinite(w))

最后一个qlsFinite中,传入参数应该是‘h’。

OpenSSL
if (!strncmp(vstart, "ASCII", 5))
  arg->format = ASN1_GEN_FORMAT_ASCII;
else if (!strncmp(vstart, "UTF8", 4))
  arg->format = ASN1_GEN_FORMAT_UTF8;
else if (!strncmp(vstart, "HEX", 3))
  arg->format = ASN1_GEN_FORMAT_HEX;
else if (!strncmp(vstart, "BITLIST", 3))
  arg->format = ASN1_GEN_FORMAT_BITLIST;

字符串“BITLIST”长度为7,而非3。

就此打住吧。我举的例子已经够说明问题了吧?

结论

本文告诉你“拷贝-粘贴”大法在最后一个粘贴代码块中出错的概率很可能是其他块的4倍。

这跟人类的心理学有关,与技术水平无关。文中说明了即便是像Clang或者Qt项目中的编程高手也会犯这种错误。

我希望这个现象的发现对于程序员们有所帮助,也许可以促使他们去研究我们的bug数据库。相信如此有助于在这些错误中发现新的规律并总结出新的编程建议。
来自: 开源社区
4
0
评论 共 2 条 请登录后发表评论
2 楼 yunzhu 2014-11-18 14:53
作者对程序员心理研究颇深啊,不过这文章被搞得真够乱的,图片全都看不到,文字中还有html
1 楼 jornye 2014-11-18 14:48
tks,以后犯这种错有理由了~

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • 现代软件工程 第四章 【结对编程】练习与讨论

    4.7.0 结对编程的练习题 地铁导航和遍历 4.7.1 结对项目的案例和论文 在现代软件工程教学的过程中,同学们已经总结了不少切身体会。例如: 总结1[i]:那是project到了比较关键的创造阶段,整整一天,我们俩椅子靠...

  • 从立创EDA,Gratipay看中文编程开发环境和推广运营的一个趋势

    前不久听说立创EDA,对比之前的讨论: 适合中文用户的编程语言和IDE, 侧重于现有语言/IDE不具备的特性 · Issue #11 · program...软件本来源于硬件,库自然也存在依赖关系,EDA中的元件库,类似于编程语言的标准库和...

  • 从电影《蝴蝶效应》中学习回溯算法的核心思想

    点击上方“视学算法”,选择加&#34;星标&#34;或“置顶”重磅干货,第一时间送达关注我们丨文末赠书深度优先搜索算法利用的是回溯算法思想。这个算法思想非常简单,但应用却 非常广泛。它除用...

  • 漫游处理器缓存效应

    也是并发编程设计中的技术难点,而且相关参考资料如同过江之鲫,浩瀚繁星,阅之如临深渊,味同嚼蜡,三言两语难以入门。正好网上有人推荐了微软大牛Igor Ostrovsky一篇博文《漫游处理器缓存效应》 ( ...

  • 编程语言发展史之:编程语言与量子计算

    因此,工程师们更倾向于采用机器学习、深度学习等新的计算方式,并希望这些计算模型能够解决现实世界中遇到的新问题。在探索新的计算方式时,工程师们需要掌握一些编程语言知识,例如掌握哪些编程语言比较适合量子...

  • 对编程很有用的自然效应

    【蝴蝶效应】【青蛙现象】【鳄鱼法则】 【鲇鱼效应】 【羊群效应】 【刺猬法则】 【手表定律】 【破窗理论】【二八定律】【木桶理论】 【马太效应】 【鸟笼逻辑】 【责任分散效应】【帕金森定律】 【晕轮效应】...

  • 编程语言那些事儿

    前言:本文根据网上各方材料整理总结而成。本人过去几年使用过的编程语言包括:C、C++、C#、...编程语言五大家族早期的编程语言分为:FORTRAN、 COBOL、 LISP、 BASIC、 和ALGOL 家族。这些语言为不同的社会群体而设...

  • 什么是COBOL? COBOL编程说明

    有些技术永不消亡,它们只是逐渐消失在木制品中。 向普通软件开发人员询问有关COBOL(面向通用商业语言)的信息,他们会看着您,就像您提到复写纸,含铅汽油或78 RPM记录一样。 与Go或Python甚至Pascal或C!之类的...

  • 数据中台不是“银弹” | 文末赠书

    * 留言点赞排名前2位即可获得免费赠书要不要建设数据中台,这是每个需要做数字化运营的公司必须回答的问题。数据中台所提供的功能肯定是企业需要的。那么何时开始建设数据中台,如何在开始数字化建设...

  • 面向对象编程是否走向了消亡

    不,面向对象编程(OOP)并没有消亡。但它远没有以前那么流行了。 我记得当时在90年代,关于面向对象编程的教科书和计算机科学课程很多。当时那就是“风口”,下一波潮流。如果你没有以那种方式编程,你就不是一个...

  • 附录B 编程的本质

    附录B 编程的本质 编程的本质 N 尼古拉斯·沃斯(Niklaus Wirth,1934年2月15日—),生於于瑞士温特图尔,是瑞士计算机科学家。Pascal语言之父。 让我们暂时撇开平台、框架、技术、设计模式、对象思想、敏捷开发论...

  • 量子力学中的引力子

    引力子,Graviton,又称重力子,在物理学中是一个传递引力的假想粒子(仍未知是否真正存在)。两个物体之间的引力可以归结为构成这两个物体的粒子之间的引力子交换。为了传递引力,引力子必须永远相吸、作用范围无限...

  • python中集合的元素可以是什么_Python集合中的元素可以是列表。

    Python集合中的元素可以是列表。答:×11、以下哪个礼节,【不是】丧礼中的内容?答:B 迎神中国大学MOOC: 双相情感障碍的终生患病率为2.4%,我国平均患病率达到1.5%。( )岁是双相情感障碍的主要发病高峰答:15-30两...

  • igm焊接机器人基本操作_焊接机器人编程与操作

    早在70年代末,上海电焊机厂与上海电动工具研究所,合作研制的直角坐标机械手,成功地应用于上海牌轿车底盘的焊接。一汽是我国最早引进焊接机器人的企业,1984年起先后从KUKA公司引进了3台点焊机器人,用于当时...

  • java基础总结(十八)--语言编程规范

    5.1.4 行末注释 5.2 文挡注释 6 声明 6.1 每行声明变量的数量 6.2 初始化 6.3 布局 6.4 类和接口的声明 7 语句 7.1 简单语句 7.2 复合语句 7.3 返回语句 7.4 if,if-else,if ...

  • 大数据时代,如何在企业中构建个性化的决策系统?

    1990年代末,互联网的爆炸式增长彻底改变了信息技术和经济模式。随着互联网技术的迅速发展,网络效应已经成为一种普遍现象。而当今社会的日益多样化、高度复杂化,传统的线下模式已经无法满足需求。在这种情况下,...

  • sun公司java编程规范【转载】

    java编程规范要学习的大致内容有如下部分,一个目录: Java编码规范 1 1. 说明 3 1.1 为什么要有编码规范 3 1.2 版权声明 3 2. 文件名(File Names) 3 2.1 文件后缀(File Suffixes) 3 3.1 Java...

  • Python项目-自动办公-56 Word_docx_格式套用.zip

    Python课程设计,含有代码注释,新手也可看懂。毕业设计、期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。 包含:项目源码、数据库脚本、软件工具等,该项目可以作为毕设、课程设计使用,前后端代码都在里面。 该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。

  • 《松鼠》生态性课堂体验教案.docx

    《松鼠》生态性课堂体验教案

Global site tag (gtag.js) - Google Analytics