CS科班教育在今天对于当程序员是否仍有价值?

  • 如果只是想到BAT打工当程序员,是不是一定要是CS科班出身?
  • CS科班出身的程序员是不是理所应当的高人一等?
  • 学校里教的技术很老旧,是不是还不如翘课自学新技术?
  • 培训班和某些大V网课是不是能学到”真本事“?

这样的问题在各大网上平台中被反复地讨论,但时常这样的讨论都以双方发生大量争执而不欢而散。应该强调本文的目的不是对于以上问题给出是或者否的答案。*事实上,我对于以上(和其他没有列举的问题)的看法相当复杂。*但我仍然认为有必要对这些问题提出一些看法,并且这样的讨论是有意义的。

某教育机构的广告。真这么简单到底还要不要上大学?某教育机构的广告。真这么简单到底还要不要上大学?

为什么要讨论这个问题?

舆论对于这个问题的态度历来是相当的极化的。一方面有不少科班出身的人会Dismiss问题本身,认为科班教育的优势和价值是不言而喻的,是无需讨论的,不可否认的。这种看法很容易被人指责是一种莫名其妙的优越感,是歧视的一种表现形式,毕竟这么旗帜鲜明的主张没有相印的证据justify是很难让人信服的。另一方面,有不少计算机方面的从业者并不是科班出身,但不妨碍他们成为具有影响力的人。自然的就有观点认为大学教育就是扯淡,读个学位不如出去打工,甚至出现了翘课实习的情况。这一观点再结合上国内某些大学的糟糕的本科教育水准的现状,让人难以反驳。 我认为既然存在这样的重大的分歧,就说明问题具有讨论的必要和空间。

与计算机相关的知识是相当的Democratized的。某种意义上说计算机是最近真理掌握在大多数人手里的学科。传统的科学往往存在于学院的深处,一般知识和技巧并不会下放给一般人。有的学科至今仍然很大程度上保持着师傅带土地徒弟(导师带PhD)的门阀样态。与计算机有关的,尤其是与软件有关的知识则相当的容易获得。我认为有几个原因造成了这种状态:

  • 很大一部分知识来源于实践而非理论推导。软件有关的知识很大程度上与实践深度挂钩。有的知识,比如Map Reduce的写法,Go语言,本身就是工程的产物。学界反倒是这些知识的消费者。
  • 开源精神。计算机的传统有相当强的自由和开源精神。计算机在理论上诞生于对于形式化系统的研究,从一开始就是公开的。工程上很早就有开源文化,软件开发很早就是围绕合作和互联展开的。这导致计算机有关的信息非常容易传播和扩散。

那么在这一背景下,如果我”仅仅想到BAT/FLAG写代码”,是否仍然有必要获得一个CS的学位呢?大学的价值在什么地方呢?培训班和自学能不能替代大学教育呢?我认为这一系列的问题的本质是我们希望从教育中获得什么?或者问,对于码农而言,到底什么知识是必要的?有多少(几乎)只有在大学中能获得呢?

为什么要讨论这个问题

有几件事让我觉得有必要写一写这个问题:

最近有一个关于萧大培训班的知乎问题非常的火热。我也去匿名凑了个热闹。说实在我不是很关心萧大的学生是不是真的会写这些东西,因为这真的不是很有来回讨论的必要。知乎上已经有大量的关于培训班到底是不是噶韭菜的争论,这个既不是最刺激的,也不会是最后一个。对于培训班的争论大多集中在培训班到底是不是能“教会”编程。实际上我们对于写代码到底需要什么样的能力并没有很好的讨论过?

网上时不时有Diss科班教育出身的人的帖子,有些帖子提出了很好的批评,还有一大部分相当的缺乏思考。对于科班学生的批评往往集中在学生缺乏“实践经历”,“不接地气”上。诸如:名校毕业生不会用Shell/某某常用指令/某某常用框架,以及“只会考试没写过几行代码”。对于科班学生的defense也大多包括“科班学生学起来快”,“潜力大”,“创新能力强”之类的。这些论据往往脱离了写代码的能力本身,仿佛已经默认了科班出身的人理应代码能力不行。我觉得(国内)现行的科班教育确实存在实践不足的问题,很大程度上是因为我们的教职员几乎没有计算机从业经历,也极少有紧贴现行技术的。我本人接受了5年的科班教育,对于有些批评说实在是有相当的不快的,因为我觉得即使纯粹是写代码的能力,大学教育也有其不可替代的价值。

代码不止有"It Works"

我想把话题扯回到有关萧大的培训班的知乎问题上。这个问题是我写这篇文章的起因,也是很好的例子。整个问题的争论都集中在萧大的学生到底有没有写出萧大所claim的项目。但是我想问,就算写出来了,能说明什么问题呢?说到底,怎么样又算是“写出来了”?

很多时候我们对于写代码的认识在于“写出”某一成品。这样的认识让我们感觉写代码的成果是二元对立的:你要么写出来了,要么没有写出来。但任何的有经验的程序员会知道事实完全不是这样的。代码在“成了”和“没成”之外还有很多其他的属性。代码除了正确性之外,还有支持的功能的多少,性能快慢,内存占用的多少,还有代码的稳定性,代码的可拓展性等等。

必须认识到就算是正确性也是很难衡量的。玩具代码往往并没有配套的周全的测试集。如果代码只是在很小的测试集上正常工作了,我们凭什么认为代码会在所有可能的输入上都正常工作?更不用提诸如代码是否在所有的输入上性能表现一致,这样的更难以回答的问题了。

我认为必须明确代码本身是复杂的和难以衡量的。如果代码具有及其复杂的属性,那也就意味着编程作为一项能力也具有多个维度。换句话说,合格的程序员不止产出“能用的”代码。合格的程序程序员产出在各个维度都满足要求的代码。正如同上面已经提到的,程序的许多维度并不是简单的是或者否的问题。处理这些复杂度必须要求写代码的人有分析代码的能力。因此,程序员不仅要会写代码,更应该会分析代码,知道如何处理代码的各个维度之间的联系。举例来说,程序员需要能回答诸如“我的代码为什么是正确的?代码的性能特性是什么?如果出现问题,如何有条不紊的确定造成问题的局部?”这样的问题。

我想要补充的是并不是所有的代码都有一样高的要求。比如如果某A操作系统的内核每个月都要崩溃一次,那么这个OS对于很多场景就是完全不能用的了。但如果Word一个月崩溃个一两次完全无伤大雅(如果你开启了自动保存)。事实就是有的代码的质量确实是nobody cares。并不是说Nobody cares的代码就不需要质量了,但这样的代码并不怎么重要也是事实。我个人用于区分什么样的代码是重要的方法是10-by-10 rule: 如果一段代码的崩溃的频率上升10倍并且性能下降10倍,而写代码人的不会被开掉(或者挨揍),那这段代码就不是很重要。如果一个人安心总是写不重要的代码,并且完全不担心被淘汰,那我也确实无话可说,只能说这篇文章确实不适合你。

总之,对于程序员而言,在会写代码的基础上,能够分析代码十分关键。写代码本身更多是熟练工,毕竟相当多的语言只要熟悉了语法和核心language constructs,写出能跑的程序并不是什么困难的问题。我们如果要学写代码,那就应该关心如何才能学到分析代码的技术。我认为科班教育最大的优势在这里。

分析代码的技术

我认为与写码有关的技术可以分成几类:

  • 关于语言(Language)的知识:比如对于C++的语法的了解程度,对于Java的常用库的熟悉程度,对于Python的语义的了解程度。这些还包括了对于语言的常见的用法(Idiom)的熟悉程度。Java程序员推崇的设计模式也归为此类
  • 领域相关(Domain Specific)的知识:这里主要包括了代码所处的平台有关的知识。比如iOS开发者需要了解MVC是什么以及iOS设备的特性。写GPU code的程序员需要对图像管线有足够的认识。编写分布式系统程序的人需要学习分布式的算法等等。这一类还包括了体系结构的具体知识。
  • 关于编程(Programmming)本身的知识:作为一项智力活动,编程本身也是有其自己的规律性的。这一部分的知识往往被人忽视,或者被轻易得归类到“经验”中。我认为正是这一部分的知识的提供了分析程序的“框架”,而领域相关的知识提供了填充框架的“实体”。二者结合使得程序员能够真正得理解手上的的代码。

我认为Democratization使得前两类知识相当容易获得。与语言有关的知识往往可以很轻易得从语言的文档中获得,通过阅读示例代码也能快速上手一门语言。培训班和网上的教程都能很好地传递这一部分知识。就这一类知识而言,大学没有任何优势(如果考虑到要交学费还有很大的劣势)。领域相关的知识相比较而言会更难获得一些。有的领域相对而言较为小众(比如体系结构),因此公开的信息并不是很多,从而在无人指导的情况下学习比较困难。有的领域则相当开放(比如机器学习),因此入门会比较轻松。大学在这一方面的价值是大学里往往有某个领域的专家,在教领域相关的知识过程中还会提供独到的见解。大学还会提供接触每个领域的前沿技术的机会。但是对于成为程序员而言这一优势确实也没有什么用,毕竟大多数人都不是怀揣着成为PhD梦来上大学的。

抽象(Abstraction)

大学最重要的优势在于第三类知识。实际上,写程序和写证明有相当大的共通性(实际上Curry-Howard Correspondence表明了它们就是一回事)。写代码其实是从在小的模块构造更大的模块。我们如何才能确认我们的程序行为正确呢?唯有通过抽象。我们首先对于小的模块的行为做出假设(考虑清楚每个小函数做了什么),然后论证由这些小的模块搭建起来的大的模块也是正确的。这一过程和数学上的证明如出一辙。因此编程是一项需要良好逻辑素养的活动。对于庞大的目标,需要小心地逐步分解为可以控制的小目标,然后仔细地厘清互相之间的依赖关系,最后把它们拼起来。**抽象是计算机整个计算机技术的核心。**编程很大程度上是在不断得设计和实现抽象。举个例子,RAII把内存安全和局部生命周期绑定到了一起,把内存安全问题抽象成了作用域的问题。正是这一抽象使得我们可以通过分析作用域而说明程序是内存安全的。

事实上我觉得我们应该能达成以下共识:写程序本质上是Logical Reasoning的过程。我个人对于培训班以及部分大学的教育最大的不满即在于此。很多时候当人们讨论编程的时候把仅仅把它作为达到目的手段(means),编程这一行为本身被认为是次要的,工具性的。我认为我们应该正视编程本身,即承认编程这一行为本身是有其规律的,是可以作为Subject加以讨论的。目前培训班(甚至是部分985大学)在教编程的时候把重心放在语言和语言特性上,更有甚者讲大量的时间花在教授具体的库的使用上,然而对于什么是抽象,如何设计抽象,如何利用抽象来reason这样的问题完全没有涉及。这实际上是非常糟糕的。

对于Abstraction本身的研究,即使在今天,也往往是数学的范畴。我认为这恰恰是科班教育对于编程的最大的价值所在。大学的理论课程的训练是对于logical reasoning极佳的训练。恕我直言,这可能也是自古以来为数不多的(甚至是唯一)的tried and true的方法。这些对于逻辑能力本身的训练能够赋予程序员编写合乎逻辑的程序的程序的能力,合乎逻辑的程序更可能是正确的,稳定的。合格的大学教育对于培养人的逻辑能力的贡献是无需多言的。因此我认为在今天CS科班教育对于当程序员仍有不可替代的价值。