国内计算机科学教科书的一些弊病

翻了翻数据结构的教科书,排序 这一章的堆排序看得我非常难受,尤其是其从一个无序数组构造堆的过程。书上的算法是这样的:把这个数组看作一棵完全二叉树,然后从下到上,从右到左地把所有的非叶节点都构造成一个堆。这样往上走的过程中可以保证每个非叶节点的左右孩子都是成型了的堆,直接比较交换即可。这个算法完全没问题,但是我认为出现在数据结构的书上很不合适,尤其是出现在这么靠后的章节。原因无他——舍近求远,舍易求难与计算机思想恰是完全相反的。

其实这个问题哪有这么复杂呢?二叉树的后序遍历就直接解决了:首先递归地把根节点的左右子树构造成一个堆,然后再和根节点比较交换即可。算法干干净净,没有乱七八糟的考虑和计算,这才是计算机该有的感觉。而纵观整本教科书,知识点间的内禀联系几乎没有什么提及,总是每章内容各自为政,也就导致学生在打基础的过程中云里雾里,完全不知道自己在做什么,最终需要绕很远的路才能回到正轨。

我在本科刚学习数据结构时非常讨厌教科书,因为感觉死板,没有 ACM 中那些精妙好玩的算法;后来随着写代码越来越多,感觉这几本教科书都是有水平的,其中提到的知识点一直都是计算机发展几十年来的底层架构;现在再看看教科书,感觉有些地方如果能换个讲法,力求知识体系网络的建设,而不是追求一招一式的酣畅,或许会更好吧。

关于计算机学习

我认为计算机领域真正的学习应该是“博客式”的,即遵循“遇到问题->查阅资料->弄懂问题->有成就感->总结记录”,早些时候我把这个叫做“需求驱动学习。

举个例子

嘟嘟(我家泰迪)平日里自诩Java小王子,无论是手写Runnable还是一口气1 << 8个线程池都信手拈来。有天老板(我)在微信上说:“大家都说‘人生苦短,我用Python’,蟒蛇听起来比咖啡厉害多了嘛,你,赶紧用Python把后台重写一遍”,嘟嘟一边暗自庆幸老板还没听说过PHP,一边嘀嘀咕咕开始了改造之路。在迁移Java的多线程部分的时候,嘟嘟想用Python的Thread来做,但是发现Python中有万恶的GIL(Gay In Love Global Interpreter Lock),想要实现走位酷炫的线程池的计划泡汤了。

一些社区建议使用多进程来代替多线程,但是嘟嘟在写了两个Demo之后发现它们和多线程不一样,变量竟然不能共享,这代码还怎么写,于是继续面向百度编程,在误点进去十几个培训机构的主页之后,嘟嘟终于找到了一个新奇的解决方案——异步编程。在短暂纠结于yield和yield from的写法之后,嘟嘟又找到了更好用的async/await,并顺利把Java上的多任务移植到了Python上。好景不长,有天嘟嘟不小心让视频转码的任务读入了硬盘深处700多个G的马克思主义视频教程,发现Python的其他多任务都不能正常响应了。在妙峰山烧了七八柱香之后,嘟嘟才了解到原来是因为一个异步任务被阻塞住了,导致很多其他任务不能被处理。最终嘟嘟还是把计算密集的任务扔到了多进程上去做。

在整个项目迁移结束之后,嘟嘟开始对迁移过程进行复盘,发现以下几点需要搞明白:

  1. 为啥多进程变量不能共享
  2. 为啥有GIL在多线程就不好用了
  3. 为啥一个异步任务阻塞会影响其他任务
  4. 为啥在百度搜Python老蹦出来培训机构

于是嘟嘟开始了新一轮的调查研究,在经历过以往的教训之后,嘟嘟学会了在一个404的网站上搜索404的信息。这才明白了进程和线程的关系,明白了进程如何通过消息队列进行通信,明白了异步编程的好处和局限性以及事件循环的原理。无论是进程、线程、协程还是纤程,本质都是想要达到一个目的,即“在需要的时候占用CPU,不需要的时候释放CPU”。找了一堆的资料之后,嘟嘟又打开了大学时崭新的操作系统课本,把处理器这一章从头读了一遍,发现醍醐灌顶,每一句都是好东西,感叹自己当时上课怎么就没发现这本书的精妙之处。感慨之余,决定把自己的感受记录下来,标题就叫《关于计算机学习》。

我的经历

从本科一年级第一节编程课开始,我就喜欢上了编程。之后整个一年级都沉浸在ACM刷题和囫囵吞枣的学习之中。虽然当时只会命令行编程,但是还是做出了一些小玩意,比如自动计算游戏中交易的收益、收集名侦探柯南TV版的分集信息等,当时没什么备份的概念,也不懂版本控制,现在程序都遗失了,留下来的只有一个基于MFC的计算器。

我的专业是计算机科学与技术,当时要学习一些计算机的基础课,比如操作系统、数据结构、计算机组成、计算机网络、编译原理、数据库等,在大学前两年中,我一直都特别讨厌这些科目。一边是自由新奇的编程实践,另一边是枯燥和看似无用的琐碎知识点——就像高中一样,显然后者无法引起任何人的兴趣——即使有,在一个具有强实践性质的专业中谈纯粹在课本中获得的快乐也和耍流氓无异。当时在这些课程中我相对不那么讨厌的是数据结构课,因为其中的很多算法我在很久之前就已经在POJ上刷过很多次了,所以上课的时候有一种仅通过预习无法感受到的亲切感——这也是本文想要传达的观点。

我是什么时候有了“还是制定专业课计划的那几个老头厉害,是我当时太年轻了”这种想法呢?在大二结束和大三开始这段时间,随着写代码越来越多,接触的领域越来越多,我开始做了一个在当时看来算得上是巨无霸的项目,从前端到后端都是我一个人完成。和我之前接触的项目不同,这个项目是真的有很多用户的(笑),所以系统上线之后不断暴露出越来越多的问题,比如数据库查询很慢,比如网络延迟很高,比如客户端卡顿等。在给自己收拾烂摊子的时候,开始重新学习了多线程、数据库、计算机网络(主要是退避算法之类),然后猛然惊觉,“这不就是我大学里的专业课么?”。

时间一晃到了现在,我已经研二了。在给师弟师妹们介绍我那点不成器的经验时,我的观点也从“多实践多编程”转变成了“先把基础打好”。计算机科学与技术的专业课都很重要,无论讲课的老师水平如何,都一定要学好,它们是构造整个互联网空间的基向量。话虽如此,这并不代表我完全同意目前计算机教学的思路,我认为计算机领域真正的学习应该是“博客式”的,即遵循“遇到问题->查阅资料->弄懂问题->有成就感->总结记录”,早些时候我把这个叫做“需求驱动学习”。

我的观点

为什么我们都不爱听大道理?为什么我们听了那么多道理仍然过不好这一生?为什么我们反感鸡汤?

我认为计算机的这些基础课就像所谓的大道理一样,没有相关的经历作为培养基的话是无论如何也不可能理解的,自然只能觉出枯燥无味和腐朽陈旧来。但是倘若踩过了无数的坑就会明白,这些基础课本字字珠玑,毫不过时(也可见我们的科学发展其实并没有大家想的那么快),古人诚不我欺也(有多少人都写成“诚不欺我”,意思还是一个意思,但是对话场景瞬间从项脊轩蹦到了王老大烧烤摊)。想要理解多少大道理就要踩多少坑,该踩的坑一个都少不了。

不要误会,我不是在宣扬基础教学无用论,我的意思是初学者一开始不必过于深入地了解基础知识,因此此时无法真正理解,不如先拓宽知识面,暂时了解有这么回事就可以,把一部分的时间匀出来自己去折腾,只要智力正常并且适合干这行,很快就会产生深入学习的需求的,这时候的学习效率远比按部就班划拉书本要高很多。以嘟嘟来举例,学习处理器调度的过程可以分为以下几个阶段:

  1. 了解到一切计算都被分解成指令交给处理器顺序执行
  2. 在Bilibili上自动监测赶海四天王的视频,并及时下载
  3. 查阅类似实现的开源代码,学习,重复踩坑,完成需求
  4. 查阅课本或工具书,学习进程和线程的原理、关系以及区别
  5. 拓展了解协程、纤程、Actor等异步编程模式
  6. 接触NodeJS、Python、C#、GoLang在多任务上的做法,对比学习
  7. 感觉自己很厉害,写博客交流学习,一写笔才发现还有很多细节不懂,继续学习
  8. 开始踩下一个坑……

以上只是一个捏造的例子,用来说明一个渐进式的学习过程可能是什么样子的,实际过程中的步骤或许没有这么精细和繁琐,但大步骤不会差很多。我可以保证相当程度的低年级计算机专业同学对于以上的这些东西都没有清晰的概念,所有东西都糊作一团。根据我浅薄的经历来看,有一些经验比较丰富的同学可能只是停留在前三个阶段,实事求是地讲,代码风格和注释都很漂亮,但是就是无法再往前一步。顺便一提,还有一些同学在学习某些语言或者框架时,总是会虔诚地走完“买书->找视频->进技术群”这个流程,我认为这样效率是比较低的,有那百度云下载视频的时间官网文档都看了好几遍了。不如先找个点切入,即使是Hello World,然后一步步拓宽把整个需求盘下来,在复盘的时候再通过书或者是视频系统学习。系统学习应该是后置的,连它能干嘛都还不知道,系统学习又有什么用呢,结果只能系统地遗忘。至于技术群,其作用是收表情包,和技术没啥关系。

无论需求从哪里来,是随便玩玩,还是饭圈妹子的抢票委托,还是老板或外包的要求,只要你决定实现一个需求,下一步就是分析和调查需求应该怎么实现。如果你有一定的基础知识(即使是广泛而不深入的),那么调研的过程会更有针对性,接下来就开始”Done is better than perfect”的过程,其间随着踩坑会开始接触相关领域的知识,然后拓展学习总结出一套敝帚自珍的宝贝,在成就感和虚荣心的驱使下,把这些碎碎念记录下来,记录的过程发现原来还有很多细节自己根本没弄清楚,再去迭代学习,一步步把这篇博客写完。基础知识在这个过程中会一步步得到加强,每一次都是重新认识,每一次的认识都更加清晰几分。

或许你又会问,总是实现一个又一个大同小异的需求,如何才能摆脱成为CRUD Boy的命运呢?多想,多拓展,多总结记录,并乐在其中,It’s that simple.

关于程序员的一点感想

对于一个作家,最重要的是能写出出色的文章,文章的水平如何,和作家用什么颜色、形状、粗细的笔来写没有任何关系。假如出现这么一幕场景:许多作家围坐在桌旁,口水飞溅地争论:

“英雄才是最好的笔!”

“瞎说!关勒铭才是最好的笔!”

“英雄笔在未来十年必将消亡的 10 个原因”

“新的写作模式,双手梅花篆字”

“怎么在一分钟之内写 300 个字——论英雄没有关勒铭流畅的原因”······

可以想象是个什么场景吗?

是的,我当然知道把程序员比做作家是牵强附会并不合适,但是一些(甚至可以说很多)程序员在讨论、在追求的都是用什么语言,用什么框架,有的人没有科班背景只是在培训机构培训了几个月(没有任何对培训机构偏见的意思)就出来加入了轰轰烈烈的”程序员讨论“中。

这样真的有意义吗?难道写代码真的要与语言、框架绑定在一起吗?你是学 Java 的,让你写 Python 你就可以说自己不是程序员了吗?程序员从何时开始这么廉价了?语言、框架(以及目前各种 Mxx 设计模式)对于程序员不就像笔对于作家一样只是工具而已吗?为什么作家要被笔束缚?为什么程序员要被语言、框架束缚?

程序员应该是骄傲的作家,不应该沦落为体力劳动者,如果你是后者,请不要,至少不要在我面前说你自己是个程序员!