# 理解专业课

如愿选上了计算机科学与技术专业，来到大学以后，前两年的课程安排跟我想象中的相距甚远。

本以为可以在大学里系统的学习计算机和软件开发知识，结果前两年安排的都是数学、物理之类的基础课，甚至经济学、管理学之类的不是理工科的课程。

于是大学的前两年我基本都是在通宵游戏和白天睡觉的状态下度过的，在课堂上的睡眠质量出奇的高，有一次甚至睡过了头：在高数课上课的时候睡着，醒来的时候发现教室里是另一个专业的物理课正要下课。

这样浑浑噩噩的两年过去之后才终于迎来了期盼已久的专业课，但是很快之后我又发现了另一个问题：专业课虽然跟计算机有关系，但是我也说不清楚学习它对于“我”的意义到底是什么，虽然我每一门专业课的成绩都还不错，但是直到毕业，我也仍然感觉每一门专业课都是一个个割裂的孤岛。

在毕业之后工作了很久，我才逐渐把“知识”和“应用”逐渐联系起来，形成一个完整的知识体系。

本以为这可能是我的个例问题，但是经过我这些年的面试，我发现绝大多数学生都没能把专业知识和实际场景联系起来。

计算机方向的大学课程，大概可以分为几类：

* 通识课，包括体育、外语、思想政治等
* 基础课，包括高等数学、大学物理、线性代数等
* 专业课，包括数据结构、操作系统、软件工程等

在这里先忽略通识课程，剩下的基础课与专业课之间大多数是部分递进的关系，也就是说后面的课程会依赖前面课程的某些部分。按照常规的教学体系，在刚入学时会学习基础课，之后学习专业课，最后是实践或实习。但是按照这个顺序学习下来，在学习前面的基础课程时，我几乎每天会产生“我学这个到底是有什么用”的困惑。

同时，（在我的那个年代）有些专业课的教材写的过于晦涩难懂（每一个被谭浩强弄疯过的人都懂），这也让专业知识变成了死记硬背的文字，而不是能够应用于生产的工具。

因此，在这一章的后半部分，我会尽可能的对计算机的知识体系做一次梳理，捋清楚专业知识之间的关系，并且推荐一些替代或者补充教材。

如果回过头来看，以开发工程师为例，他的专业知识可以分为几个部分：

* 计算机的运行原理
* 程序的设计及开发
* 程序的交付与维护
* 领域相关知识（数据库、多媒体、机器学习等）
* 其他软技能（沟通、汇报等）

这些技能彼此区别，但也互相联系，如果从更高的视角把软件工程师的专业技能和专业课放在一张图里，那么大概是这个样子：

<img src="https://2174558272-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F27G8HcQYwyyEoKIBsfO1%2Fuploads%2Fb5Z15ZvrevjxaIyC0p9p%2Ffile.excalidraw.svg?alt=media&#x26;token=8a499aa9-c651-429b-8fbe-891f1fd057b4" alt="软件工程师专业技能与专业课" class="gitbook-drawing">

接下来，让我们从通用计算机专业课（不考虑领域知识）的最上层：软件工程开始，逐步分解课程之间的依赖关系。

## 软件交付与维护

### 软件工程

[维基百科](https://zh.wikipedia.org/wiki/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B)上对软件工程的起源有着比较详细的说明：

> 1960年代中期开始爆发众所周知的软件危机，硬件成长率每年大约30％，软件每年只勉强以4～7％速度在成长，信息系统的交付日期一再延后，许多待开发的软件系统无法如期开始。1960年代软件开发成本占总成本20％以下；1970年代软件成本已达总成本80％以上，软件维护费用在软件成本中高达65％。1986年公布的数据，所有验收的外包软件中，竟然只有4％可用，其余96％却是不堪一用。大部分的企业自行开发的信息系统中，有四分之三也是功败垂成。因此软件维护成本居高不下，软件产品质量低落是最主要的原因。
>
> &#x20;1968年秋季，NATO（北约）的科技委员会召集了近50名一流的编程人员、计算机科学家和工业界巨头，讨论和制定摆脱“软件危机”的对策。在那次会议上第一次提出了软件工程（software engineering）这个概念，研究和应用如何以系统性的、规范化的、可定量的过程化方法去开发和维护软件，以及如何把经过时间考验而证明正确的管理技术和当前能够得到的最好的技术方法结合起来的学科。

简单来说，就是软件工程出现之前，编程是一门手艺，但是由于技术发展太快，手艺人经常把事情搞砸。软件工程出现之后，定义了软件的生命周期、软件开发的流程、以及整理了大量的软件开发最佳实践，最终得以让“软件开发”这件事情完成了由手艺人独立接单到社会化大规模生产的转变。

《软件工程》就是教授软件开发的工程方法的课程，通过学习软件工程，可以更“规范”的进行软件开发活动。

但是，软件工程课程本身涵盖的方面太多，因此对于具体的编程的核心过程：设计与开发，介绍的比较粗浅。

#### 建议：了解一个现代构建工具

在实际工作时，除了生命周期管理之外，依赖管理也是每天都要接触的工作，现代依赖工具已经成为了绝大多数工程，甚至是编程语言的标配，但是目前的大学课程却比较少提及这部分内容。对构建工具的周期、流程和依赖管理有基础的了解，能够解决日常中很多无效的工作。

推荐教材：各构建工具的官网文档。

## 软件的设计与开发

### 面向对象程序设计

在介绍《面向对象程序设计》这门课之前，要先理解一下“设计”与“实现”的区别。

可以把程序设计类比成建筑设计：建筑设计师通过图纸、文档描述建筑的建造方案，程序设计师通过UML图、规格文档等描述软件的建造方案。

> 注意：无论是建筑设计师、还是程序设计师，完成设计以后，他们的设计方案都还没有变成实际的建筑或代码。这在建筑设计的语境里很好理解，但令人惊讶的是，相当多的程序员在工作几年之后，仍然没有理解“程序设计”和“程序开发”之间的区别。

如果按这种方式区分，标题中的《面向对象程序设计》，可以理解为“面向对象程序的设计”，课程中前半部分是介绍面向对象程序中的各种概念和语法，比如类、接口、泛型等，后一部分介绍的是基于面向对象的思想，如何用面向对象中的各种概念描述现实问题。

进一步讲，面向对象中的“面向”，某种程度上可以改成“基于”，除了基于对象的设计，还有基于过程的，或者基于函数的。三者的本质都是让编程语言脱离了计算机的具体工作原理，而是提供了某种高层次的抽象概念，程序员把现实中存在的场景或问题，映射到这种更好理解的概念中（而不是更晦涩的计算机指令流水线），就完成了程序的设计。

> 之所以目前面向对象更受欢迎，更多的是因为“面向对象”的建模思路与真实世界更加相近，设计的门槛更低。如果有条件，建议更进一步学习函数式编程的设计方法，在某些更偏向于“解决问题”而不是“表达问题”的场景下会更有效率。

但是，面向对象程序设计这门课程对于“程序”和“设计”都没有很深入的讲解下去。

对于程序，绝大多数院校会在这门课里一并介绍，但大部分内容都是只介绍基础的语法。

对于设计，设计是个需要经验积累的能力，《面向对象程序设计》对于设计的讨论是不足的，仅讨论了基本的概念和思想，很多院校也没有后置的设计课程。因此才导致了大量程序员不懂设计、不会设计也不想设计。

<figure><img src="https://2174558272-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F27G8HcQYwyyEoKIBsfO1%2Fuploads%2FJbjqcRKjZlLRQb4vxBAT%2F26599859.png?alt=media&#x26;token=fd208e78-8d57-43bf-940f-92318487168a" alt=""><figcaption></figcaption></figure>

#### 建议：学习《面向对象设计模式》

据我了解，仅有部分高校开设了《面向对象设计模式》课程，这门课程本质上是“程序设计原理”与“程序设计实践”的桥梁。

某种程度上，也可以把“设计模式”可以换成“设计案例”，虽然大部分设计模式书里仅有十几种，但这些极具代表性的设计案例，可以覆盖相当多的程序设计场景，也可以作为进一步理解面向对象（或者其他）设计思想的参考。

因此，我建议学习完面向对象程序设计之后，抽时间学习一下《面向对象设计模式》。

推荐教材：随便找本评分高的都差不多，主要得靠自己琢磨。

#### 建议：学习画设计图

大部分《面向对象设计模式》的书里都会有设计图，但是看懂和会自己画还是两个阶段，画一两个周专门学习一下怎么画设计图，这对于日后的设计能力会有明显的提高。

推荐教材：PlantUML官网

#### 建议：学习程序开发

作为程序员，仅学习面向对象（或者面向什么其他的东西）的设计思想显然是不够的，在完成对问题的建模之后，还需要编写具体的实现代码。

虽然在课程里也会有程序开发的章节，但毕竟不是专业介绍编程语言的教材，很多时候只是能简单看看的程度。

推荐教材：

* 如果是实践型的同学，推荐“Head First”系列，比如《Head First Java》、《Head First Go》等快速入门，然后在实践中加深理解。
* 如果是理解型的同学，找各语言评分最高的书，基本上会讲解的比较全面。

#### 建议：练习程序开发

大部分大学的程序设计课程都会有专门的课程设计，设计目标是做一些CRUD之类的项目，能够对对应语言的开发有个基本的学习。

但是，大部分项目的模式都比较固定，意味着网上能找到的答案也比较固定，有些课程设计甚至用了十几年，还是当年的样子，这就导致很多毕业生写出来的代码带着一种20年前的风格。

我建议有余力的同学可以找一下自己学习的编程语言里，目前比较成熟的依赖库（比如说web框架），然后参考依赖库的官方示例文档（比如C++的oat++，Java的spring-boot、Golang的gin等等），用现代的方法实现一些功能。

推荐教材：各成熟依赖库的示例文档。

#### 建议：或许可以试试Java

一部分《面向对象程序设计》使用的语言是C++（比如我当年就是），虽然C++也是具备面向对象特征的语言，但是它本身的语法、概念和复杂度都太高了，甚至语言的复杂度远远超过了面向对象思想本身，在通过C++学习面向对象课程的过程中，很有可能被C++本身的多继承、宏、内存分配与回收这些技术点搞到怀疑人生--但是这些跟面向对象本身又没什么关系。

因此我建议通过更简单的面向对象语言入手，学习面向对象程序设计，比如说Java：语言本身没有那些复杂而又晦涩的概念，也不需要额外关心内存、指针或者示例代码里的奇技淫巧。

推荐教材：Java编程思想

#### 建议：多学几门语言

面向过程、面向对象和函数式

编程语言层出不穷，但是核心的建模思想还是只有几个大的方向，学习这几种编程语言思想，对未来更快速的掌握新的编程语言会非常有帮助。

推荐学习：Lisp 《The Little Schemer》

编译语言和脚本语言

大学里绝大部分课程都是编译型语言，但是到了日常工作中，会有很多“搞一下”的场景，比如快速搭建一个网站，或者简单统计一个数据，至少学会一门脚本语言，能够大大提升日常工作的效率。

推荐学习：Python《Head First Python》

### C程序设计

这大概是当年让我最困惑的一门课程：

1. 这门语言能干啥，为什么非要学它
2. 明明只是学习一门语法不是很复杂的编程语言，但是为什么我就是学不明白？

过了很久，我才逐渐理解原因--学习C语言有两个替代不了的特性：

1. 直接与硬件地址打交道，适合帮助理解内存、IO、操作系统等底层原理
2. 语言本身不复杂，适合用来描述数据结构与算法

但是，教材写的实在是太差了，导致那么多人学的云里雾里（不知道现在高校是否还在用谭浩强的C语言教材）。因此，我强烈建议换一本教材来学习C语言。

推荐教材：《C Primer Plus》

### 数据结构与算法

如果已经学习了上面的那些课程，那么你其实已经可以做一些基础性的开发工作了，就类似于农村盖房子有很多经验性的知识，老师傅带着教一段时间，也能出徒。

而这门《数据结构与算法》，就是程序员的“科班出身”的分界线，对它的理解，一定程度上决定了你的上限是盖个二层小楼，还是能盖出几十米的高楼。

可以再做个简单的类比：把数据结构与算法类比成建筑结构与材料，盖房子需要了解建筑结构的特性，比如能承受多少压力和拉力、应该在什么场景下选择合适的结构，凭借着底层结构的稳定，才能盖出高楼；同样，了解了数据结构和算法的特性，才能知道程序的运行效率和消耗，程序底层的结构的稳定性和效率，同样决定了程序运行能力的上限。

在未来，会用到数据结构与算法的地方包括但不限于：

* 所有面向科班出身程序员的面试
* 底层程序（内核、数据库、中间件等）的开发
* 互联网高并发或大数据量数据程序的开发
* 程序性能分析与优化

因此，如果以“优秀的程序员”为目标，无论这门课学的多好，都不浪费；相反，如果只是想找个简单的开发工作混日子，这门课对于工作的帮助其实不大。

我没有学过大学数据结构课本之外的教材（我的数据结构这门课学的出奇的好，所以我感觉教材也够用），因此不做额外推荐了。

## 计算机运行原理（软件）

### 编译原理

这门课主要讲述代码是怎么从一个个文本文件，变成计算机里可以运行的程序的。

从这门课开始，学习的内容就逐渐的跟常规的开发工作脱离了关系--你几乎很难找到一个让你去开发某种编译器的工作。

但是，掌握了底层原理，对于问题分析和问题攻坚都会有极大的帮助，大部分公司的日常工作都是拧螺丝，但是，螺丝总归是拧在汽车或者火箭上的，当这个庞然大物出现了问题的时候，能够做技术攻坚的永远只是那些掌握了基础原理的少数人。

而《编译原理》包含的内容，虽然连接了高级编程语言与机器语言，但是其中的大部分知识（词法分析、语法分析、语义分析等）却自成一派，跟其他知识的关联性并不大，因此学起来会相当枯燥。因此，我更推荐通过对lex及yacc的实践，加深对编译器的理解。

#### 建议：学习《lex与yacc》

lex是一个创建词法分析器的程序，yacc是一个创建语法分析器的程序。有了这两个程序，我们就能开发出一个新的编译器，创造一门新的编程语言！

而在使用lex与yacc的过程中，既是对编译原理的应用，也是从另一个角度对编译原理的学习。无论从哪个角度，缺少《lex与yacc》的编译原理学习，都是不完整的。

### 操作系统

这里是我们日常开发的应用程序与硬件的分水岭，操作系统与绝大部分计算机知识都有一定关系，可以说它一定程度上会决定未来职业发展的技术广度。

#### 建议：学习《从零开始写一个简单的操作系统》

如果想真正深入的理解操作系统的原理（而不是应付考试），那么没有什么比自己写一个操作系统更具有说服力的了。

甚至于，你只要学习了《写操作系统》的前几章，实现了一个基础的BootLoader，对操作系统的理解就已经能甩开《操作系统》课程考试前几名的那些人几个身位了。

#### 建议：学习《链接器与加载器》

绝大多数大学没有这门课程，但是我认为这是必须学习的一门课程，它主要讲的是编译器产生的文件，是如何通过操作系统的帮助，最终实际运行在计算机上的。

学习了它之后，能把从“操作系统”到“软件工程”的这条链路上的所有知识体系串联起来。对于未来的问题排查、开发效率提升、微服务系统设计能力、代码优化……等等等等，都会有不同程度的帮助。

计算机领域，或许没有其他任何一门专业课能有这种“四两拨千斤”的效果。

#### 建议：学习《深入理解Java虚拟机》

如果是Java系的，除了操作系统之外，还有一层jvm虚拟机挡在“应用程序”和“系统程序”之间，无论是找工作，还是日后工作中的问题排查和性能优化，这部分的知识也不可或缺。

### 计算机网络

解决了程序单机运行的原理之后，接下来一个通用的原理部分就是《计算机网络》，虽然多媒体、人工智能等等方向也有各自的原理，但是网络方向伴随着互联网兴起而逐步成为了计算机行业的基础设施。

但是，网络部分的原理同样相当抽象而枯燥，我做了这么多年技术面试，绝大多数的程序员都能够说出OSI模型，但是能把OSI模型套用到具体问题的，可能连1%的人都没有。

#### 建议：学习《TCP/IP详解》

如果不做网络相关的工作，作为普通开发日常接触最多的应该就是TCP/IP协议，日常工作用到的绝大部分网络原理，也是TCP/IP相关的原理，因此，单独学习TCP/IP是有一定意义的。

但是，TCP/IP本身涉及的知识量也足够惊人，因此只推荐给学习能力强的人。

#### 建议：学习使用wireshark

wireshark本身是个抓包分析工具，如果是动手能力强，不想看那么多字的TCP/IP枯燥的原理的话，也可以通过一个“捷径”学习TCP/IP的基础知识：抓包。通过观察现有程序的网络通讯包，并加以解释，也可以快速掌握一部分TCP/IP知识（但是要记住：这部分知识是经验性的，视野会存在局限）。

推荐书籍：《Wireshark网络分析就这么简单》、《Wireshark网络分析的艺术》

## 计算机运行原理（硬件）

### 计算机组成原理

对于非电子相关专业的计算机专业课程，《组成原理》已经算是计算机底层原理中最接近硬件的课程了，涉及了几乎所有硬件的基础工作原理。

坦率的讲，学习组成原理，能够对后续软件开发工作产生的直接帮助不多（偶尔也会有），但是不了解这部分知识，程序设计、问题排查或者架构思考的深度会受到影响，很多看上去非常“互联网”“分布式”的技术，底层核心思想其实和基础的单机硬件结构没有什么区别，回过头来看，绝大部分当今看似新潮的互联网架构，实际上只是单机组成原理的衍生品。

掌握组成原理的基本思想，是未来程序设计能力的一片重要的拼图。

#### 建议：买个开发板

如果经济条件允许，可以买个便宜的开发板，即使只是做个简单的GPIO操作，也会对计算机的软硬件有个更直观的认识。同时，大部分开发板会附赠一大坨教程，也算是不错的补充教材。
