This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
<audio id="audio" title="08 | 语言的模型:如何打破单一语言局限,让设计更好地落地?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bb/cd/bb92f2e08fc2d2d6301fd8426e36c9cd.mp3"></audio>
你好!我是郑晔。
经过前面几讲,我们已经学习了如何去了解一个现有软件的设计。从这一讲开始,我们就进入到新的模块,讨论如何设计一个软件。做设计之前,我们要先知道手边有哪些工具。所以在这个模块开启之初,我们先来讨论程序设计语言。
或许你会觉得,程序设计语言有啥好讨论的?哪个程序员没有一门看家的程序设计语言呢?不知道你是否遇到过这样的问题:
- 面向对象用来组织程序是好但我用的是C语言
- 我用的是C++,函数式编程的好,跟我有什么关系;
- 动态语言那些特性很好可惜我用的是Java
- ……
如果你这么想,说明你被自己的看家本事给局限住了,这种思维方式会让你即便学到了更多的好东西,也只能无可奈何。
其实程序设计语言之间没有那么泾渭分明的界限程序员唯有多学习几门语言才能打破语言的局限让设计更好地落地。你可以根据项目特点选择合适的语言也可以将其它语言一些优秀的地方借鉴过来。Andrew Hunt和David Thomas在《程序员修炼之道》The Pragmatic Programmer中给程序员们提了一项重要的建议**每年至少学习一门新语言。**
可是,语言那么多,我要一个一个都学过去吗?学语言到底在学什么呢?
其实,程序设计语言本身也是一个软件,它也包含模型、接口和实现。而**我们学习程序设计语言主要是为了学习程序设计语言提供的编程模型**,比如:不同的程序组织方式,不同的控制结构等等。因为不同的编程模型会带给你不同的思考方式。
既然要学习编程模型,我们就要先知道编程模型设计的来龙去脉,所以,今天我先带你领略一下程序设计语言的发展历程。
## 程序设计语言发展简史
我们今天接触到的程序设计语言都是图灵完备的。这里的“图灵完备”指的是语言指定的数据操作规则能够实现图灵机的全部功能图灵机的概念是由阿兰·图灵提出的图灵机为计算机能够解决的问题划定了一个边界。所以图灵机是所有程序设计语言最底层的模型程序设计语言都是在这个基础上生长出来的包括众所周知的计算机基础用0和1编码。
我们今天的计算机能够识别的都是0和1但真正用0和1直接写代码的人少之又少因为实在太麻烦了。所以早在计算机诞生之初就产生了**汇编语言**它可以将那些0101的操作符变成更容易记住的ADD、MOV之类的指令。
相比于01串汇编虽然进步了一些但人们很快就发现用汇编写程序也是非常痛苦的事情因为只有对计算机了如指掌才能写好汇编。更可怕的是即便你熟练掌握了一种计算机的汇编语言换成另外一种计算机你也必须从头学过。
这时,就轮到**高级程序设计语言**登场了。
第一门被广泛使用的高级程序设计语言是Fortran它为程序设计语言的发展奠定了基础。比如一些基本控制结构出现了数据开始拥有了类型类型就是一种对内存数据的解释方式。虽然这些东西在今天看来非常简单但和那个年代使用的汇编相比简直是一个巨大的飞跃。
Fortran对于计算机的发展起到了巨大的推动作用人们也逐渐认识到高级程序设计语言对于开发效率的提高。接下来人们开发了各种高级程序设计语言不断地探索怎样写好程序。
早期程序设计语言探索的集大成者就是**C语言**它提供了对于计算机而言最为恰当的抽象屏蔽了计算机硬件的诸多细节。时至今日C语言依然受众广泛。
随着高级程序设计语言的发展,门槛逐步降低,人们可以开发的程序规模也逐渐膨胀。这时候,**如何组织程序**成了新的挑战。有一种语言搭着C语言的便车将面向对象的程序设计风格带入了主流视野这就是C++。很长一段时间内C++成为了行业中最主流的选项既兼容C语言又提供了很好的程序组织方式。
虽然各种高级程序设计语言已经屏蔽了很多细节,但有一个问题始终没有得到很好的解决,也由此引发了更多的问题,这就是**内存管理**。其实,人们早就在尝试各种屏蔽内存管理的方式,但因为早期计算机硬件性能有限,所以没有任何一种方式能够成为行业主流。
后来计算机硬件的能力得到了大幅度提升这让那些在故纸堆里的技术又焕发了新的活力。这个阶段的胜利者是Java一方面它支持面向对象编程另一方面它还有垃圾回收机制——一种内存管理的方式。
Java的路其实也很坎坷因为它早期在个人电脑上的尝试并不算成功。后来选择了企业级开发的赛道才有机会展现自己的优势。因为企业级服务器本身性能优于个人电脑对Java有更高的容忍度它才得到了机会不断进行自身的优化。
当硬件不再是程序设计语言的发展障碍之后,程序设计语言又该如何发展呢?
从前面的历程不难看出,程序设计语言的发展就是一个“逐步远离计算机硬件,向着待解决的问题靠近”的过程。所以,程序设计语言接下来的发展方向就是**探索怎么更好地解决问题**了。
前面说的这些只是程序设计语言发展的主流路径,其实还有一条不那么主流的路径也一直在发展,就是**函数式编程的程序设计语言**这方面的代表就是LISP。
在这条路上,刚开始,很多人都是偏学术风格的,他们更关心的是解决方案是否优雅,也就是说,如何解决问题,如何一层层构建抽象。他们也探索更多的可能,垃圾回收机制就是从这里来的。但同样受限于当时硬件的性能,这条路上的探索在很长一段时间之内都只是一个小众游戏。
当硬件的性能不再成为阻碍,如何解决问题开始变得越来越重要时,函数式编程终于和程序设计语言发展的主流汇合了。促进函数式编程引起广泛重视也还有一个硬件因素:**多核**。
多核的出现本身是IT行业应对CPU发展进入瓶颈期的一个解决方案但它却打破了很多程序员只习惯于利用一个CPU写程序的传统方式。
为了利用多核的优势人们探索了各种方案今天看到的各种并发模型、异步模型等解决方案都从那时开始得到了蓬勃的发展。函数式编程在这个方面的探索就是利用自己声明式的表达方式屏蔽了硬件差异。让人们注意到函数式编程的价值的就是著名的MapReduce。
函数式编程的兴起让那些在函数式编程社区的探索随之兴起比如声明式编程、DSL、元编程等等。一些后出现的程序设计语言开始将面向对象和函数式编程二者融合起来比如Scala。而像Java和C++这些“老战士”则逐渐地将函数式编程的支持加入到语言之中。
相比于这些“正规军”,还有一股力量也逐渐从边缘走上了舞台,这就是**动态语言**,代表语言有 Perl、Python、Ruby、PHP等等。以前人们更喜欢用“脚本语言”称呼这类程序设计语言这个名字表明它就是为了简单地解决一些特定的问题而出现的。所以在人们心目中它们显得并不那么正式。但它们简单、轻巧的特性有效地降低了入门的门槛也赢得了一大批拥趸。
**语言的发展就是一个互相学习和借鉴的过程**。以前动态语言的弱项在于不适用于规模比较大的工程而近些年来随着动态语言用户的增多配套的工具也逐渐多了起来动态语言项目的规模也逐渐增大。而在主航道的程序设计语言也纷纷向动态语言学习努力地简化代码编写的难度比如Java和C++都开始支持类型推演Type Inference目的就是让程序员少敲几个字符。
至此,我简单地带你回顾了一下程序设计语言的发展历程,梳理了程序设计语言的发展脉络。从中不难看出,如果把程序设计语言当作一个软件,它的发展历程就是一个逐渐添加新模型的过程,而其发展的结果就是如今的开发门槛越来越低,能够开发的程序规模越来越大。
## 一切语法都是语法糖
现在,你已经能更好地理解我们在前面提出的说法,**学习程序设计语言其实就是要学习语言提供的编程模型。**
以我学过的一些程序设计语言为例:
- C语言提供了对汇编指令直接的封装。
- C++先是提供了面向对象,后来又提供了泛型编程。
- Java把内存管理从开发者面前去掉了后来引入的Annotation可以进行声明式编程。
- Ruby提供了动态类型以及由Ruby on Rails引导出的DSL风格。
- Scala和Clojure提供了函数式编程。
- Rust提供了新的内存管理方式而Libra提供的Move语言则把它进一步抽象成了资源的概念。
既然学习新的程序设计语言是为了学习新的编程模型,反过来也可以说,**不提供新编程模型的语言是不值得刻意学习的**。如果你已经学会了一两门程序设计语言,学习一门新的语言其实并不困难,因为每种语言提供的新模型是有限的,基本的元素是类似的,无非是用了不同的关键字。
所以,学习新语言,只是在做增量的学习,思维负担并没有那么沉重。一旦对于程序设计语言的模型有了新的认识,你就能理解一件事:**一切语法都是语法糖**。
语法糖Syntactic sugar是英国计算机科学家彼得·兰丁发明的一个术语指的是那些为了方便程序员使用的语法它对语言的功能没有影响。
懂得了语法糖的道理,要想更好地理解程序设计语言,一种好的做法就是打开语法糖,了解一下语法是怎么实现的:
- 类型是一种对内存的解释方式。
- class/struct是把有相关性的数据存放到一起的一种数据组织方式。
- Groovy、Scala、Kotlin、Clojure等JVM上的新语言提供了一种不同于Java的封装JVM的方式。
- ……
通过前面的介绍,你也看到了,语言的发展并非一蹴而就,而是一个渐进式的发展历程。一些新的尝试总会在一些不起眼的地方冒出来,而且语言之间也在相互借鉴。
如果你能每年学习一门新语言,起初,你可以了解不同的编程模型。当你的积累足够多了,学习语言就是在跟踪程序设计语言的最新发展了。
**当你手里有了足够多的“武器”时,你就可以打开思路,运用不同的方式解决问题了**,甚至把其它语言的好东西,借鉴到自己使用的语言中。
## 总结时刻
今天,我们谈到了程序设计语言。学习不同的程序设计语言可以帮助我们更好地落地设计,也可以让我们向不同的语言借鉴优秀的方面。
我们简要地了解了程序设计语言的发展历史,从最开始的对机器模型的封装,到今天不断降低的开发门槛,程序设计语言的演化从未停止。我们也看到各种不同的编程风格在经历了最初各自独立的发展之后,开始慢慢融合。
对程序设计语言发展的了解,可以帮助我们理解一件事:**一切语法都是语法糖**。新的语法通常是在既有的结构上不断添加出来的,为的是简化代码的编写。
《程序员修炼之道》鼓励程序员们每年至少学习一门新语言,主要是为了让我们去学习新的编程模型,而不提供新编程模型的语言不值得刻意去学习。
不过,这就需要你对程序设计语言有着更深的理解。下一讲,我们来看程序设计语言的接口,看看更具体的语言演化是如何发生的。
如果今天的内容你只能记住一件事,那请记住:**每年至少学习一门能够提供新编程模型的程序设计语言。**
<img src="https://static001.geekbang.org/resource/image/e9/ad/e9b1ccd6cdb75768cf8070fa60e2ecad.jpg" alt="">
## 思考题
最后,我想请你分享一下,你最近打算学习哪门新的程序设计语言呢?为什么?欢迎在留言区分享你的想法。
感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@@ -0,0 +1,135 @@
<audio id="audio" title="09 | 语言的接口:语法和程序库,软件设计的发力点" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3d/ba/3d91fcf5ebaef4df21e69fa99be6e3ba.mp3"></audio>
你好!我是郑晔。
在上一讲中,我们学习了程序设计语言模型的演变过程。学习不同的程序设计语言,实质上就是学习不同的编程模型。谈完了模型,接下来,就该说说接口了。
这一讲,我们就来谈谈程序设计语言的接口,一说起程序设计语言的接口,你的直观印象肯定是程序设计语言的语法,那是一个你已经很熟悉的话题了,但程序设计语言还有一个你可能都不曾留意的接口:程序库。
如果你已经能够完成基本功能的开发,想让自己在编程水平上再进一步,成为一个更好的程序员,你就可以从封装程序库开始练习。因为想封装出一个好的程序库所需的能力,就是软件设计所需的能力。封装程序库,可以成为你软件设计进阶的发力点。
## 消除重复的程序库
我们写程序的时候,只要规模稍微大一点,你就会发现同样的模式经常出现,差别无非是几个地方的参数不一样,这就是重复。
最开始的重复是指令级别的重复,程序员们会把同样的指令序列放到一起,通过传入不同的参数进行识别。你发现了吗?我说的就是函数。函数已经成了今天的主流编程方式,是几乎所有的程序设计语言都有的基础设施,人们甚至忘了它的由来。
写程序的一项主要日常工作就是定义各种函数。一旦你定义了大量的函数,就会发现有很多函数不仅仅在某个项目中是适用的,而且在很多项目中都是适用的。这时,作为一个“懒惰”的程序员,我们就会把这些在多个项目中使用的部分抽取出来,组成一个模块,这就是程序库的来源。
所以,程序库就是为了消除重复而出现的。而消除重复,也是软件设计的初衷。
程序库Library是程序员最熟悉的一项内容。学习一门新语言首先是学习语法Syntax然后学习程序库Library之后再学习运行时Runtime这样你就具备一门语言的基础了。再往后你需要了解的就是各种惯用法Idiom以及如何运用到实际的工作中。
有一些程序库实在是太常用了它们就会随着语言一起发布成为标准库。比如程序员熟知的第一个程序“Hello, world”的做法来自《C程序设计语言》其中用到的printf就是来自C的标准库。再比如Java程序员无人不知的JDK里面包含了大量的程序库一个Java程序员如果不能说出几个容器简直就不好意思和人打招呼。
当然,如果在实际工作中只使用标准库,有些代码写起来还是非常麻烦的。因为标准库提供的能力通常是很基础的。这时,我们就需要利用更多的第三方程序库,它们给提供了更丰富的选项,去完善标准库做得不够的地方。
也就是说它们会在标准库的基础上再做一次封装提供一个新的编程模型或是新的接口甚至修正一些标准库的bug让开发变得更简单。只要是人气足够的语言在这个方面做得都非常好它们会提供大量的第三方库。
正是因为第三方库的兴起,**怎样管理第三方库**就成了一个问题。今天,这已经成了一个标准的问题,也有了标准的解决方案,那就是**包管理器**。很多语言都有了自己的包管理器像Java的Maven、Node的NPM、Ruby的RubyGems等等而像Rust这样年轻的语言包管理器甚至变成了随语言的发行包一起发布的一项内容。
## 语言设计就是程序库设计
### new
虽然程序库受限于特定的程序语言但其表达的思想却不受语言限制。我给你举个例子在软件开发中我们最常做的一个操作就是初始化。如果采用C这样比较早期的语言通常的做法就是分配一块内存然后给这块内存赋值如下图所示
<img src="https://static001.geekbang.org/resource/image/65/37/659ef6ccbf186611e5ebe07618d5f237.jpeg" alt="">
类似的代码会反复出现,成为一个固定的模式。
而一些新的语言索性就将二者合二为一成了一个固定的语法这就是很多人熟悉的new。无论是C++程序员还是Java程序员对new一定不陌生。
当我们调用new时会发生什么呢首先它会申请一块内存然后调用对应类的构造函数。类也好构造函数也罢这些内容都是C语言没有的但这个初始化的模式与C语言是如出一辙的。
只不过在C语言里面这个操作通常是通过程序库实现的而到了C++和Java中它成了语法。一旦变成了语法它就成了语言的一部分成为了一个特定的模型。
对于使用者而言,这个模型就是一个接口,只要接口的行为不变,我的代码就不用变。
但对于接口另一面的实现者而言它就可以做一些特定的工作了。比如插入不同的内存分配算法这就是C++的allocator所做的事情再比如把内存完全管控起来这就是Java做的事情。没错Java之所以能够让程序员忽略内存管理new功不可没。
一个经过验证的模式最终变成了语言的一部分,而它的起点只是一个常见的用法:一个程序库。
### synchronized
我再用Java中的synchronized给你举个例子。我们都知道并发编程是程序员的一门必修课。学习并发编程一方面要学习各种概念比如“锁”另一方面还要学习不同语言相应的程序库。因为这些概念太常用了。所以Java干脆把它变成了一个语法也就是synchronized。
成为语法固然是一个巨大的进步但是在一些场景下语法反而会显得僵化。这时候又轮到程序库登场了。我还以前面所说的new和synchronized为例给你讲解一下。
## 程序库设计就是语言设计
### new
new虽然帮我们解决了一些问题但与new配合使用的构造函数往往有一个致命伤那就是它只有一个名字也就是类的名字。
当我们需要表达多种不同的构造逻辑时各村就出现了各村的高招。我曾经见过有人利用重载overload来解决问题的不同的构造逻辑用不同类型的参数。比如一个用HashMap一个用TreeMap。作为一个新加入项目的程序员你很难想到这是两种不同的构造逻辑它们与这里不同类型的数据结构其实一点关系都没有。
一个更好的解决方案是利用**工厂模式**解决这个问题,也就是说,用一个名字更能表意的函数,代替构造函数作为构造入口。
还是以Java为例ArrayList是Java程序员很熟悉的一个数据结构。如果我要创建一个包含两个元素的ArrayList同时还要创建一个初始容量为10的ArrayList。用JDK原生的做法我可以这么做
```
// 创建有两个元素的数组
ArrayList&lt;String&gt; listWithElements = new ArrayList();
listWithElements.add(&quot;foo&quot;);
listWithElements.add(&quot;bar&quot;);
```
```
// 创建一个初始容量为10的数组。
ArrayList&lt;String&gt; listWithCapacity = new ArrayList(10);
```
这种做法可行但代码表意并不清晰。那有没有更好的做法呢Google的Guava程序库对于同样的场景给出了一个不同的做法
```
ArrayList&lt;String&gt; listWithElements = newArrayList(&quot;foo&quot;, &quot;bar&quot;);
ArrayList&lt;String&gt; listWithCapacity = newArrayListWithCapacity(10);
```
显然从语义上来说这种做法更清晰。Java领域的行业名著《Effective Java》第三版的第一个条款是“用静态工厂方法代替构造函数”讨论的就是这种做法。
### synchronized
再来看synchronized。它虽然解决了一部分并发的问题但是这个解决问题的方式粒度太大了。程序员只要稍微深入一些就会感到synchronized的掣肘。于是Java的开发者在Java 5中开始了新一轮的编程模型探索这次探索的成果就是后来的并发编程库。这也为面试官们提供了新的素材。
你看到了吧,解决同一个问题,它可以用一个语法,也可以用一个程序库,二者之间是等价的。
Andrew Koenig和Barbara Moo写过一本书《C++沉思录》书里记录了C++早期开发者在设计各种C++特性时的思考,这是一本编程思想之作。当年读这本书时,有两章的标题让我陷入了沉思,分别是**“语言设计就是程序库设计”和“程序库设计就是语言设计”**。
通过今天的学习,这两句话对你来说就不难理解了。因为语法和程序库是在解决同一个问题,二者之间是相互促进的关系。通常是先有程序库,再有语法;如果语法不够好,新的程序库就会出现,新一轮的编程模型就开始孵化。
一切有生命力的语言都会不断改善自己的语法某些好用的程序库就可以转正成为语法。比如Java引入Lambda支持函数式编程C++引入类型推演,简化了代码编写。
同样地,程序库的发展也在推动着语言的不断进步,有一部分语法就是为了让程序库表现得更好而存在的。比如说:
- C里面的宏虽然很多人用它来定义常量但只有编写程序库才能让它更好地发挥出自身的价值
- Java中的Annotation很多人都在用但用它做过设计的人却很少因为它的使用场景是以程序库居多
- Scala中的隐式转换如果你没有设计过DSL很可能根本就不知道它有什么具体的作用。
至此,你已经能够理解程序设计语言的接口不只包含语法,还有程序库。而且,学习一种程序设计语言提供的模型时,不仅仅要看语法本身有什么,还要了解有语言特性的一些程序库。
所以,对于程序员而言,想要自己的编程水平上一个台阶,学习编写程序库是一个很好的路径。一方面,我们可以锻炼自己从日常工作中寻找重复;另一方面,我们可以更好地理解程序设计语言提供的能力。
## 总结时刻
今天,我们的学习主题是程序库,程序库最初只是为了消除重复。后来,逐渐有了标准库,然后有了大量的第三方库,进而发展出包管理器。
如果通用性足够好,一些经过大量实践验证过的程序库甚至会变成语言的语法,而一些语法解决得不够好的地方,又会出现新的程序库去探索新的解决方案。所以,**语言设计就是程序库设计,程序库设计就是语言设计**。二者相互促进,不断发展。
当你开始学习如何编写程序库,你对软件设计的理解就会踏上一个新的台阶。
我们说过,学习不同程序设计语言一个重要的原因是为了相互借鉴。理解了模型和接口,你就知道该借鉴什么,但具体如何借鉴呢?我们还需要了解这些模型是如何实现的,所以下一讲,我们就来谈谈程序设计语言的实现。
如果今天的内容你只能记住一件事,那请记住:**提升软件设计能力,可以从编写程序库入手。**
<img src="https://static001.geekbang.org/resource/image/91/d0/910d852b08bc45f94acb879cf3080cd0.jpeg" alt="">
## 思考题
今天我们讲到了程序库和语法之间的互相促进,最后,我想请你分享一下,你还能找出哪些语法和程序库互相促进的例子呢?欢迎在留言区分享你的想法。
感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@@ -0,0 +1,89 @@
<audio id="audio" title="10 | 语言的实现:运行时,软件设计的地基" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/3d/395ce527f5e6b8eff09c195adc9da53d.mp3"></audio>
你好!我是郑晔。
通过前两讲的学习,相信你已经对程序设计语言有了全新的认识。我们知道了,在学习不同的程序设计语言时,可以相互借鉴优秀的设计。但是要借鉴,除了模型和接口,还应该有实现。所以,这一讲,我们就来谈谈程序设计语言的实现。
程序设计语言的实现就是支撑程序运行的部分:运行时,也有人称之为运行时系统,或运行时环境,它主要是为了实现程序设计语言的执行模型。
相比于语法和程序库,我们在学习语言的过程中,对运行时的关注较少。因为不理解语言的实现依然不影响我们写程序,那我们为什么还要学习运行时呢?
因为**运行时,是我们做软件设计的地基。**你可能会问,软件设计的地基不应该是程序设计语言本身吗?并不是,一些比较基础的设计,仅仅了解到语言这个层面是不够的。
我用个例子来进行说明我曾经参与过一个开源项目在JVM上运行Ruby。这种行为肯定不是 Java语言支持的为了让Ruby能够运行在JVM上我们将Ruby的代码编译成了Java的字节码而字节码就属于运行时的一部分。
你看,**做设计真正的地基,并不是程序设计语言,而是运行时,有了对于运行时的理解,我们甚至可以做出语言本身不支持的设计**。而且理解了运行时,我们可以成为一个更好的程序员,真正做到对自己编写的代码了如指掌。
不过运行时的知识很长一段时间内都不是显学我初学编程时这方面的资料并不多。不过近些年来这方面明显得到了改善各种程序设计语言运行时的资料开始多了起来。尤其在Java社区JVM相关的知识已经成为很多程序员面试的重要组成部分。没错JVM就是一种运行时。
接下来我们就以JVM为例谈谈怎样了解运行时。
## 程序如何运行
首先,我们要澄清一点,对于大部分普通程序员来说,学习运行时并不是为了成为运行时的开发者,我们只是为了更好地理解自己写的程序是如何运行的。
运行时的相关知识很多,而**“程序如何运行”**本身就是一条主线,将各种知识贯穿起来。程序能够运行,前提条件是,它是一个可执行的文件,我们就从这里开始。
一般来说可执行的程序都是有一个可执行文件的结构对应到JVM上就是类文件的结构。然后可执行程序想要执行需要有一个加载器将它加载到内存里这就是JVM类加载器的工作。
加载是一个过程那么加载的结果是什么呢就是按照程序运行的需求将加载过来的程序放到对应的位置上这就需要了解JVM的内存布局比如程序动态申请的内存都在堆上为了支持方法调用要有栈还要有区域存放我们加载过来的程序诸如方法区等等。
到这里程序完成了加载做好了运行的准备但这只是静态的内容。接下来你就需要了解程序在运行过程中的表现。一般来说执行过程就是设置好程序计数器Program CounterPC然后开始按照指令一条一条地开始执行。所以重点是要知道这些指令到底做了什么。
在Java中我们都知道程序会编译成字节码对于Java来说字节码就相当于汇编其中的指令就是Java程序执行的基础。所以突破口就在于**了解指令是如何执行的**。
其实大部分JVM指令理解起来都很简单尤其在你了解内存布局之后。比如加法指令就是在栈上取出两个数相加之后再放回栈里面。
我要提一个看上去很简单的指令它是一根拴着牛的绳子这就是new没错就是创建对象的指令。那头牛就是内存管理机制也就是很多人熟悉的GC这是一个很大的主题如果展开来看的话也是一个庞杂的知识体系。
有了对指令的理解就对Java程序执行有了基本的理解。剩下的就可以根据自己的需要打开一些被语法和程序库隐藏起来的细节。比如synchronized是怎样实现的顺着这条线我们可以走到内存模型Java Memory ModelJMM
当然这里的内容并不是为了与你详细讨论JVM的实现无论是哪个知识点真正展开后实际上都还会有很多的细节。
这里只是以JVM为例进行讲解学习其他语言的运行时也是类似的带着“程序如何运行”这个问题去理解就好了。只不过每种语言的执行模型是不同的需要了解的内容也是有所差异的。比如理解C的运行时你需要知道更多计算机硬件本身的特性而理解一些动态语言的运行时则需要我们对语法树的结构有一定认识。
有了对运行时的理解我们就可以把一些好的语言模型借鉴到自己的语言中比如使用C语言编程时我们可以实现多态做法就是自己实现一个虚拟表这就是面向对象语言实现多态的一种方案。
**运行时的编程接口**
我们前面说过,做软件设计的地基是运行时,那怎样把我们的设计构建在运行时之上呢?这就要依赖于运行时给我们提供的接口了。所以,我们学习运行时,除了要理解运行时本身的机制之外,还要掌握运行时提供的编程接口。
在Java中最简单的运行时接口就是运行时类型识别的能力也就是很多人熟悉的getClass。通过这个接口我们可以获取到类的信息一些程序库的实现就会利用类自身声明的信息。比如之前说过有些程序库会利用Annotation进行声明式编程这样的程序库往往会在运行的过程中以getClass为入口进行一系列操作将Annotation取出来然后做相应的处理。
当然这样的接口还有很多一部分是以标准库的方式提供的比如动态代理。通过阅读JDK的文档我们很容易学会怎么去运用这些能力。还有一部分接口是以规范的方式提供的需要你对JVM有着更好的理解才能运用自如比如字节码。
前面我们说了,通过了解指令的执行方式,可以帮助我们更好地理解运行时的机制。有了这些理解,再来看字节码,理解的门槛就大幅度地降低了。
如果站在字节码的角度思考问题我们甚至可以创造出一些Java语言层面没有提供的能力比如有的程序库给Java语言扩展AOPAspect-oriented programming面向切面编程的能力。这样一来你写程序的极限就不再是语言本身了而是变成了字节码的能力极限。
给你举个例子比如Java 7发布的时候字节码定义了InvokeDynamic这个新指令当时语言层面上并没有提供任何的语法。如果你需要就可以自己编写字节码使用这个新指令像JRuby、Jython、Groovy 等一些基于JVM的语言开发者就可以利用这个指令改善自己的运行时实现。当然InvokeDynamic的诞生本身就是为了在JVM上更好地支持动态语言。
好消息是,操控字节码这件事的门槛也在逐渐降低。最开始,操作字节码是一件非常神秘的事情,在许多程序员看来,那是只有 SUN 工程师才能做的事情那时候Java还属于 SUN
后来,出现了一个叫[ASM](https://asm.ow2.io/)的程序库把字节码拉入了凡间越来越多的程序员开始拥有操作字节码的能力。不过使用ASM依然要对类文件的结构有所理解用起来还是比较麻烦。后来又出现了各种基于ASM的改进现在我个人用得比较多的是[ByteBuddy](https://bytebuddy.net/)。
有了对于字节码的了解在Java这种静态的语言上就可以做出动态语言的一些效果。比如Java语言的一些Mock框架为什么可以只声明接口就能够执行因为背后常常是动态生成了一个类。
一些动态语言为了支持自己的动态特性也提供了一些运行时的接口给开发者。比如Ruby里面很著名的method_missing很多框架用它实现了一些效果即便你未定义方法也能够执行的。你也许想到了我们提到过的Ruby on Rails中各种find_by方法就可以用它来实现。
method_missing其实就是一个回调方法当运行时在进行方法查找时如果找不到对应方法时就调用语言层面的这个方法。所以你看出来了这就是运行时和语言互相配合的产物。如果你能够对于方法查找的机制有更具体的了解使用起来就可以更加地得心应手就能实现出一些非常好的设计。
## 总结时刻
今天,我们讨论了程序设计语言的实现:运行时。**对于运行时的理解,我们甚至可以做出语言本身不支持的设计。所以,做设计真正的地基,并不是程序设计语言,而是运行时。**
理解运行时,可以将**“程序如何运行”**作为主线,将相关的知识贯穿起来。我们从了解可执行文件的结构开始,然后了解程序加载机制,知道加载器的运作和内存布局。然后,程序开始运行,我们要知道程序的运行机制,了解字节码,形成一个整体认识。最后,还可以根据需要展开各种细节,做深入的了解。
有一些语言的运行时还提供了一些语言层面的编程接口,程序员们可以与运行时进行交互,甚至拥有超过语言本身的能力。这些接口有的是以程序库的方式提供,有的则是以规范的方式提供。如果你是一个程序库的开发者,这些接口可以帮助你写出更优雅的程序。
关于程序设计语言的介绍我用了三讲分别从模型、接口和实现等不同的角度给你做了介绍。目的无非就是一个想做好设计不仅仅要有设计的理念更要有实际落地的方式。下一讲我们来讲一个你可以在项目中自己设计的语言DSL。
如果今天的内容你只能记住一件事,那请记住:**把运行时当作自己设计的地基,不受限于语言。**
<img src="https://static001.geekbang.org/resource/image/0a/d9/0ac9b789f401a370ff91b4bd495417d9.jpg" alt="">
## 思考题
最后,我想请你分享一下,你知道哪些程序库的哪些特性是利用运行时交互的接口实现的?欢迎在留言区分享你的想法。
感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@@ -0,0 +1,121 @@
<audio id="audio" title="11 | DSL你也可以设计一门自己的语言" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ed/d0/ed9d00486f582a72c093adf55b37d7d0.mp3"></audio>
你好!我是郑晔。
在前面,我们花了三讲的篇幅探讨程序设计语言,一方面是为了增进我们对程序设计语言的理解,另一方面,也希望从中学习到软件设计方面做得好的地方。除了借鉴一些语言特性之外,我们还能怎样应用程序语言,来帮我们做设计呢?
讲到程序设计语言模型时,我说过,程序设计语言的发展趋势,就是离计算机本身越来越远,而离要解决的问题越来越近。但通用程序设计语言无论怎样逼近要解决的问题,它都不可能走得离问题特别近,因为通用程序设计语言不可能知道具体的问题是什么。
这给具体的问题留下了一个空间,**如果我们能把设计做到极致,它就能成为一门语言**,填补这个空间。注意,我这里用的并不是比喻,而是真的成为一门语言,一门解决一个特定问题的语言。
<img src="https://static001.geekbang.org/resource/image/fd/c6/fd861a726b8a85d1d22ca56b78515cc6.jpg" alt="">
这种语言就是领域特定语言Domain Specific Language简称 DSL它是一种用于某个特定领域的程序设计语言。这种特定于某个领域是相对于通用语言而言的通用语言可以横跨各个领域我们熟悉的大多数程序设计语言都是通用语言。
我在[第8讲](https://time.geekbang.org/column/article/245868)说过它们都是图灵完备的但DSL不必做到图灵完备它只要做到满足特定领域的业务需求就足以缩短问题和解决方案之间的距离降低理解的门槛。
虽然大多数程序员并不会真正地实现一个通用程序设计语言但实现一个DSL我们还是有机会的。这一讲我们就来谈谈DSL看看我们可以怎样设计自己的语言。
## 领域特定语言
不过一说起设计一门语言很多人直觉上会有畏惧心理。但实际上你可能已经在各种场合接触过一些不同的DSL了。程序员最熟悉的一种DSL就是正则表达式了没错也许已经习惯使用正则表达式的你都不知道但它确实就是一种DSL一种用于文本处理这个特定领域的DSL。
如果你觉得正则表达式有点复杂还有一种更简单的DSL就是配置文件。你可能真的不把配置文件当作一种DSL但它确实是在实现某个特定领域的需求而且可以根据你的需求对软件的行为进行定制。
一个典型的例子是Ngnix。无论你是用它单独做Web服务器也好做反向代理也罢抑或是做负载均衡只要通过Ngnix的配置文件你都能实现。配合OpenResty你甚至可以完成一些业务功能。
这么一说你是不是觉得DSL的门槛不像听上去那么高了。
经过前面几讲的学习你应该知道了语法只是一种接口。很多人说到设计DSL脑子里实际想的也只是设计一种语法。所以从软件设计的角度看DSL最终呈现出来的语法只是一种接口但最重要的是它包裹的模型。
Martin Fowler在他的《领域特定语言》这本书中将这个模型称为语义模型Semantic Model。不过在我看来Martin Fowler起这个名字是站在语言开发的角度毕竟语义这个词只有学过编译原理的人才好理解。所以这里真正的重点是模型。
想要实现一个DSL可以这么说DSL的语法本身都是次要的模型才是第一位的。当你有了模型之后所谓的构建DSL就相当于设计一个接口将模型的能力暴露出来。
当把DSL理解成接口我们接受DSL的心理负担就小了很多。你可以想一想它和你熟悉的REST API其实没有什么本质的不同。
既然是接口形式就可以有很多种我们经常能接触到的DSL主要有两种外部DSL和内部 DSL。Martin Fowler在他的书中还提到了语言工作台Language Workbench不过这种做法在实际工作中用到的不多我们暂且忽略。
外部DSL和内部DSL的区别就在于DSL采用的是不是宿主语言Host Language。你可以这么理解假设你的模型主要是用Java写的如果DSL用的就是Java语言它就是内部DSL如果DSL用的不是Java比如你自己设计了一种语法那它就是外部DSL。
把概念说清楚了一些问题便迎刃而解了。这也可以解释为什么DSL让有些人畏惧了原因就是说起 DSL这些人想到的就是自己设计语法的外部的 DSL。其实即便是外部DSL也不一定要设计一门语法我们甚至可以借助已有的语法来完成。比如很多程序员熟悉的一种语法XML。
如果你是一个Java程序员XML就再熟悉不过了。从Ant到Maven从Servlet到Spring曾经的XML几乎是无处不在的。如果你有兴趣可以去找一些使用Ant做构建工具的项目项目规模稍微大一点其XML配置文件的复杂程度就不亚于普通的源代码。
因为它本质上就是一种用于构建领域的DSL只不过它的语法是XML而已。正是因为这种DSL越来越复杂后来一种新的趋势渐渐兴起就是用全功能语言也就是真正的程序设计语言做DSL这是后来像Gradle这种构建工具逐渐流行的原因它们只是用内部DSL替换了外部DSL。
从复杂度而言自己设计一种外部DSL语法大于利用一种现有语法做外部DSL二者之间的差别在于谁来开发解析器。而外部DSL的复杂度要大于内部DSL因为内部DSL连解析的过程都省略了。从实用性的角度更好地挖掘内部DSL的潜力对我们的实际工作助益更多。
## 代码的表达性
你或许会有一个疑问内部DSL听上去就是一个程序库啊你这个理解是没错的。我们前面说过语言设计就是程序库设计程序库设计就是语言设计。当一个程序库只能用在某个特定领域时它就是一个内部DSL这个内部DSL的语法就是这个程序库的用法。
我先用一个例子让你感受一下内部DSL它来自Martin Fowler的《领域特定语言》。我们要创建一个Computer的实例如果用普通风格的代码写出来应该是这个样子
```
Processor p = new Processor(2, 2500, Processor.Type.i386); Disk d1 = new Disk(150, Disk.UNKNOWN_SPEED, null);
Disk d2 = new Disk(75, 7200, Disk.Interface.SATA);
return new Computer(p, d1, d2);
```
而用内部 DSL 写出来,则是这种风格:
```
computer()
.processor()
.cores(2)
.speed(2500)
.i386()
.disk()
.size(150)
.disk()
.size(75)
.speed(7200)
.sata()
.end();
```
如果这是一段普通的Java代码我们看到一连串的方法调用一定会说这段代码糟糕至极但在这个场景下和前面的代码相比这段代码省去了好多变量反而是清晰了。这其中的差别在哪里呢
之所以我们会觉得这种一连串的方法调用可以接受一个重要的原因是这段代码并不是在做动作而是在进行声明。做动作是在说明怎么做How而声明的代码则是在说做什么What
二者的抽象级别是不同的,“怎么做”是一种实现,而“做什么”则体现着意图。**将意图与实现分离开来**是内部DSL与普通的程序代码一个重要的区别同样这也是一个好设计的考虑因素。
Martin Fowler在讨论DSL定义时提到了DSL的4个关键元素
- 计算机程序设计语言Computer programming language
- 语言性Language nature
- 受限的表达性Limited expressiveness
- 针对领域Domain focus
其中语言性强调的就是DSL要有连贯的表达能力。也就是说你设计自己的DSL时重点是要体现出意图。抛开是否要实现一个DSL不说的确**程序员在写代码时应该关注代码的表达能力**,而这也恰恰是很多程序员忽略的,同时也是优秀程序员与普通程序员拉开差距的地方。
普通程序员的关注点只在于功能如何实现,而优秀的程序员会懂得将不同层次的代码分离开来,将意图和实现分离开来,而实现可以替换。
说到这里你就不难理解学习内部DSL的价值了退一步说你不一定真的要自己设计一个内部DSL但学会将意图与实现分离开这件事对日常写代码也是有极大价值的。
有了这个意识你就可以很好地理解程序设计语言的一个重要发展趋势声明式编程。现在一些程序设计语言的语法就是为了方便进行声明式编程典型的例子就是Java的Annotation。正是它的出现Spring原来基于XML的外部DSL就逐步转向了今天常用的内部DSL了也就是很多人熟悉的Java Config。
你会发现,虽然我在这说的是写代码,但分离意图和实现其实也是一个重要的设计原则,是的,**想写好代码,一定要懂得设计**。
## 总结时刻
今天我们讨论了领域特定语言这是针对某个特定领域的程序设计语言。DSL在软件开发领域中得到了广泛的应用。要实现一个DSL首先要构建好模型。
常见的DSL主要是外部 DSL和内部DSL。二者的主要区别在于DSL采用的是不是宿主语言。相对于外部 DSL内部DSL的开发成本更低与我们的日常工作结合得更加紧密。
内部DSL体现更多的是表达能力相对于传统的代码编写方法而言这种做法很好地将作者的意图体现了出来。即便我们不去设计一个内部DSL这种写代码的方式也会对我们代码质量的提高大有助益。
关于语言,已经讲了四讲,我们先告一段落。下一讲,我们要来讨论编程范式,也就是做设计的时候,我们可以利用的元素有哪些。
如果今天的内容你只能记住一件事,那请记住:**好的设计要迈向DSL我们可以从编写有表达性的代码起步**。
<img src="https://static001.geekbang.org/resource/image/75/33/7516280b19aacd35a2235e54d14b0833.jpg" alt="">
## 思考题
最后我想请你分享一下你还能举出哪些DSL的例子呢欢迎在留言区分享你的想法。
感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。

View File

@@ -0,0 +1,119 @@
<audio id="audio" title="加餐 | 再八卦几门语言!" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/62/59/62f575fe54ca640e584888569358c959.mp3"></audio>
你好,我是郑晔!
软件设计是一个比较烧脑的话题,对于一些同学来说,学起来还是有一些辛苦的。所以,我准备了这次加餐,让大家在前面高密度地狂奔了一段时间之后,稍微休息一下。
我在第8讲中讲了程序设计语言的发展有同学觉得不过瘾想了解其他语言的发展过程。那好我们就来谈谈几门比较吸引眼球的程序设计语言。
## C#
当年Java开始起势的时候微软还处于自己的巅峰它当然不想错过Java这么有前景的东西。但是微软从来就不会老老实实按照标准做事所以你会看到微软手中的Basic已经很不像Basic了微软的C++也有着自己的扩展。
于是微软也想做出一个自己的JavaJ++就出现了。但是这不是一个正常的Java引发了SUN的不满将微软告上法庭。最终双方庭外和解微软不再祸害JavaJ++停止更新。
但有一点不得不承认微软在Windows上的JVM性能是当时最好的因为操刀J++的是Anders Hejlsberg他是全世界最顶级的程序员。微软为了不与Java开启的受控Managed代码浪潮擦肩而过于是转身又推出了C#和.NET。
C#的初版本简直和Java一模一样一个Java程序员几乎不用培训就可以成为一个C#程序员。所以从语言的角度来说最初的C#并没有对行业做出什么贡献
不过既然有Anders Hejlsberg在背后事情当然不会就这么简单收尾。C#在语言特性上开始一路狂奔一个更强大的C#崭露头角。像Lambda、类型推演这些特性早早就落户C#了
然而C#时运不济它的上升期遇到了微软的下降期。越来越多的公司选择了Java越来越多的程序员拥抱了Java而语言模型上表现优秀的C#则遭遇了冷落
当年Java号称“一个语言多个平台”而.NET则是“一个平台多个语言”。结果呢.NET的“一个平台”并不足以吸引更多的公司和程序员的投入除了微软自己其他在上面开发语言的尝试通常都是浅尝辄止。而JVM虽然目标不是为了多语言但丝毫不妨碍很多人在上面开发新语言比如Groovy、Scala、Clojure等等。
最终,**JVM成了“多个语言多个平台”**。随着微软的逐步开放,.NET也开始迈向了多平台**C#也成了一门跨平台的语言**遗憾的是为时已晚Java 已经成就了一番霸业。
如果出于学习的目的C#绝对是值得一学的程序设计语言毕竟微软在语言设计上还是很有一套的。Java语言的进化是非常缓慢的尤其是SUN的衰退又耽误了很多年。所以从语言特性上来看说C#领先Java十年并不夸张
## JavaScript
JavaScript从诞生之日起就扮演着一个不受待见的角色Brendan Eich发明JavaScript完全是为了应付工作因为他当时供职的Netscape需要让网页上的元素动起来。
雷锋和雷锋塔有什么关系Java和JavaScript有什么关系这是一个经常被人提起的段子但实际上JavaScript和Java真的有关系关系就是蹭热度。当时的Java给世界描绘了一个美好的未来让无数人心潮澎湃JavaScript就想借一下Java的东风。
JavaScript仅仅用10天就设计出来所以在它的实现中包含了各种奇怪的问题。不过它还是体现出了BrendanEich的功底比如JavaScript提供了对各种编程范式的支持。其实他真正想做的是一门函数式编程的语言但向现实妥协的结果就是借助了C风格的语法函数式编程的底子却留在了JavaScript里。
虽然在今天看来在浏览器上JavaScript一枝独秀但当年它也是有竞争对手的那个年代无处不在的微软出手做了一个VBScript。但是如同微软错过了互联网时代一样与Windows结合更加紧密的VBScript也在这场竞争中败下阵来。
当年与JavaScript联系在一起的更多的是像走马灯之类的页面特效。让JavaScript真正第一次得到重视是Ajax这门技术。Ajax的出现让页面的元素可以与远程的服务器进行交互JavaScript开始由一个小玩具变成了一门值得研究的技术前端的表现力得到了大幅度的提升。
但很长一段时间里JavaScript一直都不是一门正式的语言对于很多人来说它只是要做前端时顺便学习的语言。这种现象一直持续到Node.js的诞生。
Node.js其实是一个集成商它之所以能有良好的表现要归功于V8这个JavaScript引擎。而V8的出现则要归因于Google对于网络应用前景的格局。
想当年的浏览器大战Netscape和IE拼得你死我活最终IE凭借Windows的优势成了赢家Netscape也退出了历史舞台。然而胜利后的微软认为天下已平竟然解散了IE的团队导致了程序员们要在很长时间内忍受IE这个既不标准又慢的浏览器。
这就给后来居上者留下了空间。最有名的两个后来者一个是Netscape的转世Firefox另外一个就是Google出品的Chrome。
**Chrome认为未来的页面一定要有更强的表现力所以一个高效强大的浏览器是必需的**。既然慢是个大问题Chrome就着力解决慢这个问题甚至不惜开发了一个新的JavaScript引擎也就是V8它的重点就是解决JavaScript执行慢的问题。可等微软看懂Google的操作幡然悔悟重新投入浏览器的开发之时大势已去。Chrome成了新的霸主。
Chrome有一点做得很好V8一开始就是一个独立的JavaScript引擎。所以Node.js才可以很方便地把它借鉴过去。除了V8的性能优势Node.js还引入了异步IO的模型这刚好与JavaScript事件驱动的特点相吻合。
Node.js刚一登场便赢得了满堂喝彩。因为人们认识到JavaScript原来不只能在浏览器中运行也可以跑在服务器端。很快**NPM这个包管理器登场降低了众多开发者参与的门槛JavaScript迎来了属于自己的爆发**,各种各样的程序库让人眼花缭乱。
前端开发也由少数人的爱好成为了一个专属的职位像React、Angular、Vue等框架的出现更是让前端开发有了工程的味道而不再是小打小闹了。
一旦JavaScript突破了浏览器的限制给人们的想象空间就大了许多。除了服务器端有人想把JavaScript用在嵌入式开发中有人想把它用在手机开发中。**JavaScript成了一门全平台覆盖的语言**,大有一统天下的架势。
不过JavaScript作为一门语言其问题之多也是由来已久的。虽然JavaScript本身也在不断进化但沉重的历史包袱让很多人都想开发出新的语言去替代它。所以在JavaScript社区中很多人把它看成了一种Web上的汇编语言把新的语言编译成JavaScript这样就可以在浏览器上运行了。从早先的CoffeeScript到现在的TypeScript甚至新一代的JavaScript标准都是以这种方式进行开发的。
当然还有人有更高的追求他们认为仅仅在语言层面屏蔽JavaScript是不够的。**WebAssembly就是想成为Web上真正的汇编**真正取JavaScript而代之事实上它也得到了很多人的支持。不过这种努力至今仍在继续中还有很长的路要走。
JavaScript就是这样从一出生就不受待见到今天很多人仍想把它干掉。但这并不妨碍它在软件开发的历史中写下浓墨重彩的一笔。
## Go和Rust
在系统编程方面C语言是当之无愧的霸主然而C语言已经快50岁了。在计算机这个快速变化的行业里50年长得令人发指。在这50年中C从被人质疑发展到如日中天再到应用开发的地位逐步被取代。如今它只在系统编程有着无可替代的作用。事实上人们也一直想着替代它。
C的强项是对于计算机模型的适度抽象弱项却是在程序的组织上。因为在C诞生那个年代程序的规模还不算太大。然而C的成功却让程序的规模越来越大大到超出了C语言的能力范畴。于是有人想着把面向对象加到C语言里扩大程序的组织规模。这方面的尝试我们都熟悉的是C++。
不过C++只风光了一段时间就被Java盖了过去。C++本身有一段时间变成了语言特性的试验田,泛型编程,尤其是模板元编程的出现,一度让人怀疑人生。它成了高手极度喜爱,普通人一脸懵硬着头皮写的程序语言。
但更重要的是C++背负了C语言所有的历史负担。所以很多C的问题在C++里面依然存在比如内存管理。虽然C++有各种补丁方案但你必须对C++极其了解才能写好C++,然而,这个要求对于一个工程化的语言来说,实在是太高了。
所以无论是C还是C++,都是在执行性能上无可挑剔,在代码编写上一地鸡毛,人们还是需要一门更有开发效率的系统编程语言。
时间来到新千年又有人出手想代替C语言这回出手的人物背景强大他就是Ken ThompsonC语言的亲爹。2009年如日中天的Google推出了Go语言再加上Ken Thompson和Rob Pike这样早期的Unix先驱站在它背后Go语言的前景给人无限的遐想。
Go语言的语法设计是简单的基本上你花一个晚上就可以把Go语言完整地学习一遍。**它在接口设计和并发上的处理方式都给人眼前一亮的感觉**。人们热切地期盼着它成为下一个系统编程语言的霸主。
但事实并没有像人们想象地那样发生除了初生之时引起了一片欢呼Go语言很长一段时间都在低位徘徊。比较有趣的是中国有很多开发者对于Go的喜爱程度极高一度让Go语言在中国的热度远远超过了全球的平均水平。之所以Go没有很快赢得人们的关注因为它关注的系统编程领域并没有太多的机会留给它人们嘴上喊着热爱手里还依然用C写着代码。
不过机会总是留给做好准备的人语言也不例外。随着Docker这套虚拟化软件登上历史舞台Go语言终于有了用武之地。人们开始意识到**原来云计算领域还有一些基础设施要写用C的话不好维护用Java的话浪费资源Go恰如其分地解决了大部分问题**。
一批新生代的基础设施纷纷出炉除了Docker之外还有帮助人们实现容器部署的Kubernetes也就是k8s还有辅助Service Mesh的istio等等。
虽然在云计算基础设施中Go赢得了一席之地这属于开辟了一片蓝海。在传统系统编程的红海中Go语言其实并没有做出什么特别的成绩对于实时性和性能要求极高的领域Go语言有一个拿不出手的弱项也就是它的GC。
自动的内存管理固然是简化程序员工作的一项重要手段但对于系统编程这个领域而言GC显然还没有表现得能够赢得大家的信任而且在可见的未来也不会有明显的起色。
所以在系统编程领域替代C的征程上大家都还有机会。这条赛道上目前最有力的竞争者是Rust。
Rust出自Mozilla这是浏览器Firefox背后的公司它原本是Mozilla员工Graydon Hoare的个人项目后来得到了公司赞助由一个练手的项目成为了一个正式的项目。
Rust对初学者并不友好对于习惯“少废话、先动手”的程序员而言Rust的初体验可能一点都不好按照习惯方式写出来的代码很可能是无法编译的。比如Rust的“变”量缺省是不变的再有想写好Rust程序先要了解所有权的概念。不过也恰恰是因为这些限制让Rust写出来的程序犯下低级错误的概率大大降低了。
**如果你理解系统编程面临的问题以及现代软件开发的趋势你会发现Rust提供的选项很好地规避了许多问题**。比如,之所以要用不变性,是因为它可以规避掉很多因为“变”带来的问题,这是函数式编程给软件开发贡献的一个重要思路。再比如,所有权的概念也是为了防止一块内存不同的人去改,造成各种问题,同时,也给内存管理提供了新的思路。
内存不能让程序员管这已经成了共识但主流的GC方案又不能满足系统编程的需要Rust则给出了第三种方案把内存当作一种资源申请下来就初始化好出了生命周期就销毁掉。之所以能够做到这点还是要拜Rust强大的编译器所赐因为所有权的存在编译器可以很好地分析出内存到底该什么时候释放。
Rust成为系统编程语言的有力竞争者还有一个原因它背靠着LLVM。LLVM是一套编译器的基础设施它的出现是因为传统的工具链GCC太过沉重。LLVM把编译器的前端和后端分离开来语言开发者只要关注前端设计好各种语言特性就可以利用LLVM的后端进步的优势比如不断优化带来的性能提升。对系统编程语言来说一个重点就是可移植性。
系统编程一个重要的战场就是各种嵌入式设备而绝大多数设备都只支持C/C++语言。一个重要的原因就是谁来移植编译器C/C++的后端常常是厂商提供支持的而其他语言则多半无人理睬。现在有了LLVM的基础设施一个芯片厂商只要支持了LLVM的后端用LLVM前端开发出的语言也就都得到了支持。这对于新兴语言来说绝对是一个巨大的好消息。
Rust在语言层面表现出来的安全特性帮它赢得了像微软、亚马逊这样大厂的注意占用资源少的内存管理方式让一些人开始尝试使用它编写Linux驱动更多的移植可能也让它成为了嵌入式开发的一种考虑。在这场C语言替代者的竞争中Rust值得期待
## 总结时刻
今天的内容主要是为了让大家放松一下,所以,我们也不做内容上的总结了。
每个程序员除了学习当下要用到的知识之外,一般都会对自己的未来做一些技术储备,其中,判断技术趋势就是我们在投资未来时的一个重要参考。
如何才能更好地判断未来技术发展趋势呢?就是去知道一些技术的发展历史。
## 思考题
最后,我想请你分享一下,你看好哪门语言未来的发展?为什么?欢迎在留言区分享你的想法。
感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。