01:开篇--大道至简,给所有人看的编程书
很早就想写一本关于编程的书。我之前写过几本书,专业性比较强,受众比较窄。这本书,我希望能覆盖大部分的读者。
本书基于以下几点考虑:
理清基本的概念,洞悉编程本质。
科普,但又有一定深度。
覆盖面广,求精务实。
通过真正的代码和示例帮助读者理解,而不是只讲概念和理论。- 对于开发者,本书希望成为读者编程路上的催化剂,不代替任何一本编程书。
帮助不需要编程的读者真正理解计算机和程序是怎样工作的,降维打击。
大道至简,无招胜有招。
缘起
大约十五年前吧,我加入一个Ruby on Rails(简称RoR)团队。Ruby是一门优秀的开发语言,它的动态特性可以让程序员从逻辑上思考问题并高效实现,尤其是Ruby on Rails Web开发框架的出现更让它如虎添翼。但在团队的开发和沟通协作中,我发现很多同事对HTTP协议和SQL其实不是很了解。虽然这不妨碍他们开发出优秀的应用,但是缺少对基本的HTTP协议和关系型数据库底层的理解限制了很多人能开发出更有效率的应用,而像RoR之类的开发框架因为屏蔽了太多的底层细节,导致大家也很难接触到底层的技术。好在我是从底层操作系统和C语言一路学起来的,在团队中跟大家也算是很好的互补。
后来,我独立创业,招过好多程序员(从刚毕业的大学生到工作几年甚至十几年的程序员都有),也做为培训讲师给行业里的很多程序员做过培训(有很多学员也来自一线的互联网大厂、电信运营商等),同样发现很多问题——对底层知识如基本的HTTP协议和数据表示(且不说二进制数据表示,有些人对如CSV、JSON这样的基本数据表示都是一知半解)掌握的不扎实而导致他们开发效率以及开发出来的程序效率低下。
我也培养过很多“新人”,他们在学校时成绩不错,但缺少对现代五花八门的框架和工具的了解以及真实的工程实践经验。经过指导,他们可以很快上手,并能跟上项目开发进度,且以后发展也不错。有些人比较“懵懂”,对学校里学的知识不知道怎么用,甚至会怀疑学校里教的东西是否真正有用。我总是耐心地教育他们,学校里最重要的任务是学好基础知识,并学会如何学习。这样,到了工作岗位上以后,就可以快速学习新的知识,并融汇贯通,达到“理论联系实际”的效果。实际上,一旦一个人打通编程开发的仁督二脉,“豁然开朗”以后,后面的学习和进步就会非常快。
如何让没有编程开发经验的人快速学会开发?如何让有基础知识的人“豁然开朗”?如何让有多年开发经验的人重新审视自己过去的漫漫长路和自己的不足达到“顿悟”从而开始开挂的编程人生?解决这三个问题便是本书写作的初衷。“大道至简”、“万变不离其宗”,掌握好基础知识、抓住事物的本质、以不变应万变,便是本书写作的基本指导思想。
我是一名80后程序员,上大学才开始学习计算机,最早接触的是MS-DOS操作系统和C语言,后来又接触过汇编语言、Basic、Pascal、Java、Foxbase、Foxpro、PowerBuild等各种语言(如果算是语言的话),但由于很少用,基本上都忘记了,现在主用的语言是C和Go,以及一些常用的脚本语言Lua、Ruby、Javascript等。
一个经常被问起的问题是:我想学习编程开发,先学哪一门语言好呢?知乎上正好有这么一个问题,也有很多答案。
对于编程语言,仁者见仁,智者见智。其实,几乎每一种编程语言都能解决你的问题,但没有任何一种语言最适合解决所有问题。这里所说的「最适合」,我们可以用反证法:如果有一种编程语言可以有效地解决所有问题,那么,世界上就不会有这么多编程语言。
「PHP是最好的编程语言。」如果你是一名程序员的话,或者即将成为一名程序员,那么你肯定对这句话不陌生。是的,你也能从知乎上找到相关的问答:https://www.zhihu.com/question/26498147 。
那么,究竟有没有最好的语言呢?或者,更现实一点说,掌握哪一门语言对我最有利呢?我想,这个问题还是得你自己回答,即使你现在不能回答,那么,也希望在看完本书之后,能给自己一个答案。
在本课程中,我不会按部就班地讲历史,讲理论,仅就我自己的亲身经历,给大家说一说我对这些语言的理解,以及我认为对程序员有用或者说比较重要的东西,希望对读者有所启发。本书将涉及很多语言,但还是会以C语言为主。因为选一门语言太难了。C语言也许不是你学的第一门语言,但我建议任何程序员都能熟悉一下C语言,因为,他是一切语言的基础(当然,除了汇编)。
本课程不是一本编程入门课,更不是什么武林秘笈。但我希望能写得比较科普实用一些。我之前曾写过一本书——《FreeSWITCH权威指南》,当时,完全是按照「白居易写了诗先念给老婆婆听」的标准来的,奈何好多朋友还是告诉我看不懂。这次,希望能更浅显一些。
本课程的目标是你不需要记住什么,甚至,希望你读了之后就忘记本书的内容,而不要被作者的肤浅误导。但是,如果你在阅读本书后发现有些东西已经深深的印到你的脑子里,那些东西便应该是属于你的,你不需要相信它,但你可以拿它去印证你从别的书或网站上学到的计算机知识。总之,一个优秀的程序员首先是一个会独立思考的人,不是人云亦云。总之,成为一名优秀的程序员需要阅读很多的书,如果本书能启发一些你的思考,那目的就达到了。
本课程也会列出一些作者认为有用的参考书,但是,学完本课程,你应该能更有效的选择自己的书单。
罗塞塔石碑
如同古代中国有象形文字一样,与中国同为四大文明古国之一的埃及也有自己的象形文字。古埃及文字神秘复杂,只有长期沉迷其中的祭司阶层才能读懂,他们称之为“神的文字”,最终随着古埃及的没落而消亡,世界上再没有人能读懂。
后来,拿破仑入侵埃及时被发现有一块破碎的古埃及石碑上面刻有三种语言:除了古希腊文,还有古埃及象形文字和拼音文字(通俗体文字),再后来法国语言学家商博良通过古希腊文字对应破解了古埃及文字,从那以后古埃及的历史都能看懂了。
石碑是在埃及的港湾城市罗赛塔(今称拉希德,即Rashid)发现的,因而得名罗赛塔石碑(Rosetta Stone)。今天,几乎所有翻译软件都叫罗赛塔。
罗赛塔石碑高1.14米,宽0.73米,制作于公元前196年,刻有古埃及国王托勒密五世登基的诏书。石碑上用三种文字刻了同样的内容,这使得考古学家得以有机会对照各语言版本的内容,解读出已经失传千余年的埃及象形文之意义与结构,成为今日研究古埃及历史的重要里程碑。罗赛塔在英法两国的战争之中辗转到英国手中,自1802年起保存于大英博物馆中并公开展示。
至于为什么石碑上会有三种文字,这里有一段历史。当年,马其顿王国的亚历山大大帝征服了埃及,亚历山大大帝非常勇猛,他当时甚至征服了波斯和印度的一部分。曾经,古希腊的人们普遍认为马其顿人是蛮族,但由于希波战争国力被削弱,后被马其顿腓力二世与当时18岁的儿子亚历山大于喀罗尼亚战役后逐渐被征服,沦为马其顿的附庸。
而勇猛的亚历山大大帝年少时的老师,正是那位非常有存在感的古希腊学者亚里士多德。亚历山大受到希腊文化的影响非常深,以至于他在东征的过程中不断发扬希腊文化,到最后,连古希腊人都以这位马其顿君王为荣。
可惜的是,英勇的亚历山大不满33岁就死于疾病了,他死后,手下的三名将领瓜分了他的江山,其中一名,就是托勒密,他占有了埃及的领土,建立了著名的托勒密王朝(Ptolemaic Dynasty,这个王朝最著名的君王莫过于埃及艳后克利奥帕特拉七世了吧)。这次事件,开启了历史上的“希腊化时代”。
在当时,托勒密五世登基后,类似这种国王诏书级别的文档,就要求注上同样意思的希腊语版本了,因而有了罗赛塔石碑。
这里我们提到罗赛塔石碑,不是为了讲故事,而是想通过它解释对照学习的重要性。我们后面会使用不同的语言编写同一个程序,以便大家可以对照学习。
从我说起
我是从大学开始学习计算机的,当时主流的操作系统是MS-DOS,从3.1到6.22。最初就是自学了一点Basic和C语言,课上有Foxbase。
Foxbase其实是是一个关系型数据库系统,在DOS时代还很流行,留在记忆深处的就是`jbgz`(基本工资)之类的字段名,也能通过编程做好多数据处理的事情。当时的编程环境都是字符界面,因此,如果显示好看的软件封面或者大字体之类的,就只能借助UCDOS(DOS下的中文系统)中的「特殊显示」功能实现了,Foxbase也可以调用特殊显示。
Windows 3.2和Win95出现以后,学了一阵Foxpro。Foxpro有了图形界面,能编写更强大的程序了。
在C语言课还没开的时候,我就从图书馆借了书自学C语言,然后利用暑假照着书上的程序跑了一遍,后来基本是去图书馆借书抄程序。意识到「数据结构+算法=程序」,自己借了书学习了数据结构,看过Pascal和C语言版的,后来又看了一本Java版的《数据结构:Java语言描述》。值得一提的是,Java版的那本书非常经典,而我对Java语言的理解基本都是从那本书里学的。
后来,听说PowerBuilder比Foxpro厉害,又学了一阵PowerBuilder,但也仅限于学习,没写过什么真正的程序。Foxbase、Foxpro、PowerBuilder之类的语言算是数据库驱动的语言,它们曾经也有很广泛的应用,只是随着C-S架构式微,除了一些特殊的行业应用,现在已经见不到它们的身影了。
在图书馆看过Basic,基本上只是看书,没写过程序。有一阵总是去图书馆看C++的书,知道了面向对象、类、继承等。
在同学们都去学Visual C++的时候,我学了C++ Builder。C++ Builder是Delphi的C++版。Delphi当时很流行,甚至比微软的Visual Basic强。Delphi用的是Pascal语言,Visual Basic当然是使用Basic语言。在Windows时代,他们都可以把界面和代码分离,基本上是建一个窗体(Form),拖入很多控件(Control,按钮,文本框等),然后就可以在代码里设置`onClick`等事件处理代码(当鼠标点击时就触发执行一段函数代码),开发图形界面的程序非常方便。而相对的Visual C++虽然有「Visual」(可视化)一词,却不能像Visual Basic那样拖拽控件。
毕业设计我做了两个:首先是按导师的要求开发了几个C++ Builder控件,可以方便用它们做电压、电流表之类的仿真(最开始我的专业是应用物理)。做完了觉得没什么意思就用C++ Builder把《全国毕业生分配决策与支持系统》重写了一遍。当时我在学生处毕分办帮老师维护这套系统,该系统DOS版本是使用C语言开发的,后来的Windows版本使用了Foxpro开发,但是Bug很多(商业软件都是不开源的)。我重写的时候第一次使用了Paradox数据库,这是个很奇怪的数据库(C++ Build里自带的),但是支持SQL,在这个项目里我第一次学习了SQL(当时大家还基本停留在Foxbase和微软的Access上),在当时SQL算是很先进的东西了。
在上课的过程中我还学习过Intel 8051系列的单版机汇编语言,算是对汇编语言有所了解吧。其实只不过是写一些控制七段数据管之类(最早的电子表或计算器上显示的每个数字都是类似7个小段)的小功能,想想求伯君直接用汇编写个WPS出来实在无法想象(那时候就是心中的神吧)。
一次偶然的机会知道了Linux,从此就入了迷,但在Linux我却没有做什么编程开发,还是花了很多时间搞定中文的显示问题,折腾X Window和显卡驱动等。顺便学会了很多Linux命令。
大学毕业后我进入电信公司工作,工作的关系我可以接触到很多计算机和服务器,甚至有很多古董的DOS机器,我的主要工作是程控交换机的运维,业余时间就用C语言写程序跟交换机通信,处理交换机的各种日志等。由此接触了串行口、并行口通信等,因为当时的DOS机器上根本没有以太网卡。
后来我的编程水平得到大家认可的时候(因为别人不写程序,会写程序的我就成了神),我就把我能力范围内能换成Linux的电脑全换成了Linux。Linux支持网络、支持多用户、多任务。我原先维护的一套公司自己人员用Foxpro开发的「交接班管理系统」后来也用PHP+PostgreSQL重写了,并跑在Linux上,当时用的操作系统好像是Debian 3.x的版本。
为了写这套系统,我把PHP4.x和PostgreSQL 7.4~8.3的手册看了很多遍。
写程序的过程中我基本上在Windows上使用Editplus和在Linux上使用Vim编辑器。出于兴趣,我尝试过很多Linux发行版,如Redhat、Debian、Ubuntu、Hiweed、Mandrake、BluePoint、Turbo Linux等,一直都是折腾怎么更好地支持简体中文以及支持一些Windows应用程序如QQ等。我也尝试编译过Linux From Scratch[^lfs],这是一个从源码一步一步编译成一个自己的Linux版本的项目,学到了很多,但也花了好多时间,主要是那时候的电脑是奔腾586或奔腾3,实在是太慢。在工作中我也接触过好多UNIX,如SCO UNIX、Tru64 UNIX、Sun OS、IBM AIX等,以及Oracle、Sybase等商业数据库,也写了好多存储过程(当时很多数据处理的业务逻辑大多用存储过程实现,如计算电话费等)。
[^lfs]: www.linuxfromscratch...
在折腾各种UNIX的同时,我办公电脑的操作系统也渐渐从Win98变成Window Me、Windows 2000、Windows NT以及Vista,那时候还没有Win7。
有一次出差逛到北京第三极书店,本来想找一本关于Perl的书(Perl这门语言还是很神秘的,一直没机会学),却碰到一本《Programming Ruby》,就买了下来开始学习Ruby,后来发现,我一直在用的CakePHP Web开发框架在Ruby上更好用,Ruby语言的Web框架叫Ruby on Rails,缩写为RoR。
有一段时间我喜欢看美剧,闲着没事也尝试翻译了好多技术文档,包括一些SQLite的文档。英语还是非常重要的,因为作为程序员比较头痛的一件事就是起变量名(还记得我前面说的`jbgz`吗?)。
后来我离开了「铁饭碗」,去北京加入了一家创业公司,做系统管理员。团队中大部分是程序员,我基本上是负责部署运维,闲着没事就给大家修Bug。我们是一个RoR团队,前端使用HTML(当时还没有HTML5)和Flash(那时候Flash还很火),使用Flex开发。Flex使用Action Script,是EMCA的一个子集,跟Javascript类似。后来我们团队中又引入了Erlang语言做实时呼叫控制。我作为系统管理员,是唯一一个对所有语言熟悉的人,甚至还给Erlang语言源代码提交过一个补丁。
是的,开发是无止境的,当你用得越多,越深入,你会遇到语言的Bug、编译器的Bug、以及Linux内核的Bug等(读到这里你是不是已经准备放弃了?)。
我们团队使用的通信软件Asterisk是使用C语言开发的,后来很快换成FreeSWITCH。FreeSWITCH是一个电话软交换系统,我们用它在支撑老师和学生在线实时通信,我们当时是一个网络在线教学平台。
由于我有电信背景,我很快熟悉了FreeSWITCH并给FreeSWITCH汇报Bug、提交一些补丁等。随着跟FreeSWITCH开源社区更加熟悉,我建立了FreeSWITCH中文社区[^fscn],没想到从那以后我就离不开FreeSWITCH,至今已有十多年了。
[^fscn]: www.freeswitch.org.c...
我创建了公司第一个iOS程序,后来由其它同事继续开发并上线。当时我没有学习Objective-C,而是使用了一个跨平台的开发框架Titanium,使用Javascript开发并编译成iPhone的应用程序,当时我还没有iPhone,完全用模拟器,后来买了一个国行版的iPhone 3GS,还没有Wi-Fi功能。Titanium本身是跨平台的,也可以编译出安卓和App,但当时国内安卓手机还比较少。
在Go语言出来后,第一时间读了全部的文档,但后来没有真正写项目,因为我们已经使用了Erlang,而那时候Go的垃圾回收功能会影响程序的实时性、生态也还不成熟。我的C语言水平基本是在折腾FreeSWITCH的过程中逐渐提高的。FreeSWITCH是一个多线程的程序,以前虽然也写过多进程以及多线程的程序,但理解不深。随着给FreeSWITCH提交补丁越来越多,我成了一名核心开发者。
后来我自己创业,FreeSWITCH是我们的核心竞争力。Erlang仍然是我们的主力语言,但后来放弃了。Erlang写的程序很优雅但是招一个Erlang程序员实在是太难了,原来国内的Erlang社区甚至都去搞Go语言了。放弃Erlang的另一个原因就是它为了跨平台,使用类似Java虚拟机的方式工作,部署起来比较复杂(那时候还没有Docker),而且做好(做对)大并发其实也挺难的。
我们现在主要的开发语言基本上是C、Go和Lua,在前端就用Javascript。Lua是一个轻量级的脚本语言,跟C配合使用非常完美,我们在FreeSWITCH、Kamailio、PostgreSQL、Nginx中都可以使用Lua写脚本和存储过程。
如果你对我上面列出的各种术语很多没听说过,也没什么关系,因为大部分人都没听过,听说过也没什么用,只要知道一两种大家都知道的就行了。一个真理是:你没听过并不代表你无知,而是,它们本身就没有那么大的生命力,虽然它们在过去的时代曾经有些影响力。
其实不管什么语言,都是相通的。也就是说,只要你熟练掌握一门语言,其它语言都可以很快上手并可以写程序。当然各种语言又有各种语言的优势和自身的「坑」,如果要往深里用,还是得往深里学。
对于大部分人熟悉的Java、Python、和C#等,我却不擅长,只是简单学习过一些基本的语法,写过一些小的Demo之类的。但正如我在上面所说,所有的语言都是相通的,因此,对于一门新语言,快速学习一下语法写个简单的Demo或小程序还是比较轻松的事情,或者也能简单改改别人写的程序。希望可以在本课程中跟大家进一步共同学习。
一道面试题
请听题:用你熟悉的任何编程语言,你会用多少种方法写死循环?写出来。
这是我们公司的一道面试题。看上去是不是很简单?但是,来我们公司面试的,很少有做对的,更别提让我满意的。有的人甚至一种方法都写不对,有的即使写对了,代码结构,缩进也不对,缩进对了,字体写得也不好看。具体的错误真是五花八门,我们就不列举了,列举也没什么意义。亲爱的读者,读到这里,你能先停下来,拿起笔,亲自做一下这道题吗?
是的,这是一道笔试题,不是上机题,如果上机的话,也许能写的好点。注意,题目非常宽松,并没有限定编程语言。你能想象到一个面试程序员的求职者做不好这道题吗?
下面,我们从C语言开始,我给几个答案:
```c
while(1);
```
```c
for(;;);
```
```c
while (1) {
}
```
```c
for (;;) {
}
```
```c
do {} while (1);
```
```c
loop: goto loop;
```
其实如果能写出上述任何一种,这道题都基本可以得满分。如果能写出两种以上,就能胜过很多求职者了。当然,严谨的程序员会非常注意代码格式(如适当的缩进)以及会注明自己使用的是什么语言。
有的程序员写得多一点,反而出了Bug,下面的代码你能看出问题吗?
```c
int i = 0;
for (i = 0; i >= 0; i++) {
}
```
`int`是整型的,整数一直加下去就会溢出,溢出后`i`变为负数,循环结束。所谓“言多必失”,大抵如此。从这个例子也可以看出,能把代码写得长,并不一定比写得短的懂得多。当然这个例子如果上机测试的话是肯定可以发现的,但是,能空手写代码和需要上机验证的程序员肯定不在一个层次上。
还有的程序员会写上完整的程序,
如下:
```c
int main(int argc, char argv[])
{
for(;;);
}
```
不过,写不写`main`函数不是重点。只要能写出死循环的语句就算切题。所以做题之前首先就要读懂题目的意思,这一点很重要,后面我们也会反复讲。
好了,下面,我们再看看其它语言的程序是怎么写死循环的。
```bash
while :; do echo; done
```
```lua
while true do
end
```
```ruby
while true
end
```
```python
while True:
pass
```
```java
while (true) {}
```
```erlang
while true do
end.
```
```csharp
while (true) {}
```
```basic
1 LOOP
2 GOTO LOOP
```
```c++
while(true){}
```
```perl
while (1) {
}
```
```php
for (;;) {}
```
```javascript
while (true) {}
```
上面就是各种编程语言的罗塞塔石碑。通过对比上面的各种语言可以看出——其实大部分语言都是相通的,语法也是类似的,只要学会了其中一种(如C,是要真会,而不是一知半解),再学习其他语言就很快了(所谓举一反三)。
本课程不能代替任何编程课,本书也不能代替任何编程书。本课程不会直接教你如何编程以及如何成为一名开发高手。本书只会带你一起学习或重温各种基础知识,甚至从基本的1、2、3讲起,跟你一起思考,一起探讨各种数、编程语言、网络,以及相关的各种技术。为什么有这么多不同的语言和技术?它们出现的背景是什么?它们是解决什么问题的,它们是如何有效地解决问题的?从而带你一步一步发现和理解各种技术的本质,让你用起来得心应手。当然,本书涉及内容很广,很多内容无法深入探讨,希望读者可以通过本书的内容,知到去哪里找答案,如何找答案,从而能够深入学习、练习和研究,成为一名优秀的程序员。
对于资深有经验的程序员,可能觉得这些内容有些「小儿科」。不妨思考一下,如果一个刚毕业的程序员问你一个问题,或者不会编程的老板问你一个技术问题,你如何“抓住本质”并用他们能理解的语言和比喻给他们讲清楚。对于这些问题,本书中也没有直接的答案,但或许对你有启发。
「大道至简」——很多复杂的问题往往可以用很简单的语言描述清楚。如果实在描述不清楚,那就尝试分成几个问题再描述,所谓“分而治之”。
你准备好进一步学习了吗?
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!