标签 编程语言 下的文章

这篇漫画生动的描绘了不同时期不同编程语言的“战争”。

COBOL 是一种面向数据处理的、面向文件的、面向过程(POL)的高级编程语言,功能强大,但缺点是语法繁杂,代码冗长(看它肥的)。而 Java 当时还是个弱小的新生事物。

后来,Java 慢慢发展壮大了,内存吃的越来越多了(都和 COBOL 一样又高又肥了),当 Java 准备再去打一架的时候,COBOL 提醒他注意身后想打他们两个的三个小娃娃(Scala、Kotlin、Go)。

长江后浪推前浪。编程语言的发展很快,不停的会有功能更强的新“轮子”出现在我们眼前。而我们则需了解各个语言的优缺点,与时俱进地看待它们。


via: http://turnoff.us/geek/programming-languages-battle/

作者:Daniel Stori 译者:ItsLucas 点评:ItsLucas 校对:Bestony 合成:Bestony

本文由 LCTT 原创编译,Linux中国 荣誉推出

多语言编程环境是一把双刃剑,既带来好处,也带来可能威胁组织的复杂性。

如今,随着各种不同的编程语言的出现,许多组织已经变成了 数字多语种组织 digital polyglots 。开源打开了一个语言和技术堆栈的世界,开发人员可以使用这些语言和技术堆栈来完成他们的任务,包括开发、支持过时的和现代的软件应用。

与那些只说母语的人相比,通晓多种语言的人可以与数百万人交谈。在软件环境中,开发人员不会引入新的语言来达到特定的目的,也不会更好地交流。一些语言对于一项任务来说很棒,但是对于另一项任务来说却不行,因此使用多种编程语言可以让开发人员使用合适的工具来完成这项任务。这样,所有的开发都是多语种的;这只是野兽的本性。

多语种环境的创建通常是渐进的和情景化的。例如,当一家企业收购一家公司时,它就承担了该公司的技术堆栈 —— 包括其编程语言。或者,随着技术领导的改变,新的领导者可能会将不同的技术纳入其中。技术也有过时的时候,随着时间的推移,增加了组织必须维护的编程语言和技术的数量。

多语言环境对企业来说是一把双刃剑,既带来好处,也带来复杂性和挑战。最终,如果这种情况得不到控制,多语言将会扼杀你的企业。

棘手的技术绕口令

如果有多种不同的技术 —— 编程语言、过时的工具和新兴的技术堆栈 —— 就有复杂性。工程师团队花更多的时间努力改进编程语言,包括许可证、安全性和依赖性。与此同时,管理层缺乏对代码合规性的监督,无法衡量风险。

发生的情况是,企业具有不同程度的编程语言质量和工具支持的高度可变性。当你需要和十几个人一起工作时,很难成为一种语言的专家。一个能流利地说法语和意大利语的人和一个能用八种语言串成几个句子的人在技能水平上有很大差异。开发人员和编程语言也是如此。

随着更多编程语言的加入,困难只会增加,导致数字巴别塔的出现。

答案是不要拿走开发人员工作所需的工具。添加新的编程语言可以建立他们的技能基础,并为他们提供合适的设备来完成他们的工作。所以,你想对你的开发者说“是”,但是随着越来越多的编程语言被添加到企业中,它们会拖累你的软件开发生命周期(SDLC)。在规模上,所有这些语言和工具都可能扼杀企业。

企业应注意三个主要问题:

  1. 可见性: 团队聚在一起执行项目,然后解散。应用程序已经发布,但从未更新 —— 为什么要修复那些没有被破坏的东西?因此,当发现一个关键漏洞时,企业可能无法了解哪些应用程序受到影响,这些应用程序包含哪些库,甚至无法了解它们是用什么语言构建的。这可能导致成本高昂的“勘探项目”,以确保漏洞得到适当解决。
  2. 更新或编码: 一些企业将更新和修复功能集中在一个团队中。其他人要求每个“比萨团队”管理自己的开发工具。无论是哪种情况,工程团队和管理层都要付出机会成本:这些团队没有编码新特性,而是不断更新和修复开源工具中的库,因为它们移动得如此之快。
  3. 重新发明轮子: 由于代码依赖性和库版本不断更新,当发现漏洞时,与应用程序原始版本相关联的工件可能不再可用。因此,许多开发周期都被浪费在试图重新创建一个可以修复漏洞的环境上。

将你组织中的每种编程语言乘以这三个问题,开始时被认为是分子一样小的东西突然看起来像珠穆朗玛峰。就像登山者一样,没有合适的设备和工具,你将无法生存。

找到你的罗塞塔石碑

一个全面的解决方案可以满足 SDLC 中企业及其个人利益相关者的需求。企业可以使用以下最佳实践创建解决方案:

  1. 监控生产中运行的代码,并根据应用程序中使用的标记组件(例如,常见漏洞和暴露组件)的风险做出响应。
  2. 定期接收更新以保持代码的最新和无错误。
  3. 使用商业开源支持来获得编程语言版本和平台的帮助,这些版本和平台已经接近尾声,并且不受社区支持。
  4. 标准化整个企业中的特定编程语言构建,以实现跨团队的一致环境,并最大限度地减少依赖性。
  5. 根据相关性设置何时触发更新、警报或其他类型事件的阈值。
  6. 为您的包管理创建一个单一的可信来源;这可能需要知识渊博的技术提供商的帮助。
  7. 根据您的特定标准,只使用您需要的软件包获得较小的构建版本。

使用这些最佳实践,开发人员可以最大限度地利用他们的时间为企业创造更多价值,而不是执行基本的工具或构建工程任务。这将在软件开发生命周期(SDLC)的所有环境中创建代码一致性。由于维护编程语言和软件包分发所需的资源更少,这也将提高效率和节约成本。这种新的操作方式将使技术人员和管理人员的生活更加轻松。


via: https://opensource.com/article/18/11/multiple-programming-languages

作者:Bart Copeland 选题:lujun9972 译者:heguangzhi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

今年早些时候,我们对 Bjarne Stroustrup 进行了采访。他是 C++ 语言的创始人,摩根士丹利技术部门的董事总经理,美国哥伦比亚大学计算机科学的客座教授。他写了一封信,请那些关注编程语言进展的人去“想想瓦萨号!”

这句话对于丹麦人来说,毫无疑问,很容易理解。而那些对于 17 世纪的斯堪的纳维亚历史了解不多的人,还需要详细说明一下。瓦萨号是一艘瑞典军舰,由国王 Gustavus Adolphus 定做。它是当时波罗的海国家中最强大的军舰,但在 1628 年 8 月 10 日首航没几分钟之后就沉没了。

巨大的瓦萨号有一个难以解决的设计缺陷:头重脚轻,以至于它被一阵狂风刮翻了。通过援引这艘沉船的历史,Stroustrup 警示了 C++ 所面临的风险 —— 现在越来越多的特性被添加到了 C++ 中。

我们现在已经发现了好些能导致头重脚轻的特性。Stroustrup 在他的信中引用了 43 个提议。他认为那些参与 C++ 语言 ISO 标准演进的人(即所谓的 WG21 小组)正在努力推进语言发展,但成员们的努力方向却并不一致。

在他的信中,他写道:

分开来看,许多提议都很有道理。但将它们综合到一起,这些提议是很愚蠢的,将危害 C++ 的未来。

他明确表示,他用瓦萨号作为比喻并不是说他认为不断提升会带来毁灭。我们应该吸取瓦萨号的教训,构建一个坚实的基础,从错误中学习并对新版本做彻底的测试。

在瑞士 拉普斯威尔 Rapperswill 召开 C++ 标准化委员会会议之后,本月早些时候,Stroustrup 接受了 The Register 的采访,回答了有关 C++ 语言下一步发展方向的几个问题。(最新版是去年刚发布的 C++17;下一个版本是 C++20,预计于 2020 年发布。)

Register:在您的信件《想想瓦萨号!》中,您写道:

在 C++11 开始的基础建设尚未完成,而 C++17 基本没有在使基础更加稳固、规范和完整方面做出改善。相反,却增加了重要接口的复杂度(原文为 surface complexity,直译“表面复杂度”),让人们需要学习的特性数量越来越多。C++ 可能在这种不成熟的提议的重压之下崩溃。我们不应该花费大量的时间为专家级用户们(比如我们自己)去创建越来越复杂的东西。(还要考虑普通用户的学习曲线,越复杂的东西越不易普及。)

对新人来说,C++ 过难了吗?如果是这样,您认为怎样的特性让新人更易理解?

Stroustrup:C++ 的有些东西对于新人来说确实很具有挑战性。

另一方面而言,C++ 中有些东西对于新人来说,比起 C 或上世纪九十年代的 C++ 更容易理解了。而难点是让大型社区专注于这些部分,并且帮助新手和非专业的 C++ 用户去规避那些对高级库实现提供支持的部分。

我建议使用 C++ 核心准则作为实现上述目标的一个辅助。

此外,我的“C++ 教程”也可以帮助人们在使用现代 C++ 时走上正确的方向,而不会迷失在自上世纪九十年代以来的复杂性中,或困惑于只有专家级用户才能理解的东西中。这本即将出版的第二版的“C++ 教程”涵盖了 C++17 和部分 C++20 的内容。

我和其他人给没有编程经验的大一新生教过 C++,只要你不去深入编程语言的每个晦涩难懂的角落,把注意力集中到 C++ 中最主流的部分,就可以在三个月内学会 C++。

“让简单的东西保持简单”是我长期追求的目标。比如 C++11 的 range-for 循环:

for (int& x : v) ++x; // increment each element of the container v

v 的位置可以是任何容器。在 C 和 C 风格的 C++ 中,它可能看起来是这样:

for (int i=0; i<MAX; i++) ++v[i]; // increment each element of the array v

一些人抱怨说添加了 range-for 循环让 C++ 变得更复杂了,很显然,他们是正确的,因为它添加了一个新特性。但它却让 C++ 用起来更简单,而且同时它还消除了使用传统 for 循环时会出现的一些常见错误。

另一个例子是 C++11 的 标准线程库 standard thread library 。它比起使用 POSIX 或直接使用 Windows 的 C API 来说更简单,并且更不易出错。

Register:您如何看待 C++ 现在的状况?

Stroustrup:C++11 中作出了许多重大改进,并且我们在 C++14 上全面完成了改进工作。C++17 添加了相当多的新特性,但是没有提供对新技术的很多支持。C++20 目前看上去可能会成为一个重大改进版。编译器的状况非常好,标准库实现得也很优秀,非常接近最新的标准。C++17 现在已经可以使用,对于工具的支持正在逐步推进。已经有了许多第三方的库和好些新工具。然而,不幸的是,这些东西不太好找到。

我在《想想瓦萨号!》一文中所表达的担忧与标准化过程有关,对新东西的过度热情与完美主义的组合推迟了重大改进。“追求完美往往事与愿违”。在六月份拉普斯威尔的会议上有 160 人参与;在这样一个数量庞大且多样化的人群中很难取得一致意见。专家们也本来就有只为自己设计语言的倾向,这让他们不会时常在设计时考虑整个社区的需求。

Register:C++ 是否有一个理想的状态,或者与之相反,您只是为了程序员们的期望而努力,随时适应并且努力满足程序员们的需要?

Stroustrup:二者都有。我很乐意看到 C++ 支持彻底保证 类型安全 type-safe 资源安全 resource-safe 的编程方式。这不应该通过限制适用性或增加性能损耗来实现,而是应该通过改进的表达能力和更好的性能来实现。通过让程序员使用更好的(和更易用的)语言工具可以达到这个目标,我们可以做到的。

终极目标不会马上实现,也不会单靠语言设计来实现。为了实现这一目标,我们需要改进语言特性、提供更好的库和静态分析,并且设立提升编程效率的规则。C++ 核心准则是我为了提升 C++ 代码质量而实行的广泛而长期的计划的一部分。

Register:目前 C++ 是否面临着可以预见的风险?如果有,它是以什么形式出现的?(如,迭代过于缓慢,新兴低级语言,等等……据您的观点来看,似乎是提出的提议过多。)

Stroustrup:就是这样。今年我们已经收到了 400 篇文章。当然了,它们并不都是新提议。许多提议都与规范语言和标准库这一必需而乏味的工作相关,但是量大到难以管理。你可以在 WG21 网站上找到所有这些文章。

我写了《想想瓦萨号!》这封信作为一个呼吁,因为这种为了解决即刻需求(或者赶时髦)而不断增添语言特性,却对巩固语言基础(比如,改善 静态类型系统 static type system )不管不问的倾向让我感到震惊。增加的任何新东西,无论它多小都会产生成本,比如实现、学习、工具升级。重大的特性改变能够改变我们对编程的想法,而它们才是我们必须关注的东西。

委员会已经设立了一个“指导小组”,这个小组由在语言、标准库、实现、以及工程实践领域中拥有不错履历的人组成。我是其中的成员之一。我们负责为重点领域写一些关于发展方向、设计理念和建议重点发展领域的东西

对于 C++20,我们建议去关注:

  • 概念
  • 模块(适度地模块化并带来编译时的显著改进)
  • Ranges(包括一些无限序列的扩展)
  • 标准库中的网络概念

在拉普斯威尔会议之后,这些都有了实现的机会,虽然模块和网络化都不是会议的重点讨论对象。我是一个乐观主义者,并且委员会的成员们都非常努力。

我并不担心其它语言或新语言会取代它。我喜欢编程语言。如果一门新的语言提供了独一无二的、非常有用的东西,那它就是我们的榜样,我们可以向它学习。当然,每门语言本身都有一些问题。C++ 的许多问题都与它广泛的应用领域、大量的使用人群和过度的热情有关。大多数语言的社区都会有这样的问题。

Register:关于 C++ 您是否重新考虑过任何架构方面的决策?

Stroustrup:当我着手规划新版本时,我经常反思原来的决策和设计。关于这些,可以看我的《编程的历史》论文第 12 部分。

并没有让我觉得很后悔的重大决策。如果我必须重新做一次,我觉得和以前做的不会有太大的不同。

与以前一样,能够直接处理硬件加上零开销的抽象是设计的指导思想。使用 构造函数 constructor 析构函数 destructor 去处理资源是关键( 资源获取即初始化 Resource Acquisition Is Initialization ,RAII); 标准模板库 Standard Template Library (STL) 就是解释 C++ 库能够做什么的一个很好的例子。

Register:在 2011 年被采纳的每三年发布一个新版本的节奏是否仍然有效?我之所以这样问是因为 Java 已经决定更快地迭代。

Stroustrup:我认为 C++20 将会按时发布(就像 C++14 和 C++17 那样),并且主流的编译器也会立即采用它。我也希望 C++20 基于 C++17 能有重大的改进。

对于其它语言如何管理它们的版本,我并不十分关心。C++ 是由一个遵循 ISO 规则的委员会来管理的,而不是由某个大公司或某种“ 终生的仁慈独裁者 Beneficial Dictator Of Life (BDOL)”来管理。这一点不会改变。C++ 每三年发布一次的周期在 ISO 标准中是一个引人注目的创举。通常而言,周期应该是 5 或 10 年。

Register:在您的信中您写道:

我们需要一个能够被“普通程序员”使用的,条理还算清楚的编程语言。他们主要关心的是,能否按时高质量地交付他们的应用程序。

改进语言能够解决这个问题吗?或者,我们还需要更容易获得的工具和教育支持?

Stroustrup:我尽力宣传我关于 C++ 的实质和使用方式的理念,并且我鼓励其他人也和我采取相同的行动。

特别是,我鼓励讲师和作者们向 C++ 程序员们提出有用的建议,而不是去示范复杂的示例和技术来展示他们自己有多高明。我在 2017 年的 CppCon 大会上的演讲主题就是“学习和传授 C++”,并且也指出,我们需要更好的工具。

我在演讲中提到了构建技术支持和包管理器,这些历来都是 C++ 的弱项。标准化委员会现在有一个工具研究小组,或许不久的将来也会组建一个教育研究小组。

C++ 的社区以前是十分无组织性的,但是在过去的五年里,为了满足社区对新闻和技术支持的需要,出现了很多集会和博客。CppCon、isocpp.org、以及 Meeting++ 就是一些例子。

在一个庞大的委员会中做语言标准设计是很困难的。但是,对于所有的大型项目来说,委员会又是必不可少的。我很忧虑,但是关注它们并且面对问题是成功的必要条件。

Register:您如何看待 C++ 社区的流程?在沟通和决策方面你希望看到哪些变化?

Stroustrup:C++ 并没有企业管理一般的“社区流程”;它所遵循的是 ISO 标准流程。我们不能对 ISO 的条例做大的改变。理想的情况是,我们设立一个小型的、全职的“秘书处”来做最终决策和方向管理,但这种理想情况是不会出现的。相反,我们有成百上千的人在线讨论,大约有 160 人在技术问题上进行投票,大约有 70 组织和 11 个国家的人在最终提议上正式投票。这样很混乱,但是有些时候它的确能发挥作用。

Register:在最后,您认为那些即将推出的 C++ 特性中,对 C++ 用户最有帮助的是哪些?

Stroustrup:

  • 那些能让编程显著变简单的概念。
  • 并行算法 Parallel algorithms —— 如果要使用现代硬件的并发特性的话,这方法再简单不过了。
  • 协程 Coroutines ,如果委员会能够确定在 C++20 上推出。
  • 改进了组织源代码方式的,并且大幅改善了编译时间的模块。我希望能有这样的模块,但是还没办法确定我们能不能在 C++20 上推出。
  • 一个标准的网络库,但是还没办法确定我们能否在 C++20 上推出。

此外:

  • Contracts(运行时检查的先决条件、后置条件、和断言)可能对许多人都非常重要。
  • date 和 time-zone 支持库可能对许多人(行业)非常重要。

Register:您还有想对读者们说的话吗?

Stroustrup:如果 C++ 标准化委员会能够专注于重大问题,去解决重大问题,那么 C++20 将会非常优秀。但是在 C++20 推出之前,我们还有 C++17;无论如何,它仍然远超许多人对 C++ 的旧印象。®


via: https://www.theregister.co.uk/2018/06/18/bjarne_stroustrup_c_plus_plus/

作者:Thomas Claburn 选题:lujun9972 译者:qhwdw 校对:thecyanbirdNorthurlandpityonline

本文由 LCTT 原创编译,Linux中国 荣誉推出

编程语言对软件质量的影响是什么?这个问题在很长一段时间内成为一个引起了大量辩论的主题。在这项研究中,我们从 GitHub 上收集了大量的数据(728 个项目,6300 万行源代码,29000 位作者,150 万个提交,17 种编程语言),尝试在这个问题上提供一些实证。这个还算比较大的样本数量允许我们去使用一个混合的方法,结合多种可视化的回归模型和文本分析,去研究语言特性的影响,比如,在软件质量上,静态与动态类型和允许混淆与不允许混淆的类型。通过从不同的方法作三角测量研究(LCTT 译注:一种测量研究的方法),并且去控制引起混淆的因素,比如,团队大小、项目大小和项目历史,我们的报告显示,语言设计确实(对很多方面)有很大的影响,但是,在软件质量方面,语言的影响是非常有限的。最明显的似乎是,不允许混淆的类型比允许混淆的类型要稍微好一些,并且,在函数式语言中,静态类型也比动态类型好一些。值得注意的是,这些由语言设计所引起的轻微影响,绝大多数是由过程因素所主导的,比如,项目大小、团队大小和提交数量。但是,我们需要提示读者,即便是这些不起眼的轻微影响,也是由其它的无形的过程因素所造成的,例如,对某些函数类型、以及不允许类型混淆的静态语言的偏爱。

1 序言

在给定的编程语言是否是“适合这个工作的正确工具”的讨论期间,紧接着又发生了多种辩论。虽然一些辩论出现了带有宗教般狂热的色彩,但是大部分人都一致认为,编程语言的选择能够对编码过程和由此生成的结果都有影响。

主张强静态类型的人,倾向于认为静态方法能够在早期捕获到缺陷;他们认为,一点点的预防胜过大量的矫正。动态类型拥护者主张,保守的静态类型检查无论怎样都是非常浪费开发者资源的,并且,最好是依赖强动态类型检查来捕获错误类型。然而,这些辩论,大多数都是“纸上谈兵”,只靠“传说中”的证据去支持。

这些“传说”也许并不是没有道理的;考虑到影响软件工程结果的大量其它因素,获取这种经验性的证据支持是一项极具挑战性的任务,比如,代码质量、语言特征,以及应用领域。比如软件质量,考虑到它有大量的众所周知的影响因素,比如,代码数量, 6 团队大小, 2 和年龄/熟练程度。 9

受控实验是检验语言选择在面对如此令人气馁的混淆影响时的一种方法,然而,由于成本的原因,这种研究通常会引入一种它们自己的混淆,也就是说,限制了范围。在这种研究中,完整的任务是必须要受限制的,并且不能去模拟 真实的世界 中的开发。这里有几个最近的这种大学本科生使用的研究,或者,通过一个实验因素去比较静态或动态类型的语言。 7 12 15

幸运的是,现在我们可以基于大量的真实世界中的软件项目去研究这些问题。GitHub 包含了多种语言的大量的项目,并且在大小、年龄、和开发者数量上有很大的差别。每个项目的仓库都提供一个详细的记录,包含贡献历史、项目大小、作者身份以及缺陷修复。然后,我们使用多种工具去研究语言特性对缺陷发生的影响。对我们的研究方法的最佳描述应该是“混合方法”,或者是三角测量法; 5 我们使用文本分析、聚簇和可视化去证实和支持量化回归研究的结果。这个以经验为根据的方法,帮助我们去了解编程语言对软件质量的具体影响,因为,他们是被开发者非正式使用的。

2 方法

我们的方法是软件工程中典型的大范围观察研究法。我们首先大量的使用自动化方法,从几种数据源采集数据。然后使用预构建的统计分析模型对数据进行过滤和清洗。过滤器的选择是由一系列的因素共同驱动的,这些因素包括我们研究的问题的本质、数据质量和认为最适合这项统计分析研究的数据。尤其是,GitHub 包含了由大量的编程语言所写的非常多的项目。对于这项研究,我们花费大量的精力专注于收集那些用大多数的主流编程语言写的流行项目的数据。我们选择合适的方法来评估计数数据上的影响因素。

2.1 数据收集

我们选择了 GitHub 上的排名前 19 的编程语言。剔除了 CSS、Shell 脚本、和 Vim 脚本,因为它们不是通用的编程语言。我们包含了 Typescript,它是 JavaScript 的超集。然后,对每个被研究的编程语言,我们检索出以它为主要编程语言的前 50 个项目。我们总共分析了 17 种不同的语言,共计 850 个项目。

我们的编程语言和项目的数据是从 GitHub Archive 中提取的,这是一个记录所有活跃的公共 GitHub 项目的数据库。它记录了 18 种不同的 GitHub 事件,包括新提交、fork 事件、PR(拉取请求)、开发者信息和以每小时为基础的所有开源 GitHub 项目的问题跟踪。打包后的数据上传到 Google BigQuery 提供的交互式数据分析接口上。

识别编程语言排名榜单

我们基于它们的主要编程语言分类合计项目。然后,我们选择大多数的项目进行进一步分析,如 表 1 所示。一个项目可能使用多种编程语言;将它确定成单一的编程语言是很困难的。Github Archive 保存的信息是从 GitHub Linguist 上采集的,它使用项目仓库中源文件的扩展名来确定项目的发布语言是什么。源文件中使用数量最多的编程语言被确定为这个项目的 主要编程语言

t1.jpg

表 1 每个编程语言排名前三的项目

检索流行的项目

对于每个选定的编程语言,我们先根据项目所使用的主要编程语言来选出项目,然后根据每个项目的相关 的数量排出项目的流行度。 的数量表示了有多少人主动表达对这个项目感兴趣,并且它是流行度的一个合适的代表指标。因此,在 C 语言中排名前三的项目是 linux、git、php-src;而对于 C++,它们则是 node-webkit、phantomjs、mongo ;对于 Java,它们则是 storm、elasticsearch、ActionBarSherlock 。每个编程语言,我们各选了 50 个项目。

为确保每个项目有足够长的开发历史,我们剔除了少于 28 个提交的项目(28 是候选项目的第一个四分位值数)。这样我们还剩下 728 个项目。表 1 展示了每个编程语言的前三个项目。

检索项目演进历史

对于 728 个项目中的每一个项目,我们下载了它们的非合并提交、提交记录、作者数据、作者使用 git 的名字。我们从每个文件的添加和删除的行数中计算代码改动和每个提交的修改文件数量。我们以每个提交中修改的文件的扩展名所代表的编程语言,来检索出所使用的编程语言(一个提交可能有多个编程语言标签)。对于每个提交,我们通过它的提交日期减去这个项目的第一个提交的日期,来计算它的 提交年龄 。我们也计算其它的项目相关的统计数据,包括项目的最大提交年龄和开发者总数,用于我们的回归分析模型的控制变量,它在第三节中会讨论到。我们通过在提交记录中搜索与错误相关的关键字,比如,errorbugfixissuemistakeincorrectfaultdefectflaw,来识别 bug 修复提交。这一点与以前的研究类似。 18

表 2 汇总了我们的数据集。因为一个项目可能使用多个编程语言,表的第二列展示了使用某种编程语言的项目的总数量。我们进一步排除了项目中该编程语言少于 20 个提交的那些编程语言。因为 20 是每个编程语言的每个项目的提交总数的第一个四分位值。例如,我们在 C 语言中共找到 220 项目的提交数量多于 20 个。这确保了每个“编程语言 – 项目”对有足够的活跃度。

t2.jpg

表 2 研究主题

总而言之,我们研究了最近 18 年以来,用了 17 种编程语言开发的,总共 728 个项目。总共包括了 29,000 个不同的开发者,157 万个提交,和 564,625 个 bug 修复提交。

2.2 语言分类

我们基于影响语言质量的几种编程语言特性定义了语言类别, 7 8 12 ,如 表 3 所示。

编程范式 Programming Paradigm 表示项目是以命令方式、脚本方式、还是函数语言所写的。在本文的下面部分,我们分别使用 命令 procedural 脚本 scripting 这两个术语去代表命令方式和脚本方式。

t3.jpg

表 3. 语言分类的不同类型

类型检查 Type Checking 代表静态或者动态类型。在静态类型语言中,在编译时进行类型检查,并且变量名是绑定到一个值和一个类型的。另外,(包含变量的)表达式是根据运行时,它们可能产生的值所符合的类型来分类的。在动态类型语言中,类型检查发生在运行时。因此,在动态类型语言中,它可能出现在同一个程序中,一个变量名可能会绑定到不同类型的对象上的情形。

隐式类型转换 Implicit Type Conversion 允许一个类型为 T1 的操作数,作为另一个不同的类型 T2 来访问,而无需进行显式的类型转换。这样的隐式类型转换在一些情况下可能会带来类型混淆,尤其是当它表示一个明确的 T1 类型的操作数时,把它再作为另一个不同的 T2 类型的情况下。因为,并不是所有的隐式类型转换都会立即出现问题,通过我们识别出的允许进行隐式类型转换的所有编程语言中,可能发生隐式类型转换混淆的例子来展示我们的定义。例如,在像 Perl、 JavaScript、CoffeeScript 这样的编程语言中,一个字符和一个数字相加是允许的(比如,"5" + 2 结果是 "52")。但是在 Php 中,相同的操作,结果是 7。像这种操作在一些编程语言中是不允许的,比如 Java 和 Python,因为,它们不允许隐式转换。在强数据类型的 C 和 C++ 中,这种操作的结果是不可预料的,例如,int x; float y; y=3.5; x=y;是合法的 C 代码,并且对于 xy 其结果是不同的值,具体是哪一个值,取决于含义,这可能在后面会产生问题。 a Objective-C 中,数据类型 id 是一个通用对象指针,它可以被用于任何数据类型的对象,而不管分类是什么。 b 像这种通用数据类型提供了很好的灵活性,它可能导致隐式的类型转换,并且也会出现不可预料的结果。 c 因此,我们根据它的编译器是否 允许 或者 不允许 如上所述的隐式类型转换,对编程语言进行分类;而不允许隐式类型转换的编程语言,会显式检测类型混淆,并报告类型不匹配的错误。

不允许隐式类型转换的编程语言,使用一个类型判断算法,比如,Hindley 10 和 Milner, 17 或者,在运行时上使用一个动态类型检查器,可以在一个编译器(比如,使用 Java)中判断静态类型的结果。相比之下,一个类型混淆可能会悄无声息地发生,因为,它可能因为没有检测到,也可能是没有报告出来。无论是哪种方式,允许隐式类型转换在提供了灵活性的同时,最终也可能会出现很难确定原因的错误。为了简单起见,我们将用 隐含 implicit 代表允许隐式类型转换的编程语言,而不允许隐式类型转换的语言,我们用 明确 explicit 代表。

内存分类 Memory Class 表示是否要求开发者去管理内存。尽管 Objective-C 遵循了一个混合模式,我们仍将它放在非管理的分类中来对待,因为,我们在它的代码库中观察到很多的内存错误,在第 3 节的 RQ4 中会讨论到。

请注意,我们之所以使用这种方式对编程语言来分类和研究,是因为,这种方式在一个“真实的世界”中被大量的开发人员非正式使用。例如,TypeScript 被有意地分到静态编程语言的分类中,它不允许隐式类型转换。然而,在实践中,我们注意到,开发者经常(有 50% 的变量,并且跨 TypeScript —— 在我们的数据集中使用的项目)使用 any 类型,这是一个笼统的联合类型,并且,因此在实践中,TypeScript 允许动态地、隐式类型转换。为减少混淆,我们从我们的编程语言分类和相关的模型中排除了 TypeScript(查看 表 37)。

2.3 识别项目领域

我们基于编程语言的特性和功能,使用一个自动加手动的混合技术,将研究的项目分类到不同的领域。在 GitHub 上,项目使用 project descriptionsREADME 文件来描述它们的特性。我们使用一种文档主题生成模型(Latent Dirichlet Allocation,缩写为:LDA) 3 去分析这些文本。提供一组文档给它,LDA 将生成不同的关键字,然后来识别可能的主题。对于每个文档,LDA 也估算每个主题分配的文档的概率。

我们检测到 30 个不同的领域(换句话说,就是主题),并且评估了每个项目从属于每个领域的概率。因为,这些自动检测的领域包含了几个具体项目的关键字,例如,facebook,很难去界定它的底层的常用功能。为了给每个领域分配一个有意义的名字,我们手动检查了 30 个与项目名字无关的用于识别领域的领域识别关键字。我们手动重命名了所有的 30 个自动检测的领域,并且找出了以下六个领域的大多数的项目:应用程序、数据库、代码分析、中间件、库,和框架。我们也找出了不符合以上任何一个领域的一些项目,因此,我们把这个领域笼统地标记为 其它 。随后,我们研究组的另一名成员检查和确认了这种项目领域分类的方式。表 4 汇总了这个过程识别到的领域结果。

t4.jpg

表 4 领域特征

2.4 bug 分类

尽管修复软件 bug 时,开发者经常会在提交日志中留下关于这个 bug 的原始的重要信息;例如,为什么会产生 bug,以及怎么去修复 bug。我们利用很多信息去分类 bug,与 Tan 的 et al 类似。 13 24

首先,我们基于 bug 的 原因 Cause 影响 Impact 进行分类。\_ 原因 \_ 进一步分解为不相关的错误子类:算法方面的、并发方面的、内存方面的、普通编程错误,和未知的。bug 的 影响 也分成四个不相关的子类:安全、性能、失败、和其它的未知类。因此,每个 bug 修复提交也包含原因和影响的类型。表 5 展示了描述的每个 bug 分类。这个类别分别在两个阶段中被执行:

t5.jpg

表 5 bug 分类和在整个数据集中的描述

(1) 关键字搜索 我们随机选择了 10% 的 bug 修复信息,并且使用一个基于关键字的搜索技术去对它们进行自动化分类,作为可能的 bug 类型。我们对这两种类型(原因和影响)分别使用这个注释。我们选择了一个限定的关键字和习惯用语集,如 表 5 所展示的。像这种限定的关键字和习惯用语集可以帮我们降低误报。

(2) 监督分类 我们使用前面步骤中的有注释的 bug 修复日志作为训练数据,为监督学习分类技术,通过测试数据来矫正,去对剩余的 bug 修复信息进行分类。我们首先转换每个 bug 修复信息为一个词袋(LCTT 译注:bag-of-words,一种信息检索模型)。然后,删除在所有的 bug 修复信息中仅出现过一次的词。这样减少了具体项目的关键字。我们也使用标准的自然语言处理技术来解决这个问题。最终,我们使用支持向量机(LCTT 译注:Support Vector Machine,缩写为 SVM,在机器学习领域中,一种有监督的学习算法)去对测试数据进行分类。

为精确评估 bug 分类器,我们手动注释了 180 个随机选择的 bug 修复,平均分布在所有的分类中。然后,我们比较手动注释的数据集在自动分类器中的结果。最终处理后的,表现出的精确度是可接受的,性能方面的精确度最低,是 70%,并发错误方面的精确度最高,是 100%,平均是 84%。再次运行,精确度从低到高是 69% 到 91%,平均精确度还是 84%。

我们的 bug 分类的结果展示在 表 5 中。大多数缺陷的原因都与普通编程错误相关。这个结果并不意外,因为,在这个分类中涉及了大量的编程错误,比如,类型错误、输入错误、编写错误、等等。我们的技术并不能将在任何(原因或影响)分类中占比为 1.4% 的 bug 修复信息再次进行分类;我们将它归类为未知。

2.5 统计方法

我们使用回归模型对软件项目相关的其它因素中的有缺陷的提交数量进行了建模。所有的模型使用 负二项回归 negative binomial regression (缩写为 NBR)(LCTT 译注:一种回归分析模型) 去对项目属性计数进行建模,比如,提交数量。NBR 是一个广义的线性模型,用于对非负整数进行响应建模。 4

在我们的模型中,我们对每个项目的编程语言,控制几个可能影响最终结果的因素。因此,在我们的回归分析中,每个(语言/项目)对是一个行,并且可以视为来自流行的开源项目中的样本。我们依据变量计数进行对象转换,以使变量保持稳定,并且提升了模型的适用度。 4 我们通过使用 AIC 和 Vuong 对非嵌套模型的测试比较来验证它们。

去检查那些过度的多重共线性(LCTT 译注:多重共线性是指,在线性回归模型中解释变量之间由于存在精确相关关系或高度相关关系而使模型估计失真或难以估计准确。)并不是一个问题,我们在所有的模型中使用一个保守的最大值 5,去计算每个依赖的变量的膨胀因子的方差。 4 我们通过对每个模型的残差和杠杆图进行视觉检查来移除高杠杆点,找出库克距离(LCTT 译注:一个统计学术语,用于诊断回归分析中是否存在异常数据)的分离值和最大值。

我们利用 效果 ,或者 差异 ,编码到我们的研究中,以提高编程语言回归系数的表现。 4 加权的效果代码允许我们将每种编程语言与所有编程语言的效果进行比较,同时弥补了跨项目使用编程语言的不均匀性。 23 去测试两种变量因素之间的联系,我们使用一个独立的卡方检验(LCTT 译注:Chi-square,一种统计学上的假设检验方法)测试。 14 在证实一个依赖之后,我们使用 Cramer 的 V,它是与一个 r × c 等价的正常数据的 phi(φ) 系数,去建立一个效果数据。

3 结果

我们从简单明了的问题开始,它非常直接地解决了人们坚信的一些核心问题,即:

问题 1:一些编程语言相比其它语言来说更易于出现缺陷吗?

我们使用了回归分析模型,去比较每个编程语言对所有编程语言缺陷数量平均值的影响,以及对缺陷修复提交的影响(查看 表 6)。

t6.jpg

表 6. 一些语言的缺陷要少于其它语言

我们包括了一些变量,作为对明确影响反应的控制因子。项目 年龄 age 也包括在内,因为,越老的项目生成的缺陷修复数量越大。 提交 commits 数量也会对项目反应有轻微的影响。另外,从事该项目的 开发人员 dev 的数量和项目的原始 大小 size ,都会随着项目的活跃而增长。

上述模型中估算系数的大小和符号(LCTT 译注:指 “+”或者“-”)与结果的预测因子有关。初始的四种变量是控制变量,并且,我们对这些变量对最终结果的影响不感兴趣,只是说它们都是积极的和有意义的。语言变量是指示变量,是每个项目的变化因子,该因子将每种编程语言与所有项目的编程语言的加权平均值进行比较。编程语言系数可以大体上分为三类。第一类是,那些在统计学上无关紧要的系数,并且在建模过程中这些系数不能从 0 中区分出来。这些编程语言的表现与平均值相似,或者它们也可能有更大的方差。剩余的系数是非常明显的,要么是正的,要么是负的。对于那些正的系数,我们猜测可能与这个编程语言有大量的缺陷修复相关。这些语言包括 C、C++、Objective-C、Php,以及 Python。所有的有一个负的系数的编程语言,比如 Clojure、Haskell、Ruby,和 Scala,暗示这些语言的缺陷修复提交可能小于平均值。

应该注意的是,虽然,从统计学的角度观察到编程语言与缺陷之间有明显的联系,但是,大家不要过高估计编程语言对于缺陷的影响,因为,这种影响效应是非常小的。异常分析的结果显示,这种影响小于总异常的 1%。

ut1.jpg

我们可以这样去理解模型的系数,它代表一个预测因子在所有其它预测因子保持不变的情况下,这个预测因子一个 单位 unit 的变化,所反应出的预期的响应的对数变化;换句话说,对于一个系数 β i ,在 β i 中一个单位的变化,产生一个预期的 e β i 响应的变化。对于可变因子,这个预期的变化是与所有编程语言的平均值进行比较。因此,如果对于一定数量的提交,用一个处于平均值的编程语言开发的特定项目有四个缺陷提交,那么,如果选择使用 C++ 来开发,意味着我们预计应该有一个额外的(LCTT 译注:相对于平均值 4,多 1 个)缺陷提交,因为 e 0.18 × 4 = 4.79。对于相同的项目,如果选择使用 Haskell 来开发,意味着我们预计应该少一个(LCTT 译注:同上,相对于平均值 4)缺陷提交。因为, e −0.26 × 4 = 3.08。预测的精确度取决于剩余的其它因子都保持不变,除了那些微不足道的项目之外,所有的这些都是一个极具挑战性的命题。所有观察性研究都面临类似的局限性;我们将在第 5 节中详细解决这些事情。

结论 1:一些编程语言相比其它编程语言有更高的缺陷相关度,不过,影响非常小。

在这篇文章的剩余部分,我们会在基本结论的基础上详细阐述,通过考虑不同种类的应用程序、缺陷、和编程语言,可以进一步深入了解编程语言和缺陷倾向之间的关系。

软件 bug 通常落进两种宽泛的分类中:

  1. 特定领域的 bug :特定于项目功能,并且不依赖于底层编程语言。
  2. 普通 bug :大多数的普通 bug 是天生的,并且与项目功能无关,比如,输入错误,并发错误、等等。

因此,在一个项目中,应用程序领域和编程语言相互作用可能会影响缺陷的数量,这一结论被认为是合理的。因为一些编程语言被认为在一些任务上相比其它语言表现更突出,例如,C 对于低级别的(底层)工作,或者,Java 对于用户应用程序,对于编程语言的一个不合适的选择,可能会带来更多的缺陷。为研究这种情况,我们将理想化地忽略领域特定的 bug,因为,普通 bug 更依赖于编程语言的特性。但是,因为一个领域特定的 bug 也可能出现在一个普通的编程错误中,这两者是很难区分的。一个可能的变通办法是在控制领域的同时去研究编程语言。从统计的角度来看,虽然,使用 17 种编程语言跨 7 个领域,在给定的样本数量中,理解大量的术语将是一个极大的挑战。

鉴于这种情况,我们首先考虑在一个项目中测试领域和编程语言使用之间的依赖关系,独立使用一个 卡方检验 Chi-square 测试。在 119 个单元中,是 46 个,也就是说是 39%,它在我们设定的保守值 5 以上,它太高了。这个数字不能超过 20%,应该低于 5。 14 我们在这里包含了完整有值; d 但是,通过 Cramer 的 V 测试的值是 0.191,是低相关度的,表明任何编程语言和领域之间的相关度是非常小的,并且,在回归模型中包含领域并不会产生有意义的结果。

去解决这种情况的一个选择是,去移除编程语言,或者混合领域,但是,我们现有的数据没有进行完全挑选。或者,我们混合编程语言;这个选择导致一个相关但略有不同的问题。

问题 2: 哪些编程语言特性与缺陷相关?

我们按编程语言类别聚合它们,而不是考虑单独的编程语言,正如在第 2.2 节所描述的那样,然后去分析与缺陷的关系。总体上说,这些属性中的每一个都将编程语言按照在上下文中经常讨论的错误、用户辩论驱动、或者按以前工作主题来划分的。因此,单独的属性是高度相关的,我们创建了六个模型因子,将所有的单独因子综合到我们的研究中。然后,我们对六个不同的因子对缺陷数量的影响进行建模,同时控制我们在 问题 1 节中使用的模型中的相同的基本协变量(LCTT 译注:协变量是指在实验中不能被人为操纵的独立变量)。

关于使用的编程语言(在前面的 表 6中),我们使用跨所有语言类的平均反应来比较编程语言 。这个模型在 表 7 中表达了出来。很明显,Script-Dynamic-Explicit-Managed 类有最小的量级系数。这个系数是微不足道的,换句话说,对这个系数的 Z 校验 z-test (LCTT 译注:统计学上的一种平均值差异校验的方法) 并不能把它从 0 中区分出来。鉴于标准错误的量级,我们可以假设,在这个类别中的编程语言的行为是非常接近于所有编程语言行为的平均值。我们可以通过使用 Proc-Static-Implicit-Unmanaged 作为基本级并用于处理,或者使用基本级来虚假编码比较每个语言类,来证明这一点。在这种情况下,Script-Dynamic-Explicit-Managed 是明显不同于 p = 0.00044 的。注意,虽然我们在这是选择了不同的编码方法,影响了系数和 Z 值,这个方法和所有其它的方面都是一样的。当我们改变了编码,我们调整系数去反应我们希望生成的对比。 4 将其它类的编程语言与总体平均数进行比较,Proc-Static-Implicit-Unmanaged 类编程语言更容易引起缺陷。这意味着与其它过程类编程语言相比,隐式类型转换或者管理内存会导致更多的缺陷倾向。

t7.jpg

表 7. 函数式语言与缺陷的关联度和其它类语言相比要低,而过程类语言则大于或接近于平均值。

在脚本类编程语言中,我们观察到类似于允许与不允许隐式类型转换的编程语言之间的关系,它们提供的一些证据表明,隐式类型转换(与显式类型转换相比)才是导致这种差异的原因,而不是内存管理。鉴于各种因素之间的相关性,我们并不能得出这个结论。但是,当它们与平均值进行比较时,作为一个组,那些不允许隐式类型转换的编程语言出现错误的倾向更低一些,而那些出现错误倾向更高的编程语言,出现错误的机率则相对更高。在函数式编程语言中静态和动态类型之间的差异也很明显。

函数式语言作为一个组展示了与平均值的很明显的差异。静态类型语言的系数要小很多,但是函数式语言类都有同样的标准错误。函数式静态编程语言出现错误的倾向要小于函数式动态编程语言,这是一个强有力的证据,尽管如此,Z 校验仅仅是检验系数是否能从 0 中区分出来。为了强化这个推论,我们使用处理编码,重新编码了上面的模型,并且观察到,Functional-Static-Explicit-Managed 编程语言类的错误倾向是明显小于 Functional-Dynamic-Explicit-Managed 编程语言类的 p = 0.034。

ut2.jpg

与编程语言和缺陷一样,编程语言类与缺陷之间关系的影响是非常小的。解释类编程语言的这种差异也是相似的,虽然很小,解释类编程语言的这种差异小于 1%。

我们现在重新回到应用领域这个问题。应用领域是否与语言类相互影响?怎么选择?例如,一个函数化编程语言,对特定的领域有一定的优势?与上面一样,对于这些因素和项目领域之间的关系做一个卡方检验,它的值是 99.05, df = 30, p = 2.622e–09,我们拒绝无意义假设,Cramer 的 V 产生的值是 0.133,表示一个弱关联。因此,虽然领域和编程语言之间有一些关联,但在这里应用领域和编程语言类之间仅仅是一个非常弱的关联。

结论 2:在编程语言类与缺陷之间有一个很小但是很明显的关系。函数式语言与过程式或者脚本式语言相比,缺陷要少。

这个结论有些不太令人满意的地方,因为,我们并没有一个强有力的证据去证明,在一个项目中编程语言或者语言类和应用领域之间的关联性。一个替代方法是,基于全部的编程语言和应用领域,忽略项目和缺陷总数,而去查看相同的数据。因为,这样不再产生不相关的样本,我们没有从统计学的角度去尝试分析它,而是使用一个描述式的、基于可视化的方法。

我们定义了 缺陷倾向 Defect Proneness 作为 bug 修复提交与每语言每领域总提交的比率。图 1 使用了一个热力图展示了应用领域与编程语言之间的相互作用,从亮到暗表示缺陷倾向在增加。我们研究了哪些编程语言因素影响了跨多种语言写的项目的缺陷修复提交。它引出了下面的研究问题:

f1.jpg

图 1. 编程语言的缺陷倾向与应用领域之间的相互作用。对于一个给定的领域(列底部),热力图中的每个格子表示了一个编程语言的缺陷倾向(行头部)。“整体”列表示一个编程语言基于所有领域的缺陷倾向。用白色十字线标记的格子代表一个 null 值,换句话说,就是在那个格子里没有符合的提交。

问题 3: 编程语言的错误倾向是否取决于应用领域?

为了回答这个问题,我们首先在我们的回归模型中,以高杠杆点过滤掉认为是异常的项目,这种方法在这里是必要的,尽管这是一个非统计学的方法,一些关系可能影响可视化。例如,我们找到一个简单的项目,Google 的 v8,一个 JavaScript 项目,负责中间件中的所有错误。这对我们来说是一个惊喜,因为 JavaScript 通常不用于中间件。这个模式一直在其它应用领域中不停地重复着,因此,我们过滤出的项目的缺陷度都低于 10% 和高于 90%。这个结果在 图 1 中。

我们看到在这个热力图中仅有一个很小的差异,正如在问题 1 节中看到的那样,这个结果仅表示编程语言固有的错误倾向。为验证这个推论,我们测量了编程语言对每个应用领域和对全部应用领域的缺陷倾向。对于除了数据库以外的全部领域,关联性都是正向的,并且 p 值是有意义的(<0.01)。因此,关于缺陷倾向,在每个领域的语言排序与全部领域的语言排序是基本相同的。

ut3.jpg

结论 3: 应用领域和编程语言缺陷倾向之间总体上没有联系。

我们证明了不同的语言产生了大量的缺陷,并且,这个关系不仅与特定的语言相关,也适用于一般的语言类;然而,我们发现,项目类型并不能在一定程度上协调这种关系。现在,我们转变我们的注意力到反应分类上,我想去了解,编程语言与特定种类的缺陷之间有什么联系,以及这种关系怎么与我们观察到的更普通的关系去比较。我们将缺陷分为不同的类别,如 表 5 所描述的那样,然后提出以下的问题:

问题 4:编程语言与 bug 分类之间有什么关系?

我们使用了一个类似于问题 3 中所用的方法,去了解编程语言与 bug 分类之间的关系。首先,我们研究了 bug 分类和编程语言类之间的关系。一个热力图(图 2)展示了在编程语言类和 bug 类型之上的总缺陷数。去理解 bug 分类和语言之间的相互作用,我们对每个类别使用一个 NBR 回归模型。对于每个模型,我们使用了与问题 1 中相同的控制因素,以及使用加权效应编码后的语言,去预测缺陷修复提交。

f2.jpg

图 2. bug 类别与编程语言类之间的关系。每个格子表示每语言类(行头部)每 bug 类别(列底部)的 bug 修复提交占全部 bug 修复提交的百分比。这个值是按列规范化的。

结果和编程语言的方差分析值展示在 表 8 中。每个模型的整体异常是非常小的,并且对于特定的缺陷类型,通过语言所展示的比例在大多数类别中的量级是类似的。我们解释这种关系为,编程语言对于特定的 bug 类别的影响要大于总体的影响。尽管我们结论概括了全部的类别,但是,在接下来的一节中,我们对 表 5 中反应出来的 bug 数较多的 bug 类别做进一步研究。

t8.jpg

表 8. 虽然编程语言对缺陷的影响因缺陷类别而不同,但是,编程语言对特定的类别的影响要大于一般的类别。

编程错误 普通的编程错误占所有 bug 修复提交的 88.53% 左右,并且在所有的编程语言类中都有。因此,回归分析给出了一个与问题 1 中类似的结论(查看 表 6)。所有的编程语言都会导致这种编程错误,比如,处理错误、定义错误、输入错误、等等。

内存错误 内存错误占所有 bug 修复提交的 5.44%。热力图 图 2 证明了在 Proc-Static-Implicit-Unmanaged 类和内存错误之间存在着非常紧密的联系。非管理内存的编程语言出现内存 bug,这是预料之中的。表 8 也证明了这一点,例如,C、C++、和 Objective-C 引发了很多的内存错误。在管理内存的语言中 Java 引发了更多的内存错误,尽管它少于非管理内存的编程语言。虽然 Java 自己做内存回收,但是,它出现内存泄露一点也不奇怪,因为对象引用经常阻止内存回收。 11 在我们的数据中,Java 的所有内存错误中,28.89% 是内存泄漏造成的。就数量而言,编程语言中内存缺陷相比其它类别的 原因 造成的影响要大很多。

并发错误 在总的 bug 修复提交中,并发错误相关的修复提交占 1.99%。热力图显示,Proc-Static-Implicit-Unmanaged 是主要的错误类型。在这种错误中,C 和 C++ 分别占 19.15% 和 7.89%,并且它们分布在各个项目中。

ut4.jpg

属于 Static-Strong-Managed 语言类的编程语言都被证实处于热力图中的暗区中,普通的静态语言相比其它语言产生更多的并发错误。在动态语言中,仅仅有 Erlang 有更多的并发错误倾向,或许与使用这种语言开发的并发应用程序非常多有关系。同样地,在 表 8 中的负的系数表明,用诸如 Ruby 和 `Php 这样的动态编程语言写的项目,并发错误要少一些。请注意,某些语言,如 JavaScript、CoffeeScript 和 TypeScript 是不支持并发的,在传统的惯例中,虽然 Php 具有有限的并发支持(取决于它的实现)。这些语言在我们的数据中引入了虚假的 0,因此,在 表 8 中这些语言的并发模型的系数,不能像其它的语言那样去解释。因为存在这些虚假的 0,所以在这个模型中所有语言的平均数非常小,它可能影响系数的大小,因此,她们给 w.r.t. 一个平均数,但是,这并不影响他们之间的相对关系,因为我们只关注它们的相对关系。

基于 bug 修复消息中高频词的文本分析表明,大多数的并发错误发生在一个条件争用、死锁、或者不正确的同步上,正如上面表中所示。遍历所有语言,条件争用是并发错误出现最多的原因,例如,在 Go 中占 92%。在 Go 中条件争用错误的改进,或许是因为使用了一个争用检测工具帮助开发者去定位争用。同步错误主要与消息传递接口(MPI)或者共享内存操作(SHM)相关。Erlang 和 Go 对线程间通讯使用 MPI e ,这就是为什么这两种语言没有发生任何 SHM 相关的错误的原因,比如共享锁、互斥锁等等。相比之下,为线程间通讯使用早期的 SHM 技术的语言写的项目,就可能存在锁相关的错误。

安全和其它冲突错误 在所有的 bug 修复提交中,与 冲突 Impact 错误相关的提交占了 7.33% 左右。其中,Erlang、C++、Python 与安全相关的错误要高于平均值(表 8)。Clojure 项目相关的安全错误较少(图 2)。从热力图上我们也可以看出来,静态语言一般更易于发生失败和性能错误,紧随其后的是 Functional-Dynamic-Explicit-Managed 语言,比如 Erlang。对异常结果的分析表明,编程语言与冲突失败密切相关。虽然安全错误在这个类别中是弱相关的,与残差相比,解释类语言的差异仍然比较大。

结论 4: 缺陷类型与编程语言强相关;一些缺陷类型比如内存错误和并发错误也取决于早期的语言(所使用的技术)。对于特定类别,编程语言所引起的错误比整体更多。

4. 相关工作

在编程语言比较之前做的工作分为以下三类:

(1) 受控实验

对于一个给定的任务,开发者使用不同的语言进行编程时受到监视。研究然后比较结果,比如,开发成果和代码质量。Hanenberg 7 通过开发一个解析程序,对 48 位程序员监视了 27 小时,去比较了静态与动态类型。他发现这两者在代码质量方面没有显著的差异,但是,基于动态类型的语言花费了更短的开发时间。他们的研究是在一个实验室中,使用本科学生,设置了定制的语言和 IDE 来进行的。我们的研究正好相反,是一个实际的流行软件应用的研究。虽然我们只能够通过使用回归模型间接(和 事后 )控制混杂因素,我们的优势是样本数量大,并且更真实、使用更广泛的软件。我们发现在相同的条件下,静态化类型的语言比动态化类型的语言更少出现错误倾向,并且不允许隐式类型转换的语言要好于允许隐式类型转换的语言,其对结果的影响是非常小的。这是合理的,因为样本量非常大,所以这种非常小的影响在这个研究中可以看得到。

Harrison et al. 8 比较了 C++ 与 SML,一个是过程化编程语言,一个是函数化编程语言,在总的错误数量上没有找到显著的差异,不过 SML 相比 C++ 有更高的缺陷密集度。SML 在我们的数据中并没有体现出来,不过,认为函数式编程语言相比过程化编程语言更少出现缺陷。另一个重点工作是比较跨不同语言的开发工作。 12 20 不过,他们并不分析编程语言的缺陷倾向。

(2) 调查

Meyerovich 和 Rabkin 16 调查了开发者对编程语言的观点,去研究为什么一些编程语言比其它的语言更流行。他们的报告指出,非编程语言的因素影响非常大:先前的编程语言技能、可用的开源工具、以及现有的老式系统。我们的研究也证明,可利用的外部工具也影响软件质量;例如,在 Go 中的并发 bug(请查看问题 4 节内容)。

(3) 对软件仓库的挖掘

Bhattacharya 和 Neamtiu 1 研究了用 C 和 C++ 开发的四个项目,并且发现在 C++ 中开发的组件一般比在 C 中开发的组件更可靠。我们发现 C 和 C++ 的错误倾向要高于全部编程语言的平均值。但是,对于某些 bug 类型,像并发错误,C 的缺陷倾向要高于 C++(请查看第 3 节中的问题 4)。

5. 有效的风险

我们认为,我们的报告的结论几乎没有风险。首先,在识别 bug 修复提交方面,我们依赖的关键字是开发者经常用于表示 bug 修复的关键字。我们的选择是经过认真考虑的。在一个持续的开发过程中,我们去捕获那些开发者一直面对的问题,而不是他们报告的 bug。不过,这种选择存在过高估计的风险。我们对领域分类是为了去解释缺陷的倾向,而且,我们研究组中另外的成员验证过分类。此外,我们花费精力去对 bug 修复提交进行分类,也可能有被最初选择的关键字所污染的风险。每个项目在提交日志的描述上也不相同。为了缓解这些风险,我们像 2.4 节中描述的那样,利用手工注释评估了我们的类别。

我们判断文件所属的编程语言是基于文件的扩展名。如果使用不同的编程语言写的文件使用了我们研究的通用的编程语言文件的扩展名,这种情况下也容易出现错误倾向。为减少这种错误,我们使用一个随机样本文件集手工验证了我们的语言分类。

根据我们的数据集所显示的情形,2.2 节中的解释类编程语言,我们依据编程语言属性的主要用途作了一些假设。例如,我们将 Objective-C 分到非管理内存类型中,而不是混合类型。同样,我们将 Scala 注释为函数式编程语言,将 C# 作为过程化的编程语言,虽然,它们在设计的选择上两者都支持。 19 21 在这项研究工作中,我们没有从过程化编程语言中分离出面向对象的编程语言(OOP),因为,它们没有清晰的区别,主要差异在于编程类型。我们将 C++ 分到允许隐式类型转换的类别中是因为,某些类型的内存区域可以通过使用指针操作被进行不同的处理, 22 并且我们注意到大多数 C++ 编译器可以在编译时检测类型错误。

最后,我们将缺陷修复提交关联到编程语言属性上,它们可以反应出报告的风格或者其它开发者的属性。可用的外部工具或者 library 也可以影响一个相关的编程语言的 bug 数量。

6. 总结

我们对编程语言和使用进行了大规模的研究,因为它涉及到软件质量。我们使用的 Github 上的数据,具有很高的复杂性和多个维度上的差异的特性。我们的样本数量允许我们对编程语言效果以及在控制一些混杂因素的情况下,对编程语言、应用领域和缺陷类型之间的相互作用,进行一个混合方法的研究。研究数据显示,函数式语言是好于过程化语言的;不允许隐式类型转换的语言是好于允许隐式类型转换的语言的;静态类型的语言是好于动态类型的语言的;管理内存的语言是好于非管理的语言的。进一步讲,编程语言的缺陷倾向与软件应用领域并没有关联。另外,每个编程语言更多是与特定的 bug 类别有关联,而不是与全部 bug。

另一方面,即便是很大规模的数据集,它们被多种方法同时进行分割后,也变得很小且不全面。因此,随着依赖的变量越来越多,很难去回答某个变量所产生的影响有多大这种问题,尤其是在变量之间相互作用的情况下。因此,我们无法去量化编程语言在使用中的特定的效果。其它的方法,比如调查,可能对此有帮助。我们将在以后的工作中来解决这些挑战。

致谢

这个材料是在美国国家科学基金会(NSF)以及美国空军科学研究办公室(AFOSR)的授权和支持下完成的。授权号 1445079, 1247280, 1414172,1446683,FA955-11-1-0246。

参考资料

  1. Bhattacharya, P., Neamtiu, I. Assessing programming language impact on development and maintenance: A study on C and C++. In Proceedings of the 33rd International Conference on Software Engineering, ICSE'11 (New York, NY USA, 2011). ACM, 171–180.
  2. Bird, C., Nagappan, N., Murphy, B., Gall, H., Devanbu, P. Don't touch my code! Examining the effects of ownership on software quality. In Proceedings of the 19th ACM SIGSOFT Symposium and the 13th European Conference on Foundations of Software Engineering (2011). ACM, 4–14.
  3. Blei, D.M. Probabilistic topic models. Commun. ACM 55 , 4 (2012), 77–84.
  4. Cohen, J. Applied Multiple Regression/Correlation Analysis for the Behavioral Sciences. Lawrence Erlbaum, 2003.
  5. Easterbrook, S., Singer, J., Storey, M.-A., Damian, D. Selecting empirical methods for software engineering research. In Guide to Advanced Empirical Software Engineering (2008). Springer, 285–311.
  6. El Emam, K., Benlarbi, S., Goel, N., Rai, S.N. The confounding effect of class size on the validity of object-oriented metrics. IEEE Trans. Softw. Eng. 27 , 7 (2001), 630–650.
  7. Hanenberg, S. An experiment about static and dynamic type systems: Doubts about the positive impact of static type systems on development time. In Proceedings of the ACM International Conference on Object Oriented Programming Systems Languages and Applications, OOPSLA'10 (New York, NY, USA, 2010). ACM, 22–35.
  8. Harrison, R., Smaraweera, L., Dobie, M., Lewis, P. Comparing programming paradigms: An evaluation of functional and object-oriented programs. Softw. Eng. J. 11 , 4 (1996), 247–254.
  9. Harter, D.E., Krishnan, M.S., Slaughter, S.A. Effects of process maturity on quality, cycle time, and effort in software product development. Manage. Sci. 46 4 (2000), 451–466.
  10. Hindley, R. The principal type-scheme of an object in combinatory logic. Trans. Am. Math. Soc. (1969), 29–60.
  11. Jump, M., McKinley, K.S. Cork: Dynamic memory leak detection for garbage-collected languages. In ACM SIGPLAN Notices , Volume 42 (2007). ACM, 31–38.
  12. Kleinschmager, S., Hanenberg, S., Robbes, R., Tanter, É., Stefik, A. Do static type systems improve the maintainability of software systems? An empirical study. In 2012 IEEE 20th International Conference on Program Comprehension (ICPC) (2012). IEEE, 153–162.
  13. Li, Z., Tan, L., Wang, X., Lu, S., Zhou, Y., Zhai, C. Have things changed now? An empirical study of bug characteristics in modern open source software. In ASID'06: Proceedings of the 1st Workshop on Architectural and System Support for Improving Software Dependability (October 2006).
  14. Marques De Sá, J.P. Applied Statistics Using SPSS, Statistica and Matlab , 2003.
  15. Mayer, C., Hanenberg, S., Robbes, R., Tanter, É., Stefik, A. An empirical study of the influence of static type systems on the usability of undocumented software. In ACM SIGPLAN Notices , Volume 47 (2012). ACM, 683–702.
  16. Meyerovich, L.A., Rabkin, A.S. Empirical analysis of programming language adoption. In Proceedings of the 2013 ACM SIGPLAN International Conference on Object Oriented Programming Systems Languages & Applications (2013). ACM, 1–18.
  17. Milner, R. A theory of type polymorphism in programming. J. Comput. Syst. Sci. 17 , 3 (1978), 348–375.
  18. Mockus, A., Votta, L.G. Identifying reasons for software changes using historic databases. In ICSM'00. Proceedings of the International Conference on Software Maintenance (2000). IEEE Computer Society, 120.
  19. Odersky, M., Spoon, L., Venners, B. Programming in Scala. Artima Inc, 2008.
  20. Pankratius, V., Schmidt, F., Garretón, G. Combining functional and imperative programming for multicore software: An empirical study evaluating scala and java. In Proceedings of the 2012 International Conference on Software Engineering (2012). IEEE Press, 123–133.
  21. Petricek, T., Skeet, J. Real World Functional Programming: With Examples in F# and C#. Manning Publications Co., 2009.
  22. Pierce, B.C. Types and Programming Languages. MIT Press, 2002.
  23. Posnett, D., Bird, C., Dévanbu, P. An empirical study on the influence of pattern roles on change-proneness. Emp. Softw. Eng. 16 , 3 (2011), 396–423.
  24. Tan, L., Liu, C., Li, Z., Wang, X., Zhou, Y., Zhai, C. Bug characteristics in open source software. Emp. Softw. Eng. (2013).

作者

Baishakhi Ray ([email protected]), Department of Computer Science, University of Virginia, Charlottesville, VA.

Daryl Posnett ([email protected]), Department of Computer Science, University of California, Davis, CA.

Premkumar Devanbu ([email protected]), Department of Computer Science, University of California, Davis, CA.

Vladimir Filkov ([email protected]), Department of Computer Science, University of California, Davis, CA.

脚注


via: https://cacm.acm.org/magazines/2017/10/221326-a-large-scale-study-of-programming-languages-and-code-quality-in-github/fulltext?imm_mid=0f7103&cmp=em-prog-na-na-newsltr_20171007

作者:Baishakhi Ray, Daryl Posnett, Premkumar Devanbu, Vladimir Filkov 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

当你真正掌握了整体化的工程设计思维时,你就会发现高屋建瓴的工程设计已经远远超越了技术优化的层面。我们的每一件创造都催生于人类活动的大背景下,被这种人类活动赋予了广泛的经济学意义、社会学意义,甚至于具有了奥地利经济学家所称的“ 人类行为学意义 praxeology ”。而这种人类行为学意义则是明确的人类行为所能达到的最高层次。

对我来说这并不只是一种抽象的理论。当我在撰写关于开源项目开发的文章时,文章的内容正是关于人类行为学的 —— 这些文章并不涉及哪个具体的软件技术或者话题,而是在讨论科技所服务的人类行为。从人类行为学角度对科技进行更深入的理解,可以帮助我们重塑科技,并且提升我们的生产力和成就感。这种提升并不总是因为我们有了更新的工具,而更多的是因为我们改变了使用现有工具的思路,提升了我们对这些工具的驾驭能力。

在这个思路之下,我的随笔文章的第三篇中谈到了 C 语言的衰退和正在到来的巨大改变,而我们也确实能够感受到系统级别编程的新时代的到来。在这里,我会把我的统观见解总结成更具体的、更实用的对计算机语言设计的分析。例如总结出为什么一些语言会成功,另一些语言会失败。

在我最近的一篇文章中,我写道:所有计算机语言的设计都是对机器资源和程序员人力成本的相对权衡的结果;是对一种相对价值主张的体现。而这些设计思路都是在硬件算力成本不断下降,程序员人力成本相对稳定且可能不减反增的背景下产生的。我还强调了语言设计在实现了一些原有的权衡方案之后,其未来的转化和演变成本在这种语言的成败中所要扮演的一些额外角色。在文中我也阐述了编程语言设计者是如何为当前和可见的未来寻找新的最优设计方案的。

现在我要集中讲解一下我在上面段落里最后提到的那个概念,即语言设计工程师们其实可以在多个方面来改进和提高现阶段编程语言的设计水准。比如输入系统的优化,GC (垃圾回收机制) 和手动内存分配的权衡,命令导向、函数导向和面向对象导向的混合和权衡。但是站在人类行为学的角度去考量,我认为设计师们一定会做出更简单的设计权衡,即针对近景问题还是针对远景问题来优化一种语言的设计。

所谓的“远”、“近”之分,是指随着硬件成本的逐渐降低,软件复杂程度的上升和由现有语言向其他语言转化的成本的增加,根据这些因素的变化曲线所做出的判断。近景问题是编程人员眼下看到的问题,远景问题则是指可预见的,但未必会很快到来的一系列情况。针对近景问题的解决方案可以被很快部署下去,且能够在短期内非常有效,但随着情况的变化和时间的推移,这种方案可能很快就不适用了。而远景的解决方案可能因为其自身的复杂和超前性而夭折,或是因其代价过高无法被接受和采纳。

在计算机刚刚面世的时候, FORTRAN 就是一个近景设计方案, LISP 语言的设计则是针对远景问题;汇编语言多是近景设计方案,很好的阐明了这类设计很适用于非通用语言,同样的例子还包括 ROFF 标记语言。PHP 和 Javascript 则是我们后来看到的采用了近景设计思维的语言。那么后来的远景设计方案有哪些例子呢? Oberon、Ocaml、ML、XML-Docbook 都是它的例子。学术研究中设计出的语言多倾向于远景设计,因为在学术研究领域,原创性以及大胆的假设与想法是很重要的。这和学术研究的动机以及其奖励机制很有关系(值得注意的是,在学术研究中,倾向于远景设计的本源动机并非出于技术方面的原因,而是出自于人类行为,即标新立异才能在学术上有成绩)。学术研究中设计出的编程语言是注定会失败的;因为学术研究的产物常常有高昂的转入成本,无人问津的设计。这类语言也因为在社区中不够流行而不能得到主流的采纳,具有孤立性,且常常被搁置成为半成品。(如上所述的问题正是对 LISP 历史的精辟总结,而且我是以一个对 LISP 语言有深入研究,并深深喜爱它的使用者的身份来评价 LISP 的)。

一些近景设计的失败案例则更加惨不忍睹。对这些案例来说,我们能够期待的最好的结果就是这种语言可以消亡的相对体面一些,被一种新的语言设计取而代之。如果这些近景设计导向的语言没有死亡而是一直被沿用下去(通常是因为转化成本过高),那么我们则会看到不断有新的特性和功能在这些语言原来的基础之上堆积起来,以保持它们的可用性和有效性。直到这种堆积把这些语言变得越来越复杂,变的危若累卵且不可理喻。是的,我说的就是 C++ 。当然, 还有 Javascript。Perl 也不例外,尽管它的设计者 Larry Walls 有不错的设计品味,避免了很多问题,让这种语言得以存活了很多年。但也正是因为 Larry Walls 的好品味,让他在最终对 Perl 的固有问题忍无可忍之后发布了全新的 Perl 6。

从这种角度去思考程序语言,我们则可以把语言设计中需要侧重的目标重新归纳为两部分: (1)以时间的远近为轴,在远景设计和近景设计之间选取一个符合预期的最佳平衡点;(2)降低由一种或多种语言转化为这种新语言的转入成本,这样就可以更好地吸纳其它语言的用户群。接下来我会讲讲 C 语言是怎样占领全世界的。

在整个计算机发展史中,没有谁能比 C 语言在选择远景和近景设计的平衡点的时候做的更完美。事实胜于雄辩,作为一种实用的主流语言,C 语言有着很长的寿命,它目睹了无数个竞争者的兴衰,但它的地位仍旧不可取代。从淘汰它的第一个竞争者到现在已经过了 35 年,但看起来 C 语言的终结仍旧不会到来。

当然,你可以把 C 语言的持久存在归功于文化惰性,但那是对“文化惰性”这个词的曲解,C 语言一直得以延续的真正原因是因为目前还没有人能提供另一种足够好的语言,可以抵消取代 C 语言所需要的转化成本!

相反的,C 语言低廉的 内向转化成本 inward transition costs (转入成本)并未引起大家应有的重视,C 语言几乎是唯一的一个极其多样和强大的编程工具,以至于从它漫长统治时期的初期开始,它就可以适用于多种语言如 FORTRAN、Pascal、汇编语言和 LISP 的编程习惯。在一九八零年代我就注意到,我常常可以根据一个 C 语言新人的编码风格判断出他之前在使用什么语言,这也从另一方面证明了 C 语言可以轻松的被其它语言的使用者所接受,并吸引他们加入进来。

C++ 语言同样胜在它低廉的转化成本。很快,大部分新兴的语言为了降低自身的转入成本,都纷纷参考了 C 语言的语法。值得注意的是这给未来的语言设计带来了一种影响:即新语言的设计都在尽可能的向 C 的语法靠拢,以便这种新语言可以有很低的内向转化成本(转入成本),使其他语言的使用者可以欣然接受并使用这种新语言。

另一种降低转入成本的方法则是把一种编程语言设计的极其简单并容易入手,让那些即使是没有编程经验的人都可以轻松学会。但做到这一点并非易事。我认为只有一种语言 —— Python —— 成功的做到了这一点,即通过易用的设计来降低内向转化成本。对这种程序语言的设计思路我在这里一带而过,因为我并不认为一种系统级别的语言可以被设计的像 Python 一样傻瓜易用,当然我很希望我的这个论断是错的。

而今我们已经来到 2017 年末尾,你一定猜测我接下来会向那些 Go 语言的鼓吹者一样对 Go 大加赞赏一番,然后激怒那些对 Go 不厌其烦的人群。但其实我的观点恰恰相反,我认为 Go 本身很有可能在许多方面遭遇失败。Go 团队太过固执独断,即使几乎整个用户群体都认为 Go 需要做出某些方面的改变了,Go 团队也无动于衷,这是个大问题。目前,Go 语言的 GC 延迟问题以及用以平衡延迟而牺牲掉的吞吐量,都可能会严重制约这种语言的适用范围。

即便如此,在 Go 的设计中还是蕴含了一个让我颇为认同的远大战略目标。想要理解这个目标,我们需要回想一下如果想要取代 C 语言,要面临的短期问题是什么。正如我之前提到的,这个问题就是,随着软件工程项目和系统的不断扩张,故障率也在持续上升,这其中内存管理方面的故障尤其多,而内存管理故障一直是导致系统崩溃和安全漏洞的主要元凶。

我们现在已经认清,一种语言要想取代 C 语言,它的设计就必须遵循两个十分重要准则:(1)解决内存管理问题;(2)降低由 C 语言向本语言转化时所需的转入成本。从人类行为学的角度来纵观编程语言的历史,我们不难发现,作为 C 语言的准替代者,如果不能有效解决转入成本过高这个问题,那设计者所做的其它部分做得再好都不算数。相反的,如果一种 C 的替代语言把转入成本过高这个问题解决地很好,即使它在其他部分做的不是最好的,人们也不会对这种语言吹毛求疵。

而 Go 正是遵循了上述两点设计思路,虽然这个思路并不是一种完美无瑕的设计理论,也有其局限性。比如,目前 GC 延迟的问题就限制了 Go 的推广。但是 Go 目前选择了照搬 Unix 下 C 语言的传染战略,把其自身设计成一种易于转入,便于传播的语言。这样它的广泛和快速的传播就会使其迅速占领市场,从而打败那些针对远景设计的看起来更好的语言。

没错,我所指的这个远景设计方案就是 Rust。而 Rust 的自身定位也正是一种远景和长期的 C 语言替代方案。我曾经在之前的一些文章中解释过我为什么认为 Rust 还没有做好和 Go 展开竞争的准备。TIBOE 和 PYPL 的语言评价指数榜也很好的证明了我的对于 Rust 的这个观点。在 TIBOE 上 Rust 从来没有进过前 20 名。而在 TIBOE 和 PYPL 两个指数榜上, Rust 都要比 Go 的表现差很多。

五年后的 Rust 会发展的怎样还未可知。但如果 Rust 社区的开发人员对这种语言的设计抱着认真投入的态度,并愿意倾听,那么我建议他们要特别重视转入成本的问题。以我个人经历来说,目前由 C 语言转入 Rust 语言的壁垒很高,使人望而却步。如果 Corrode 之类的 Code-lifting 工具只是把 C 语言映射为不安全的 Rust 语言,那么 Corrode 这类工具也是不能解决这种转入壁垒的。或者如果有更简单的方法能够自动注释代码的所有权或生命周期,那么编译器就能把 C 代码直接映射到 Rust,人们也不再需要 Corrode 这一类工具了。目前我还不知道这个问题要如何解决,但我觉得 Rust 社区最好能够找到一种解决方案来代替 Corrode 和其同类工具。

在最后我想强调一下,Ken Thompson 曾经有过语言设计的辉煌历史。他设计的一些语言虽然看起来只是为了解决近景问题,实际上却具有很高的质量和开放程度,让这些语言同样非常适合远景问题,非常易于被提高和拓展。当然 Unix 也是这样的, 这让我不禁暗自揣测,那些我认为的 Go 语言中乍看上去不利于其远景发展的一些令人担忧烦扰的设计(例如缺乏泛型)也许并没有我想象的那样糟糕。如果确如我所认为的那样,即这些设计会影响 Go 的远景发展,那么恐怕我真的是比 Ken 还要聪明有远见了。但是我并不认为我有那么高明。Go 的前途我们还是只能拭目以待。


via: http://esr.ibiblio.org/?p=7745

作者:Eric Raymond 译者:ValoniakimyunfengHe 校对:yunfengHewxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

摘要:这篇文章我们将对一些各种各样的博客的流行度相对于他们在谷歌上的排名进行一个分析。所有代码可以在 github 上找到。

想法来源

我一直在想,各种各样的博客每天到底都有多少页面浏览量,以及在博客阅读受众中最受欢迎的是什么编程语言。我也很感兴趣的是,它们在谷歌的网站排名是否与它们的受欢迎程度直接相关。

为了回答这些问题,我决定做一个 Scrapy 项目,它将收集一些数据,然后对所获得的信息执行特定的数据分析和数据可视化。

第一部分:Scrapy

我们将使用 Scrapy 为我们的工作,因为它为抓取和对该请求处理后的反馈进行管理提供了干净和健壮的框架。我们还将使用 Splash 来解析需要处理的 Javascript 页面。Splash 使用自己的 Web 服务器充当代理,并处理 Javascript 响应,然后再将其重定向到我们的爬虫进程。

我这里没有描述 Scrapy 的设置,也没有描述 Splash 的集成。你可以在这里找到 Scrapy 的示例,而这里还有 Scrapy+Splash 指南。

获得相关的博客

第一步显然是获取数据。我们需要关于编程博客的谷歌搜索结果。你看,如果我们开始仅仅用谷歌自己来搜索,比如说查询 “Python”,除了博客,我们还会得到很多其它的东西。我们需要的是做一些过滤,只留下特定的博客。幸运的是,有一种叫做 Google 自定义搜索引擎(CSE)的东西,它能做到这一点。还有一个网站 www.blogsearchengine.org,它正好可以满足我们需要,它会将用户请求委托给 CSE,这样我们就可以查看它的查询并重复利用它们。

所以,我们要做的是到 www.blogsearchengine.org 网站,搜索 “python”,并在一侧打开 Chrome 开发者工具中的网络标签页。这截图是我们将要看到的:

突出显示的是 blogsearchengine 向谷歌委派的一个搜索请求,所以我们将复制它,并在我们的 scraper 中使用。

这个博客抓取爬行器类会是如下这样的:

class BlogsSpider(scrapy.Spider):
    name = 'blogs'
    allowed_domains = ['cse.google.com']

    def __init__(self, queries):
        super(BlogsSpider, self).__init__()
        self.queries = queries

与典型的 Scrapy 爬虫不同,我们的方法覆盖了 __init__ 方法,它接受额外的参数 queries,它指定了我们想要执行的查询列表。

现在,最重要的部分是构建和执行这个实际的查询。这个过程放在 start_requests 爬虫的方法里面执行,我们愉快地覆盖它:

    def start_requests(self):
        params_dict = {
            'cx': ['partner-pub-9634067433254658:5laonibews6'],
            'cof': ['FORID:10'],
            'ie': ['ISO-8859-1'],
            'q': ['query'],
            'sa.x': ['0'],
            'sa.y': ['0'],
            'sa': ['Search'],
            'ad': ['n9'],
            'num': ['10'],
            'rurl': [
                'http://www.blogsearchengine.org/search.html?cx=partner-pub'
                '-9634067433254658%3A5laonibews6&cof=FORID%3A10&ie=ISO-8859-1&'
                'q=query&sa.x=0&sa.y=0&sa=Search'
            ],
            'siteurl': ['http://www.blogsearchengine.org/']
        }

        params = urllib.parse.urlencode(params_dict, doseq=True)
        url_template = urllib.parse.urlunparse(
            ['https', self.allowed_domains[0], '/cse',
             '', params, 'gsc.tab=0&gsc.q=query&gsc.page=page_num'])
        for query in self.queries:
            for page_num in range(1, 11):
                url = url_template.replace('query', urllib.parse.quote(query))
                url = url.replace('page_num', str(page_num))
                yield SplashRequest(url, self.parse, endpoint='render.html',
                                    args={'wait': 0.5})

在这里你可以看到相当复杂的 params_dict 字典,它控制所有我们之前找到的 Google CSE URL 的参数。然后我们准备好 url_template 里的一切,除了已经填好的查询和页码。我们对每种编程语言请求 10 页,每一页包含 10 个链接,所以是每种语言有 100 个不同的博客用来分析。

42-43 行,我使用一个特殊的类 SplashRequest 来代替 Scrapy 自带的 Request 类。它封装了 Splash 库内部的重定向逻辑,所以我们无需为此担心。十分整洁。

最后,这是解析程序:

    def parse(self, response):
        urls = response.css('div.gs-title.gsc-table-cell-thumbnail') \
            .xpath('./a/@href').extract()
        gsc_fragment = urllib.parse.urlparse(response.url).fragment
        fragment_dict = urllib.parse.parse_qs(gsc_fragment)
        page_num = int(fragment_dict['gsc.page'][0])
        query = fragment_dict['gsc.q'][0]
        page_size = len(urls)
        for i, url in enumerate(urls):
            parsed_url = urllib.parse.urlparse(url)
            rank = (page_num - 1) * page_size + i
            yield {
                'rank': rank,
                'url': parsed_url.netloc,
                'query': query
            }

所有 Scraper 的核心和灵魂就是解析器逻辑。可以有多种方法来理解响应页面的结构并构建 XPath 查询字符串。您可以使用 Scrapy shell 尝试并随时调整你的 XPath 查询,而不用运行爬虫。不过我更喜欢可视化的方法。它需要再次用到谷歌 Chrome 开发人员控制台。只需右键单击你想要用在你的爬虫里的元素,然后按下 Inspect。它将打开控制台,并定位到你指定位置的 HTML 源代码。在本例中,我们想要得到实际的搜索结果链接。他们的源代码定位是这样的:

在查看这个元素的描述后我们看到所找的 <div> 有一个 .gsc-table-cell-thumbnail CSS 类,它是 .gs-title <div> 的子元素,所以我们把它放到响应对象的 css 方法(46 行)。然后,我们只需要得到博客文章的 URL。它很容易通过'./a/@href' XPath 字符串来获得,它能从我们的 <div> 直接子元素的 href 属性找到。(LCTT 译注:此处图文对不上)

寻找流量数据

下一个任务是估测每个博客每天得到的页面浏览量。得到这样的数据有各种方式,有免费的,也有付费的。在快速搜索之后,我决定基于简单且免费的原因使用网站 www.statshow.com 来做。爬虫将抓取这个网站,我们在前一步获得的博客的 URL 将作为这个网站的输入参数,获得它们的流量信息。爬虫的初始化是这样的:

class TrafficSpider(scrapy.Spider):
    name = 'traffic'
    allowed_domains = ['www.statshow.com']

    def __init__(self, blogs_data):
        super(TrafficSpider, self).__init__()
        self.blogs_data = blogs_data

blogs_data 应该是以下格式的词典列表:{"rank": 70, "url": "www.stat.washington.edu", "query": "Python"}

请求构建函数如下:

    def start_requests(self):
        url_template = urllib.parse.urlunparse(
            ['http', self.allowed_domains[0], '/www/{path}', '', '', ''])
        for blog in self.blogs_data:
            url = url_template.format(path=blog['url'])
            request = SplashRequest(url, endpoint='render.html',
                                    args={'wait': 0.5}, meta={'blog': blog})
            yield request

它相当的简单,我们只是把字符串 /www/web-site-url/ 添加到 'www.statshow.com' URL 中。

现在让我们看一下语法解析器是什么样子的:

    def parse(self, response):
        site_data = response.xpath('//div[@id="box_1"]/span/text()').extract()
        views_data = list(filter(lambda r: '$' not in r, site_data))
        if views_data:
            blog_data = response.meta.get('blog')
            traffic_data = {
                'daily_page_views': int(views_data[0].translate({ord(','): None})),
                'daily_visitors': int(views_data[1].translate({ord(','): None}))
            }
            blog_data.update(traffic_data)
            yield blog_data

与博客解析程序类似,我们只是通过 StatShow 示例的返回页面,然后找到包含每日页面浏览量和每日访问者的元素。这两个参数都确定了网站的受欢迎程度,对于我们的分析只需要使用页面浏览量即可 。

第二部分:分析

这部分是分析我们搜集到的所有数据。然后,我们用名为 Bokeh 的库来可视化准备好的数据集。我在这里没有给出运行器和可视化的代码,但是它可以在 GitHub repo 中找到,包括你在这篇文章中看到的和其他一切东西。

最初的结果集含有少许偏离过大的数据,(如 google.com、linkedin.com、Oracle.com 等)。它们显然不应该被考虑。即使其中有些有博客,它们也不是针对特定语言的。这就是为什么我们基于这个 StackOverflow 回答 中所建议的方法来过滤异常值。

语言流行度比较

首先,让我们对所有的语言进行直接的比较,看看哪一种语言在前 100 个博客中有最多的浏览量。

这是能进行这个任务的函数:

def get_languages_popularity(data):
    query_sorted_data = sorted(data, key=itemgetter('query'))
    result = {'languages': [], 'views': []}
    popularity = []
    for k, group in groupby(query_sorted_data, key=itemgetter('query')):
        group = list(group)
        daily_page_views = map(lambda r: int(r['daily_page_views']), group)
        total_page_views = sum(daily_page_views)
        popularity.append((group[0]['query'], total_page_views))
    sorted_popularity = sorted(popularity, key=itemgetter(1), reverse=True)
    languages, views = zip(*sorted_popularity)
    result['languages'] = languages
    result['views'] = views
    return result

在这里,我们首先按语言(词典中的关键字“query”)来分组我们的数据,然后使用 python 的 groupby 函数,这是一个从 SQL 中借来的奇妙函数,从我们的数据列表中生成一组条目,每个条目都表示一些编程语言。然后,在第 14 行我们计算每一种语言的总页面浏览量,然后添加 ('Language', rank) 形式的元组到 popularity 列表中。在循环之后,我们根据总浏览量对流行度数据进行排序,并将这些元组展开到两个单独的列表中,然后在 result 变量中返回它们。

最初的数据集有很大的偏差。我检查了到底发生了什么,并意识到如果我在 blogsearchengine.org 上查询“C”,我就会得到很多无关的链接,其中包含了 “C” 的字母。因此,我必须将 C 排除在分析之外。这种情况几乎不会在 “R” 和其他类似 C 的名称中出现:“C++”、“C”。

因此,如果我们将 C 从考虑中移除并查看其他语言,我们可以看到如下图:

评估结论:Java 每天有超过 400 万的浏览量,PHP 和 Go 有超过 200 万,R 和 JavaScript 也突破了百万大关。

每日网页浏览量与谷歌排名

现在让我们来看看每日访问量和谷歌的博客排名之间的联系。从逻辑上来说,不那么受欢迎的博客应该排名靠后,但这并没那么简单,因为其他因素也会影响排名,例如,如果在人气较低的博客上的文章更新一些,那么它很可能会首先出现。

数据准备工作以下列方式进行:

def get_languages_popularity(data):
    query_sorted_data = sorted(data, key=itemgetter('query'))
    result = {'languages': [], 'views': []}
    popularity = []
    for k, group in groupby(query_sorted_data, key=itemgetter('query')):
        group = list(group)
        daily_page_views = map(lambda r: int(r['daily_page_views']), group)
        total_page_views = sum(daily_page_views)
        popularity.append((group[0]['query'], total_page_views))
    sorted_popularity = sorted(popularity, key=itemgetter(1), reverse=True)
    languages, views = zip(*sorted_popularity)
    result['languages'] = languages
    result['views'] = views
    return result

该函数接受爬取到的数据和需要考虑的语言列表。我们对这些数据以语言的流行程度进行排序。后来,在类似的语言分组循环中,我们构建了 (rank, views_number) 元组(从 1 开始的排名)被转换为 2 个单独的列表。然后将这一对列表写入到生成的字典中。

前 8 位 GitHub 语言(除了 C)是如下这些:

评估结论:我们看到,所有图的 PCC (皮尔逊相关系数)都远离 1/-1,这表示每日浏览量与排名之间缺乏相关性。值得注意的是,在大多数图表(8 个中的 7 个)中,相关性是负的,这意味着排名的降低会导致浏览量的减少。

结论

因此,根据我们的分析,Java 是目前最流行的编程语言,其次是 PHP、Go、R 和 JavaScript。在日常浏览量和谷歌排名上,排名前 8 的语言都没有很强的相关性,所以即使你刚刚开始写博客,你也可以在搜索结果中获得很高的评价。不过,成为热门博客究竟需要什么,可以留待下次讨论。

这些结果是相当有偏差的,如果没有更多的分析,就不能过分的考虑这些结果。首先,在较长的一段时间内收集更多的流量信息,然后分析每日浏览量和排名的平均值(中值)值是一个好主意。也许我以后还会再回来讨论这个。

引用

  1. 抓取:

    1. blog.scrapinghub.com: Handling Javascript In Scrapy With Splash
    2. BlogSearchEngine.org
    3. twingly.com: Twingly Real-Time Blog Search
    4. searchblogspot.com: finding blogs on blogspot platform
  2. 流量评估:

    1. labnol.org: Find Out How Much Traffic a Website Gets
    2. quora.com: What are the best free tools that estimate visitor traffic…
    3. StatShow.com: The Stats Maker

via: https://www.databrawl.com/2017/10/08/blog-analysis/

作者:Serge Mosin 译者:Chao-zhi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出