最近在写编程语言发展史,其中就讨论了一门好的编程语言,应该关注哪些设计要点。根据书稿,整理如下,欢迎大家讨论和指正。
编程语言是现代计算机科学中不可或缺的一部分,人们使用编程语言来实现各种各样的软件和应用程序。但是,编程语言仅仅具备语言的基本元素还远远无法满足开发者的实际需求,好的编程语言需要在很多方面具有出色的设计水平,这包括安全性、性能、灵活性、低心智负担、正交性、易用性等方面,这些方面有的相辅相成,有的则是各有取舍,下面让我们更深入了解一下这些不同方面的含义及其重要性。
安全性和性能
安全性在计算机领域的不同上下文有不同的含义,一种是强调程序被非法反编译和破解的安全性,一种是强调程序自身存在缺陷在运行时出现非预期异常的安全性,本书在提到安全性时默认指第二种安全性的含义。
编程语言的安全性主要体现在多个方面。
类型安全:类型安全是指编程语言在编译或运行时能够检测和防止类型错误的能力。这有助于避免程序员在处理变量和数据结构时犯错误,从而减少潜在的安全漏洞。C语言格式函数printf作为类型安全问题的典型代表最能说明类型安全的重要性。一般强类型的编程语言(如C++)具备较好的类型安全能力,而弱类型的语言(如JavaScript)的类型安全能力较弱,很多类型错误需要在运行时才能发现。
异常处理:一流的异常处理能鼓励程序员考虑程序的异常逻辑,在一定程度上提高了语言的安全性。良好的异常处理机制设计,能让程序员能便捷地编写异常处理逻辑,并和普通程序逻辑明显区分,这有助于确保程序在遇到问题时能够优雅地恢复,而不是崩溃或产生不可预测的行为。更多异常处理的讨论可以参考本章后面的“异常处理”小节。
语言表达:语言表达的安全性包括:语义的清晰度、语法的一致性,防御性地还原程序员意图三个方面。当语义规则过于复杂、灵活、冗余、上下文相关时,语义的清晰性也会降低。高度精确的语义,可以有利于静态分析工具准确地发现潜在问题,空安全就是伴随指针的语义的完善逐渐建立起来的,它经历了由静态分析工具到编译器支持的过程。语法的一致性则可以让开发者更容易熟悉语言的语法特点,更容易理解和掌握语言,甚至可以凭直觉写出符合语法的代码。而防御性地还原程序员意图鼓励开发者采用最小权限的编程实践,例如Rust语言中的声明默认为常量,变量需要添加mut关键字,可以明显提高语言表达的安全性,避免无意的误用发生。
算术运算安全:算术运算安全常常以运算溢出、精度丢失等形式出现,并且隐藏较深,往往在数值较大、精度要求较高的场景中出现问题。一些对性能不敏感的脚本语言(如Ruby)提供了整数类型自动扩展能力,能在算术运算中自动扩充字长提供安全度很高的算术运算安全,Python语言内置了decimal模块也支持高精度的算数运算。但目前大部分语言不提供高精度运算安全的保障,浮点数的精度和溢出问题往往需要借助程序库来实现。
跨平台安全:编程语言的跨平台一致性是保障跨平台安全的核心。具体表现在字长的一致性、算术精度的一致性、基本类型的一致性、字符串编码的一致性、IO的一致性等。虚拟机语言和脚本语言在跨平台安全上更有优势。而面向底层系统编程的语言由于要面向硬件,往往会提供平台相关的抽象(如C/C++的int长度是平台相关的),导致降低了跨平台安全性。一些兼具安全性和底层编程的语言如Rust提供了std::os::raw的库来支持平台相关的编程支持,把一流的平台无关的原始类型保留给了语言核心。
语言生态(工具链的可靠性):一个健康的社区和成熟活跃的生态有助于提高编程语言的安全性。编程语言工具有庞大的用户群体和专业的维护团队,能及时发现和修复语言工具链中的缺陷、漏洞等问题,语言的基础库也更加稳定可靠,这些都为语言的可用性提供保障。大平台主推的语言(如C#)和流行的语言(如Python)往往具有成熟活跃的生态,很多项目在语言选型时都会考虑语言生态情况,可见语言生态的重要性。
内存管理安全:内存管理安全是指编程语言在分配和释放内存时能够防止错误的发生,避免出现重复释放