https://liujiacai.net/blog/2015/10/21/programming-guide/
来我们这个实验室里读研的学生可能自从来到这里的第一天就觉得自己的命运很苦逼。他们读本科时主修的是机械设计、制造以及自动化之类的专业,毕业时的简历上也顶多是写写擅长 MS Word、PowerPoint、UGNX、AutoCAD 之类的应用软件。他们有限的学习生涯里,怎么也不会想到来到这里竟然要首先重新学习 C 语言,然后还要学 C++,接下来还要学习 Python 或 Lua 什么的,而且竟然还不让用 Windows,只能用连个 QQ 都没有并且经常出故障的 Linux……
在他们心里,编程似乎并不是多么有趣的事,所以他们就觉得编程很难。对此我有同感,大家都会玩的 Windows 里的挖雷与纸牌那样的小游戏,还有很多人会打的麻将,还有围棋,这些事我觉得也挺难的。
我曾经告诉他们,如果不会编程,那么他们就很难解决自己的研究方向上的那些问题,因此也就不可能写出有价值的论文,不可能顺利的毕业拿到学位,不可能找到很好的工作……这种功利性的『威胁』,对于有些人会有点效用,但是如果没有能力让他们自发的对编程产生足够的兴趣,这对于任何一个『好为人师』的人而言都是一种莫名其妙的羞辱。
现在我试着去告诉他们,编程不仅不难,而且会很有趣,其中充满着值得深思的东西,而这些深思对于我们人生也会产生许多增益。于是,就有了这篇文章。
编程是什么
无论你是不是程序猿,每一天你都在编程,每一天你都被编程。编程,就是设计一些步骤,组织这些步骤,让这些步骤在当前环境中正确的运行,最终得出自己想要的结果。
你的每一天都是在起床、喝水、吃饭、工作、上厕所、娱乐、睡觉等步骤的有序组织下运转的,你活在这个程序中,同时你在这个程序中为改善自己的生活而制定各种计划并努力去实现。
机械设计,其实比编程还要编程。所谓的机械零件,就是数据结构。所谓的传动机制,就是应用程序接口(API)。所谓发动机,就是程序的内核。你将零件装配好,通过传动机制将它们接驳到发动机上,于是你就创造出来一部机器,通上电或者打着火,就可以让它运转起来。
编程比机械设计来的更为简单,你不需要经常给自己所编写的程序添加润滑油,也不需要去对每个数据结构进行复杂的力学分析,更不需要关注这些数据结构是否严丝合缝的相互配合,至少目前的计算机软件工程是这个样子的,它不像机械工程学科那样以坚实的物理定律为基础。机械的结构与运行规律总是可计算、可分析的,而软件的结构与运行过程却充满着太多不严格的环节。这种不严格,却给我们营造了一个可以发挥天赋或工科实践经验的空间。显然,即使软件工程存在着各种不严格,但是我们却能够通过编程模拟出机械工程的一切。事实上也是如此,现代的机械工程领域,软件已经无处不在。
机械设计有很多精妙的『算法』,像缝纫机、枪械、发动机之类的机构,设计它们其实要比计算机世界里的算法设计难得多,而且这些机构对人类文明的发展往往能够产生巨大的推动作用。希望你不要因此爱上机械设计……学会编程,你会对机械设计的理解更为深刻。因为编程是将『设计』本身作为一种智力活动而对待的。你可以将机械工程领域的那些智力活动应用于编程,也可以将编程中的智力活动应用到任何设计之中。
[图片上传失败...(image-322edc-1650795958924)]
正如 SICP 一书的序言所言,教育者、将军、营养学家、心理学家以及父母们,他们做规划,而士兵、学生以及某些社群则被规划。克服大型的问题,要经过一系列的规划,其中大部分规划会运作于现实之中,因为这些规划总是与迫切处理的问题息息相关。若将规划这件事情本身作为一项智力活动来欣赏或研究,那么就必须转到计算机编程上面来。你需要阅读与编写计算机程序,而且要大量的做。程序是怎样的,它们的功能是什么,这些不太重要,重要的是它们的性能如何,它们之间能否精巧的相互配合从而构造更大规模的程序。
入门书
学习编程之前,应该先问自己一个问题:我为什么要学习编程?不要打我……我知道你们是被逼着去学习编程的,那就不妨被逼着思考『我为什么要学习编程?』。
如果不知道答案,也没有关系。反正这个问题与学习编程也没有太大关系。其实,我们已经做了非常多的不需要回答为什么的事了。我们连『我们为何而存在』这样的问题都不知道答案,却依然糊里糊涂的活到了现在。
先推荐几本入门书以及阅读它们的方法,因为学习编程最不需要的方法就是将一本讲编程的书从头读到尾……编程不是考试,它是基于现实生活的创造。这种创造是渐进的,你在创造之初可能也无法预料到结果会是如何,这是任何创造性活动的基本属性。
我推荐的第一本书是《计算机程序的构造和解释》,英文名是《Structure and Interpretation of Computer Programs》,简称 SICP。英语阅读能力好的同学,可以看英文版。中文阅读能力好的同学,可以看中文译本,裘宗燕老师的文字素养与翻译的严谨程度是可赞的。这本书的阅读,建议分以下三个阶段:
- 阅读前两章,第一章是讲计算过程的抽象方法,第二章是将基本的数据的抽象方法。这两章的内容涵盖了软件世界的『九年制义务教育』的全部内容,所用的教学语言也是非常成熟且设计精巧的 Scheme 语言的一个很小的子集。学习这两章内容的过程中,可以穿插着阅读《Teach Yourself Scheme in Fixnum Days》的前 10 章,这份 Scheme 教程也有一份中文译本。SICP 的习题,即使不去做,也应该把题目看一下,动脑子想一想,判断一下能不能做得出来。这些习题,在网络上很容易找到答案。
- 复习 C 语言,教材用 Kernighan 与 C 语言之父 Ritchie 合写的那本《C 程序设计语言》即可。这个阶段的设置,主要是面向我们实验室内部。因为我们实验室里的同学在本科阶段通常是要修 C 语言这门课的,但是当时他们可能并未真正从学习编程的角度去学习,现在可以通过第一阶段基于 Scheme 语言建立的编程观念去重新认识一下 C 语言,只有这样方能理解 C 语言的优点与缺点,并且去思考如何充分发挥 C 的优势,然后用 Scheme 来弥补 C 的不足。借助 GNU Guile 2,很容易实现 C 与 Scheme 复合编程。这个过程可以穿插阅读 Kernighan 写的《程序设计实践》。
- 阅读 SICP 的第 3 章,然后再找一本讲 C++ 的书,比如 C++ 世界中非常有名但我不以为然的砖书《C++ Primer》,只学习基于类的数据抽象以及面向对象编程部分即可。这个阶段,SICP 的第三章阐述了面向对象编程与函数式编程两种方法。从 SICP 中获得的面向对象编程,可以在 C++ 的学习中得到进一步一些强化,至于函数式编程方法,可以假装自己已经知悉,留待日后需要时再作打算。最后,记得将《Teach Yourself Scheme in Fixnum Days》剩下的内容看完。
C++ 的入门书,我更推荐《C++ Without Fear》,中文译本叫《好学的 C++》,现在应该是第 2 版。之所以不推荐大家认为是 C++ 四书五经之一的《C++ Primer》,是因为我总觉得它像一本事无巨细的案头手册,比较适合那些已经有了编程经验甚至 C++ 经验的程序猿阅读。我心目中真正好的教材应该像小说那样,由一条或多条逻辑主线延展而成,这种教材对于非科班出身的人尤为重要。
对于大部分编程任务而言,上述书所涉及的知识已经足够用了,而且上面的这几本书也是非常耐读的书,只要你不是那么着急的将它们读完,它们总是很有趣。我很喜欢 SICP 与《程序设计实践》这两本书,因为太喜欢了,所以一直都不舍得把它们读完。
让实践有些难度
书是要看的,但是看书的过程中最好开动你的双手。所以,你不应该停下来问自己:为何要学习编程?
我希望总有一天,你能给自己找到一个答案,那就是你想写一个 XXX 程序。这个程序至少应该对你是有用,亦即它的主要功能不与你的系统里的其他程序存在着重复。如果你能明确这一点,那么你所创造的程序就有了意义,你的学习就有了意义。
为何要学习编程?因为你要创造一些从来没有的软件,而且它能够帮助你做一些你认为是很重要的事!凡是你认为重要的事,对于很多人而言,很有可能也是很重要的,因此你所创造的东西就可以帮助更多的人,这意味着会有一些你可能不认识的人需要你,这就是你的价值所在。
编程的实践,应该将它作为探索未知世界的智力活动,应该从书中跳出来,将自己从那些示例中获得的经验用于解决现实中的问题。如果你觉得,现实中根本不存在什么问题需要你去解决。那么……请你回答一下『你为何而存在』这个问题吧。
看书,是从前人正确的经验中学习。实践,是从自己的失败中学习。既然决定要实践了,所以还是给自己找一些比较难走的路走走看吧,让失败多一些,让失败早一些。
以我个人比较感兴趣的几个东西为例,可以写一个基于 TeX 的现代文学编程语言,通过它不仅可以历练编程能力,也会对编译原理中的语法分析环节有一些实际的认识。也可以尝试去写一个三维几何库,能够完成凸包、Delaunay剖分以及 Voronoi 图等计算,不求大而全,只求小而精,以后漫长的时间里可以慢慢的去改进它。还可以去找一个自己喜欢的开源项目,去阅读它的源代码,了解它所用的项目构建系统,分析项目结构,试着去修改它的代码……我现在最感兴趣的是 LuaTeX。
问题是最重要的
假如你已经有了非常多次的实践上的失败,并且你已经大致掌握了 Scheme 与 C/C++ 这样的语言,那么每年学一门新的语言,这并非难事。可能你会对网络上经常发生的语言之战觉得奇怪。
是问题决定了语言,是问题决定了编程范式,是问题决定了信仰。如果你能很明确的认识到这些,那么你就不会陷入某种语言宗教的泥淖之中。对于许多事都是如此……搞机械的人,也经常信仰 UGNX,CATIA,PROE 这些『宗教』的……
如果非要给自己找一种信仰,那么我信仰我的存在就是为了解决问题的。
如果在我用的 Linux 系统上做一些自动化程度高一些的维护任务,我不会厌憎佶屈聱牙的 Bash 脚本,而是非常欣赏它像胶水一样快速的将几个本来是独立运行的程序连接起来替我完成复杂的任务。
如果我要临时的做一些文本处理工作,我可以用 python 3,因为它对 UTF-8 支持的挺好,而且字符串库功能齐备。如果只是进行一些文本的替换,emacs 或 sed 之类现成的工具也够用了。
如果我要写一个严肃的程序,严肃到了它的生命可能要很久,那我会选择一门成熟稳定的语言来实现它,即使用 C,我也不会烦弃它的繁琐的代码,我会尽力凝练程序中要实现的功能。
人生中本来就面临着许多选择,但是非常多的人在选择之前并未认真的去考察自己面对的问题。
不过,对问题本身的考察,需要一套基本的工具集。没有听诊器、手术刀或 X 光,再厉害的医生也无法分析人体的内部发生了什么。对于编程而言,我认为上述我推荐的书中所涉及的知识已经足够用来洞察软件世界的各种问题了,以此为基础,我们只需再保持心态的开放,随时汲取所需的知识就可以了。例如,虽然上述几本书没有一本是讲 Web 开发的,但是如果你熟悉 Scheme,就可以发现 JavaScript、HTML 5、CSS 3 之类的语言并没有超出 Scheme 的范畴,你可以很快的就掌握它们。
算法
解决问题需要算法。既然编程无处不在,那么算法也是无处不在的。但是,如果随便拿起一本讲算法的书,随便一本,可能都会让你觉得头昏脑胀。也许你会担心,连算法的书都看不懂,还怎么写程序?
当初我刚学习编程的时候,写过二十四点、汉诺塔、八皇后、俄罗斯方块之类的小程序。后来,在现实的项目里,也写过堆排序、快速排序、矩阵的 LU 与 SVD 分解、无向连通图的最小生成树及最短路径之类的程序。但是现在,随便拿一个让我去实现,我还是不得不去翻书看懂算法,然后再去写程序……
我想说的是,如果你正在阅读一本讲算法的书,书里有些算法或它的示例是你一时无法看懂的,可以跳过去。很多专门讲算法的书里,充斥着心智游戏。如果你无法将自己代入到这些游戏的情境中,这个游戏的玩法自然就是不明了的。现在看起来,这是很自然的事,然而当初我却一遍又一遍的怀疑自己的智商,特别是看到网络上很多人像喝白开水一样的谈论着这些心智游戏,我一度怀疑,我不适合做编程方面的事。
幸好,这个世界足够稳定,以至于我们不需要了解相对论与量子力学也能够很好的生活下去。大部分人,连牛顿力学都不需要了解……算法也是这样,特别是现在已经存在了相当多的实现,例如几乎任何一种编程语言的标准库中都提供一维数据的快速排序算法的实现。基本上,只要是对现实中的问题非常重要的算法,你总是能够找到它们的既有实现,取而用之。
当你走在街上,那些高高矗立恢宏建筑,建筑工人建造它们的过程中可曾用了极高心智的技术?编程,本质上也是如此,工程经验的重要性大于心智。甚至在编程中,过多的运用心智,反而会适得其反。
我不是说学习算法没有必要,我只是强调不要被一时难以理解的算法挡住你。你天生就拥有一些无比强大的算法,它们是穷举、贪婪与分治,还有最强大的『演化』与『神经网络』。那些专门讲算法的书,只不过是是了很蹩脚的语言、符号以及示例将你天生的直觉刻画出来而已。只要你在现实中遇到问题,你总是能够找到求解这个问题的方法,而不是只有读懂了某本讲算法的书你才能解决这个问题。
很多算法书,都是我看不懂的。它们的第一章就是让我复习数学归纳法,第二章就是让我学习算法的时间与空间复杂度分析……而我属于对数学缺乏直觉的人,对我而言,这些书的唯一价值就是故意不让我去读它。即使是我心目中的大神 Knuth 的传世之作《计算机编程艺术》,它唯一的目的似乎就是让我觉得我不是搞艺术的。
很久之后,我在学校图书馆闲逛的时候,发现了《如何求解问题:现代启发式方法》这本书,翻了翻,就开始叹息,为什么一开始不知道这本书?
增强对计算机的理解
有时间与精力可以阅读一些专业性强一些的计算机理论的书籍,譬如操作系统原理、编译原理、算法与数据结构之类。看不懂太专业的书,或者没那么多时间和精力,可以看看计算机科学的一些科普著作。有本《通灵芯片》值得一看,薄薄的小册子,三五天的业余时间就可以看完。有本《编码:隐匿在计算机软硬件背后的语言》,算是《通灵芯片》的加强版,也值得一看。有一本《深入理解计算机系统》,以程序员的视角来看计算机的软硬件系统,也是一本很好的书,不过就是要读完它,需要一些耐心与时间,所以没必要一次性看完。也可以继续将 SICP 的第四、五章看完。
虽然你的编程技能不会因为读了这些讲述计算机原理的书而突飞猛进,但是这些书可以让你理解你的程序是在一个什么样的世界里运行的。虽然你不知道自己为何而存在,但是你知道这个程序为何而存在。你不仅知道它为何而存在,还知道它怎样存在,并且也知道怎样让它更好的存在。这样,也就没必要在那些所谓的『XXX 箴言』、『XXX 之道』、『XXX 之禅』的书籍上浪费你有限的生命。
有时间,也可以复习一下《黑客帝国》,它的导演虽然不是程序猿,但胜似程序猿。看完黑客帝国,也可以看看 Steven Levy 写的《黑客:计算机革命的英雄》。从技术层面跃迁到人文层面,也许那时你会对自己的人生有着更为深刻的认识。计算机,是人类为自己创造出来的最好的一面镜子。我们现在没有能力了解自身,但是我们可以制造与发展计算机来逐步了解自己。
思考生命
懂编程就像懂其他任何一门技艺一样,没什么了不起,因为任何技艺都不会比你的生命更复杂,它甚至不如路边已经被你视而不见的野草复杂。生命,本身就是一个非常奇妙的东西,它的诞生即偶然又必然。整个生命体系只要略微有一点点误差,我可能就不会诞生于世。当我确认我已经存在于这个世上了,但是我又不知道我为何而存在。
如果每个人都思考过『我为何而存在』这个问题,那么就意味着对于这个问题的思考,我们不是自发的,而是被迫的。这个问题,自我们诞生以来就像烙印一样存在于每个人的思想里。这个问题是人类的终极问题,我们所解决的一切问题最终都会指向它。我们为这个问题而生。不仅仅是为了自己去回答这个问题而生,还要为他人回答这个问题而创造生存条件。他人,包括我们的家人,也包括我们的朋友以及那些我们并不不认识的人。
我们努力赚钱,是为了过更好的生活么?似乎并非如此,生活条件的富足,似乎仅仅是为了我们去思考『我们为何而存在』而创造一个更好的环境。即使是世界上最穷的人,也会自问,我为何而存在,上苍为何如此待我之类的问题。在这个问题面前,富人与穷人是绝对平等的。我们看见美女,可能会怦然心动,追而娶之,是为了一段美好的爱情么?也许美色是一种诱惑,用于保证人类能够继续繁衍生息,以便继续思考那个终极问题;即使是出于爱情,也极有可能是因为你爱的人对于你回答『我为何而存在』这个问题具有增益作用。
一切的生物,皆为命而生,这就是生命。生,是一种可变并且可自我繁衍的状态。只要有一种事物,它的状态是可变的,而且这种状态在保持自身变化的过程中能够产生新的可变且可繁衍的状态,那么它就有『生』。那么命是什么?汉字的『命』,字形上有点儿『一个人一生都在叩问』的意思……我们在叩问什么,或许正是『我为何而存在』这个问题。
苏格拉底说他的存在是为了『认识自己』,但这似乎不是答案,只是一个过程。在『泛型与闭包』,我之所以感慨『每一个人,都像是一个闭包』,是觉得如果我们是程序里的一个函数,我们自身似乎永远也无法知道自己的运行结果是什么。这种想法有点宿命论的意味,但是每个人的诞生又充满着偶然,我们的生存活动似乎并不受创造者的影响,或者创造者无意于去干扰我们的活动,因此在生活中我们经常觉得自己有自由意志。
如果我们即是被创造的,又有一定的自由意志,那么苏格拉底或许就是对的,创造者试图创造我们的目的就是让我们去『认识自己』。我们虽然并不知道我们的生命终止之时会产生什么样的『计算结果』,但是兴许创造者可以理解这些结果。同时,我们有积极生活的自由,也有消极颓废的自由,我们可以将认识自己作为终极问题去探索的自由,也有完全不配合这位创造者而自杀的自由。还有一种可能,创造者已经不存在,整个宇宙系统也许只是一个被遗弃的废墟,或者他只是在机器之前打了个盹……
很容易发现,将问题上溯到原点,再从原点演化到现在,不难得出苏格拉底式的结论。既然我们还活着,既然我们在问自己存在的意义,那么答案就自然指向了『认识自己』。生命对我而言只是个过程。在这个过程中,我唯一能观测的并非世界的变化,而是我自身的变化。至于我自身的状态的变化是已经发生过了,现在只是回放,还是正在发生,这显得非常的不重要,因为我们无法跳出这种状态的变化。这可能也是为什么 OOP 比 FP 更容易理解的根本原因。
这个世界有许许多多的工作,似乎没有任何一种工作能像编程这样激发我对生命开始思考。
从其他领域寻找答案
为了寻找终极问题的答案,有必要阅读一些哲学、物理、生物之类的书籍。人类数千年的文明,『我为何而存在』这个问题也已经被思考了数千年。既然我们此刻所处的系统还在运行着,那就说明答案并未真正出现。但是,在决定自己去思考这个问题之前,看看过去的时代里的有智慧的人是如何思考的,这是一个必须的过程。人类对这个问题的所有思考过程是伴随着自身的繁衍生息不断的传递下去的。
也许你该看点哲学类的书。如果从未想过去看哲学的书,我推荐一本 14 岁的少男少女就应该阅读的《苏菲的世界》,你不要打我。罗素的那本《西方哲学史》虽然出自他个人的视角,但是显然我们对哲学的理解也很难达到他那样的高度,鉴于我们也不是打算去在哲学上有所成就,《西方哲学史》足够我们看的了。我不认为这个世界上真正存在『正确』的哲学。
复习物理。《费恩曼物理学讲义》第一卷就很好,人类所能感知到的这个世界,费恩曼像讲故事一样的差不多一网打尽了。如果连费曼的书都看不懂,不妨看看《时间的形状》+《量子物理史话》,它们是近年来在相对论及其之后的物理学方面中国人写的非常优秀的科普书。还有一本是我大学时经常看的《从一到无穷大》,虽然年代已颇为久远,但依然不失为极好的物理科普著作。
生物学,这门课在中学时是我最讨厌的课程之一。因为我实在是看不懂书里的插图,乡村中学连个显微镜与真实的标本都没有,所以长期以来,我一直都是个生物盲。很多常见的花草树木鸟兽虫鱼,我连它们的名字都叫不上来,更不要说它们具体属于哪个门纲目科属种了。但是有一本生物学的书我还是能看懂的——《漫画玩转遗传学》,这是本非常好的遗传学科普书。当时我是因为学习遗传算法而买来的,结果从它从第一页开始就把我的眼睛抓住了。
但是,很诚恳的说,这些书都读完,我还是不知道『我为何而存在』这个问题的答案,但是我对编程有了更多的思考。特别是最近,量子力学将基本粒子的运动归结为概率问题的思想,这对于我一直致力去解决的一个问题有很大的启发。
这些书你都看过么?
都看过,但是有一些没有看完。有些书是看完了,但是时间久了,有些遗忘,一直想找点时间再重读一遍。
有些书是因为实在太好,不忍心一下都看完。这样说,有些矫情,但事实就是如此。像《费恩曼物理学讲义》,虽然有着物理学界小飞侠之称的费恩曼已经将深邃的物理学变成了我能够读懂的人类语言,但是一方面我不是专业研究物理学的人,我没有必要赶进度似的将他的书彻底读完,另一方面是一旦读完了,我就会惋惜,它不会再有第二季……我打算将费恩曼的这三卷物理学讲义留给我今后的时间里慢慢的去看,我也可以把这些知识讲给我的儿子听。
有些书对我而言是因为太难,即使我觉得已经看完了,但过一段时间发现,跟没看过是一样的。这些书,我也只能归类为没有看完的书。
凡是我看的书,都是我认为在书中所涉及的方向上,作者比我走的更为深远。但是我也不会自卑,因为他们现在已经没法走了……
后记
本文写于一个深秋又寂寞的下午,目的只是为我的小伙伴们学习编程指出一条道路。从 SICP 开始,可能有许多人觉得不靠谱,但是考虑到这些小伙伴一个一个都是研究生,考研期间经过了高数、线代、概率以及英语的『洗礼』,研一也会修数值分析与矩阵分析这两门数学课。即使他们以前从未接触过编程,但是他们的情况,SICP 还是挺适合他们的。如果他们连 SICP 的前三章都搞不定,这只能说明他们连 MIT 大一的学生都不如了……这或许不是他们的悲哀,而是这个国家的悲哀。
对于我不知道底细又打算学编程的同学,这篇文章可能不会太靠谱,所以我只能在题目中写上『不负责任』。不过,文章中出现的这些书,我觉得还都是挺不错的,有时间看一下,应该不是浪费时间。