编程语言设计的五大陷阱
在近几年来,编程语言的设计正在经历着类似于“文艺复兴”的过程,这么说主要是基于下面两个事实:
1)多核技术推动着PC消费者更多的关注并行程序。
2)动态语言的性能越来越好,其性期已经可以足够用来实现互联网服务,并且它们正在走出“脚本语言”阴影。
这篇文章试图收集最重要的编程语言的设计错误,以便让那些程序语言设计者们在设计新型的编程语言时避免。
文章避免了一些纠缠不清的有好有坏的问题,如:动态类型或是静态类型,同时也省略了那些看起来并不严重,很容易被修改的错误。例如,加入“参量”(ParametricType),这在Java中已经有了。Sun在发布Java 1.0版后的第八年才加入了这一功能。
1. Null指针
几乎在所有的主流编程语言中,对一个对像的引用可能会是一个空指针,这个错误会引发运行时错误。C.A.R.Hoare最近声明向这一“发明”负责,尽管如此,其它许多的设计者们都应该对这样的设计受到批评。下面是C.A.RHoare的“忏悔”:我把它叫做“亿万美元错误”。这个空指针的发明创造来自1965年。……现在的编程语言引入了“非空引用”的声明规格。这个方案被我在1965年给拒绝了。
其它语言,如C/C++更夸张,它们在运到这样的错误时,直接Crash掉,而Java,Python和其它语言会抛出一NullPointerException异常,但问题是,这个RuntimeException可能会被几乎所有的语句抛出。
其实,只需要一个静态类型的语言就可以保证不会出现空指针或空引用。例如:Cyclone是一个安全的C变种,其引入了非空指针和指针运算的限制。一些语言甚至让你根本不可能创建空指针,虽然这使得明确的指针不能行进行运算。Haskell就是这样的一个语言,其提供了MaybeMonad,其强制程序员考虑“Null”的情形。
2. 很难解析的语法
编程语言的语法应该来自LALR或是更好的LL(1)。今天的程序员需要适当的工具来支持其开发语言,也就是我们常说的IDE,编译器或是其它可以帮你解析程序语言的编程工具。这并不会出现在一个单一的前端。也许,多重编译器已经被实现出来了。这可能让我们的开始变得更容易一些。然而,我们现实中的一个反例是C++,几乎没有哪个C++的编译器可以把C++这个语言完美地正确地解释出来,而且不同C++的编译器的行为如此的诡异。编程语法的开销是微不足道的,程序员应该在编写程序中享有更快速和高效的回报。
3. 未定义的语义
别在语言规格中说“实现规范”!尽可能的少使用“未定义”这样的术语来描述语言的行为(C/C++中出现了很多undefined的行为)!黄金准则是StandardML,其是一个完整地正式的语义。C语言是这样一个反例,其规则中有太多太多的未定义的情况。
然而,由于其广泛使用,所以某些行为的定义已经成为了世界的共识(江湖的行规,或,潜规则)。举个例子,在C中,整型overflow的行为是未定义的,而编译器也是有能力推断出“x<x+1”是否总是为真。不幸的是,这个本来是编译器应该干的事,交给了程序员,于是在C的世界里,出现了大量的整型溢出的代码。而当整型溢出的时候,几乎所有的行为都是像x86处理器一样(如:maxint+1==minint)。
明确的语义可以让验证和错误检查更容易。虽然,软件校验来得比缓慢,但一定会来。我可以想像,编程语言的下一个机会将会是更容易地校验,这可能需要十到二十年的时间,但今天开始这样做的语言将会在那天成为世界的主流。
4. 坏的Unicode支持
程序中几乎都要处理字符串,但别忘了并不是所有人都会使用英语来编程。今天,几乎所有的编程语言都不支持Unicode,所以,我们只能使用ANSI的英语来编程。这个时代,程序员应该使用Unicode来编程,所以,源代码也可以声明其用什么来编码。
在文本和字节序间的转换和区分在的标准库方面会比语言方面更是一个问题,当然,这也影响了语法。
5. 预处理器
像C++和MP4的预处理器已经被广泛地使用着,使用预处理器更像是一种hack而不是一个干净的解决方案。他们被用来,使用外部文件(如头文件,但确没有正确地模块机制),使用条件编译,宏替换,等。把这些功能与编程语言集成起来一起使用可以增加程序的性能和开发效率,并没有什么不好的地方。
如果要举一个反例,那么就是预编译器的模块化系统。C使用#include而C++更痛苦,因为模板需要写一个大的头文件,而且其会被包含在几乎所有的其它文件中。而一个真正的模块化的系统是不需要使用extern关键字,也不需要程序的链接,而应该是直接使用。
文章原文地址:http://beza1e1.tuxen.de/articles/proglang_mistakes.html