每个计算机科学专业的学生应该知道什么?
INFO
原文为 Matt Might 的 What every computer science major should know。 由 Gemini 2.5 Pro 翻译,请对内容进行甄别。
鉴于该领域的迅猛发展,要辨别现代计算机科学学位应包含哪些内容已变得颇具挑战性。
我所在的院系也正在进行这场辩论,因此我将自己的想法汇总,以回答“每个计算机科学专业的学生应该知道什么?”这个问题。
我试图从四个方面来综合回答这个问题:
- 为了找到一份好工作,每个学生应该知道什么?
- 为了维持终身就业能力,每个学生应该知道什么?
- 为了进入研究生院深造,每个学生应该知道什么?
- 为了造福社会,每个学生应该知道什么?
我下面的想法既包含了普适的原则,也包含了与现代计算领域相关的具体建议。
计算机科学专业的学生们:可以随意将此作为一份自学指南。
欢迎通过电子邮件或 tweet 提出增删建议。
更新:感谢大家的建议和提醒!我会随时将它们采纳进来,使这份文档保持更新。
作品集 vs 简历
计算机科学课程源于工程学和数学,其毕业生招聘也沿用了基于简历的方式。
一份简历并不能说明一个程序员的能力。
每个计算机科学专业的学生都应该建立一个作品集。
作品集可以很简单,比如一个个人博客,为每个项目或成就写一篇文章。一个更好的作品集应包含每个项目的专属页面,以及可公开浏览的代码(或许可以托管在 github 或 Google code 上)。
对开源项目的贡献应该被链接和记录下来。
一个代码作品集能让雇主直接评判你的能力。
而 GPA 和简历则不能。
教授们应该设计一些适合放入作品集的课程项目,而学生们在每门课程结束后,也应该花时间更新他们的作品集。
示例
技术沟通
在计算机科学领域,独行侠已是濒危物种。
现代计算机科学家必须练习如何有说服力地、清晰地向非程序员传达他们的想法。
在小公司里,一个程序员能否将她的想法传达给管理层,可能决定着公司的成败。
不幸的是,这并非增加一门课程就能解决的问题(尽管一门扎实的技术沟通课程不会有什么坏处)。
更多的课程需要为学生提供机会,让他们通过口头报告来展示自己的工作并捍卫自己的想法。
具体建议
我建议学生们掌握一种演示工具,如 PowerPoint 或 Keynote(我的最爱)。(抱歉,尽管我非常喜欢基于 LaTeX 的演示工具,但它们实在太静态了。)
在制作精美的数学文档方面,LaTeX 无可匹敌。所有技术课程的书面作业都应该用 LaTeX 提交。
推荐阅读
- Zobel 的 Writing for Computer Science
- Asher 的 Even a Geek Can Speak
- The LaTeX Companion
- Knuth 的 The TeXbook。(警告:仅限专家。)
- 数学写作笔记
- Simon Peyton-Jones 关于如何做一场好的研究演讲的建议
- 我关于如何发送和回复电子邮件的建议
工程核心
计算机科学不完全是工程学。
但两者非常接近。
计算机科学家们将会发现自己与工程师一起工作。
计算机科学家和传统工程师需要使用同一种语言——一种植根于实分析、线性代数、概率论和物理学的语言。
计算机科学家应该学习物理学,直到电磁学。但要做到这一点,他们需要学习多元微积分(以及微分方程,以求万全)。
在构建可靠的模拟时,掌握概率论和(通常是)线性代数是无价的。在解释结果时,没有什么能替代对统计学的扎实理解。
推荐阅读
- Spivak 的 Calculus
- Wasserman 的 All of Statistics: A Concise Course in Statistical Inference
Unix 哲学
计算机科学家应该熟悉并实践 Unix 的计算哲学。
Unix 哲学(而不是 Unix 本身)强调通过语言抽象和组合来实现计算。
在实践中,这意味着要熟悉命令行计算、文本文件配置和无 IDE 的软件开发。
具体建议
鉴于 Unix 系统的普及,今天的计算机科学家应该能流利地使用基本的 Unix,包括:
- 浏览和操作文件系统;
- 用管道组合进程;
- 能熟练地使用
emacs和vim编辑文件; - 为软件项目创建、修改和执行 Makefile;
- 编写简单的 shell 脚本。
除非学生们理解 Unix 哲学的强大之处,否则他们会抗拒它。因此,最好是给学生一些挑战,让他们完成一些 Unix 具有相对优势的有用任务,例如:
- 在给定目录中找出占用空间最大的五个文件夹。
- 报告计算机上重复的 MP3 文件(根据文件内容,而非文件名)。
- 将一个姓和名都被小写的人名列表,进行正确的首字母大写转换。
- 找出所有第二个字母是
x,倒数第二个字母是n的英文单词。 - 将你的麦克风输入通过网络直接传输到另一台电脑的扬声器。
- 将给定目录中所有文件名中的空格替换为下划线。
- 报告来自特定 IP 地址对 Web 服务器的最近十次错误访问。
推荐阅读
- Kernighan 和 Pike 的 The Unix Programming Environment
- Kerrisk 的 The Linux Programming Interface: A Linux and UNIX System Programming Handbook
- Powers、Peek、O'Reilly 和 Loukides 的 Unix Power Tools
- commandlinefu
- Linux Server Hacks
- The single Unix specification
系统管理
有些计算机科学家瞧不起系统管理,认为那是“IT”人员的任务。
这种想法是,计算机科学家可以自学技术员能做的任何事情。
理论上说,这是对的。
然而,这种态度是错误的:计算机科学家必须能够胜任且安全地管理自己的系统和网络。
软件开发中的许多任务,如果不通过系统管理员,执行效率会最高。
具体建议
每个现代计算机科学家都应该能够:
- 安装和管理一个 Linux 发行版。
- 配置和编译 Linux 内核。
- 使用
dig、ping和traceroute来排查网络连接问题。 - 编译和配置像 Apache 这样的 Web 服务器。
- 编译和配置像 bind 这样的 DNS 守护进程。
- 用文本编辑器维护一个网站。
- 剪切和压制网线。
推荐阅读
- Nemeth、Synder、Hein 和 Whaley 的 UNIX and Linux System Administration Handbook
编程语言
编程语言的兴衰如同太阳周期。
但程序员的职业生涯不应如此。
虽然教授与雇主需求相关的语言很重要,但让学生学会如何自学新语言同样重要。
学习如何学习编程语言的最好方法,就是学习多种编程语言和编程范式。
学习第 n 门语言的难度是学习第 (n-1) 门语言难度的一半。
然而,要真正理解编程语言,你必须实现一个。理想情况下,每个计算机科学专业的学生都应该修一门编译器课程。至少,每个计算机科学专业的学生都应该实现一个解释器。
具体语言
以下语言提供了范式和实际应用的合理组合:
- Racket
- C
- JavaScript
- Squeak
- Java
- Standard ML
- Prolog
- Scala
- Haskell
- C++
- 汇编语言
Racket
Racket 作为 Lisp 的一个全功能方言,拥有极其简洁的语法。
对于一小部分学生来说,这种语法是一种障碍。
坦白地说,如果这些学生在思想上存在根本障碍,甚至无法暂时接受一种陌生的语法体系,那么他们就缺乏在计算机科学领域生存所需的思维灵活性。
Racket 强大的宏系统和高阶编程能力彻底消除了数据和代码之间的界限。
如果教得好,Lisp 能解放思想。
推荐阅读
- Felleisen、Findler、Flatt 和 Krishnamurthi 的 How to Design Programs
- The Racket Docs
ANSI C
C 是对硬件的一种简洁而无情的抽象。
在嵌入式系统编程中,C 仍然无与伦比。
学习 C 语言能让你以任何其他语言都无法比拟的方式,深入理解占主导地位的冯·诺依曼架构。
鉴于糟糕的 C 编程在缓冲区溢出安全漏洞的普遍性中扮演了关键角色,程序员学会如何正确地用 C 编程至关重要。
推荐阅读
- Kernighan 和 Ritchie 的 ANSI C
JavaScript
JavaScript 是动态高阶语言(如 Python、Ruby 和 Perl)中流行的语义模型的一个很好的代表。
作为 Web 的原生语言,其实用优势是独一无二的。
推荐阅读
- Flanagan 的 JavaScript: The Definitive Guide
- Crockford 的 JavaScript: The Good Parts
- Herman 的 Effective JavaScript: 68 Specific Ways to Harness the Power of JavaScript
Squeak
Squeak 是 Smalltalk 的现代方言,是最纯粹的面向对象语言。
它传达了“面向对象”的精髓。
推荐阅读
Java
Java 的流行时间会很长,长到我们无法忽视它。
推荐阅读
- Bloch 的 Effective Java
Standard ML
Standard ML 是 Hindley-Milner 系统的一个简洁体现。
Hindley-Milner 类型系统是现代计算领域最伟大(却最鲜为人知)的成就之一。
虽然 Hindley-Milner 中的类型推断在复杂性上是指数级的,但对于人类感兴趣的程序来说,它总是很快。
这个类型系统足够丰富,可以表达复杂的结构不变量。事实上,它如此丰富,以至于类型正确的程序通常没有错误。
推荐阅读
- Paulson 的 ML for the Working Programmer
- Milner、Harper、MacQueen 和 Tofte 的 The Definition of Standard ML
Prolog
虽然应用领域比较小众,但逻辑编程是计算思维的另一种范式。
为了在某些情况下,程序员可能需要在另一种范式中模拟它,逻辑编程是值得理解的。
另一个值得学习的逻辑语言是 miniKanren。miniKanren 强调纯粹的(不允许 cut)逻辑编程。这种约束演变出一种称为关系式编程的逻辑编程风格,并赋予了 Prolog 程序通常不具备的特性。
推荐阅读
Scala
Scala 是函数式和面向对象编程语言的精心融合。Scala 是 Java 本应成为的样子。
它构建在 Java 虚拟机之上,与现有的 Java 代码库兼容,因此,它作为 Java 最有可能的继任者而脱颖而出。
推荐阅读
- Odersky、Spoon 和 Venners 的 Programming in Scala
- Wampler 和 Payne 的 Programming Scala
Haskell
Haskell 是 Hindley-Milner 语系中的皇冠明珠。
Haskell 充分利用了惰性求值,是所有主流编程语言中最接近纯数学编程的。
推荐阅读
- Lipovaca 的 Learn You a Haskell
- O'Sullivan、Goerzen 和 Stewart 的 Real World Haskell
ISO C++
C++ 是一个必要的恶魔。
但是,既然必须教,就必须教全。
特别是,计算机科学专业的学生毕业时,甚至应该掌握模板元编程。
推荐阅读
- Stroustrup 的 The C++ Programming Language
- Vandevoorde 和 Josuttis 的 C++ Templates: The Complete Guide
- Bentley 的 Programming Pearls
汇编语言
任何一种汇编语言都可以。
既然 x86 很流行,那就不妨学它。
学习编译器是学习汇编语言的最佳方式,因为它能让计算机科学家直观地感受到高级代码是如何被转换的。
具体建议
计算机科学家应该理解生成式编程(宏)、词法(和动态)作用域、闭包、续延、高阶函数、动态分派、子类型、模块和函子,以及单子等作为独立于任何特定语法的语义概念。
推荐阅读
- Abelson、Sussman 和 Sussman 的 Structure and Interpretation of Computer Programs
- Queinnec 的 Lisp in Small Pieces
离散数学
计算机科学家必须牢固掌握形式逻辑和证明。通过代数运算和自然演绎进行证明,可以锻炼日常编程任务中常见的推理能力。通过归纳法进行证明,可以锻炼构建递归函数时所用的推理能力。
计算机科学家必须能流利地使用形式化的数学符号,并能对基本的离散结构进行严谨的推理:集合、元组、序列、函数和幂集。
具体建议
对于计算机科学家来说,学习关于以下内容的推理很重要:
- 树
- 图
- 形式语言
- 自动机
学生们应该学习足够的数论知识,以便研究和实现常见的密码学协议。
推荐阅读
- Velleman 的 How to Prove It: A Structured Approach
- Polya 的 How To Solve It
数据结构与算法
学生们当然应该学习常见(或罕见但异常有效)的数据结构和算法。
但是,比了解某个具体的算法或数据结构(这些通常很容易查到)更重要的是,计算机科学家必须理解如何设计算法(例如,贪心策略、动态规划策略),以及如何跨越从理想算法到其具体实现的鸿沟。
具体建议
至少,寻求长期稳定就业的计算机科学家应该了解以下所有内容:
- 哈希表
- 链表
- 树
- 二叉搜索树
- 有向图和无向图
计算机科学家应该准备好实现或扩展一个操作这些数据结构的算法,包括搜索元素、添加元素和删除元素的能力。
为了完整起见,计算机科学家应该同时了解每种算法的命令式和函数式版本。
推荐阅读
- CLRS
- Knuth 的任何一本 Art of Computer Programming 系列
理论
掌握理论是进入研究生院进行研究的先决条件。
当理论能为一个问题提供硬性边界时(或者当它提供了一种规避最初看起来是硬性边界的方法时),它是无价的。
计算复杂性理论可以理直气壮地宣称是整个计算机“科学”中为数不多的真正具有预测性的理论之一。
一个计算机科学家必须知道易处理性和可计算性的边界在哪里。忽视这些限制,在最好的情况下会导致挫败,在最坏的情况下会导致失败。
具体建议
在本科阶段,理论课程应至少涵盖计算模型和计算复杂性。
计算模型应涵盖有限状态自动机、正则语言(和正则表达式)、下推自动机、上下文无关语言、形式文法、图灵机、lambda 演算和不可判定性。
在本科阶段,学生至少应学习足够的复杂性理论,以理解 P、NP、NP-Hard 和 NP-Complete 之间的区别。
为了避免留下错误的印象,学生们应该通过归约到 SAT 和使用现代 SAT 求解器来解决一些 NP 中的大问题。
推荐阅读
- Sipser 的 Introduction to the Theory of Computation
- Papadimitriou 的 Computational Complexity
- Sedgewick 和 Wayne 的 Algorithms
- Cormen、Leiserson、Rivest 和 Stein 的 Introduction to Algorithms
体系结构
没有什么可以替代对计算机体系结构的扎实理解。
计算机科学家应该从晶体管的层面开始向上理解计算机。
对体系结构的理解应涵盖标准的抽象层次:晶体管、门、加法器、多路复用器、触发器、ALU、控制单元、缓存和 RAM。
在可预见的未来,理解 GPU 的高性能计算模型将非常重要。
具体建议
要 在现代系统上获得良好性能,对缓存、总线和硬件内存管理的良好理解至关重要。
为了更好地掌握机器体系结构,学生应该设计并模拟一个小型的 CPU。
推荐阅读
- nand2tetris,它从零开始构建一台计算机。
- Patterson 和 Hennessy 的 Computer Organization and Design
- Drepper 的 "What every programmer should know about memory"
操作系统
任何足够大的程序最终都会变成一个操作系统。
因此,计算机科学家应该了解内核如何处理系统调用、分页、调度、上下文切换、文件系统和内部资源管理。
要获得高性能,对操作系统的良好理解仅次于对编译器和体系结构的理解。
在没有操作系统的嵌入式系统上编程时,理解操作系统(我倾向于广义地解释为包括运行时系统)变得尤为重要。
具体建议
让学生们亲手实践一个真实的操作系统非常重要。有了 Linux 和虚拟化技术,这比以往任何时候都更容易。
为了更好地理解内核,学生们可以:
- 在启动过程中打印 "hello world";
- 设计自己的调度器;
- 修改页面处理策略;
- 创建自己的文件系统。
推荐阅读
- Love 的 Linux Kernel Development
网络
鉴于网络的无处不在,计算机科学家应该对网络协议栈和网络内的路由协议有扎实的理解。
在一个不可靠的传输协议(如 IP)之上构建一个高效、可靠的传输协议(如 TCP)的机制,对计算机科学家来说不应该是魔法,而应该是核心知识。
计算机科学家必须理解协议设计中涉及的权衡——例如,何时选择 TCP,何时选择 UDP。(程序员还需要理解如果他们大规模使用 UDP,对网络拥塞的更大社会影响。)
具体建议
鉴于现代程序员接触网络编程的频率,了解现有标准的协议是很有帮助的,例如:
- 802.3 和 802.11
- IPv4 和 IPv6
- DNS、SMTP 和 HTTP
计算机科学家应该理解数据包冲突解决中的指数退避算法,以及拥塞控制中涉及的加性增、乘性减机制。
每个计算机科学家都应该实现以下内容:
- 一个 HTTP 客户端和守护进程;
- 一个 DNS 解析器和服务器;
- 一个命令行的 SMTP 邮件发送器。
任何学生都不应该在没有用 wireshark 嗅探到他们老师的谷歌搜索查询的情况下通过网络入门课程。
要求所有学生从头开始在 IP 之上实现一个可靠的传输协议可能有点过分,但我可以说,作为一名学生,这对我个人来说是一次变革性的经历。
推荐阅读
- Stevens、Fenner 和 Rudoff 的 Unix Network Programming
安全
一个可悲的事实是,大多数安全漏洞都源于粗心的编程。更可悲的是,许多学校在培训程序员保护代码方面做得很差。
计算机科学家必须了解程序可能被攻破的各种方式。
他们需要培养一种防御性编程的意识——一种思考自己的代码可能如何被攻击的思维方式。
安全是那种最好贯穿整个课程体系的培训:每个学科都应该警告学生其固有的漏洞。
具体建议
至少,每个计算机科学家都需要了解:
- 社会工程学
- 缓冲区溢出
- 整数溢出
- 代码注入漏洞
- 竞争条件
- 权限混淆
一些读者指出,计算机科学家还需要了解基本的 IT 安全措施,例如如何选择真正安全的密码以及如何用 iptables 正确配置防火墙。
推荐阅读
- Kennedy、O'Gorman、Kearns 和 Aharoni 的 Metasploit: The Penetration Tester's Guide
- Anderson 的 Security Engineering
密码学
密码学使我们的许多数字生活成为可能。
计算机科学家应该理解并能够实现以下概念,以及在实现过程中的常见陷阱:
- 对称密钥密码系统
- 公钥密码系统
- 安全哈希函数
- 挑战-响应认证
- 数字签名算法
- 门限密码系统
由于这是密码系统实现中的一个常见错误,每个计算机科学家都应该知道如何为手头的任务获取一个足够随机的数。
至少,正如几乎每一次数据泄露事件所显示的,计算机科学家需要知道如何对密码进行加盐和哈希存储。
具体建议
每个计算机科学家都应该体验一下用自己手写的统计工具破解前现代密码系统密文的乐趣。
RSA 实现起来足够简单,每个人都应该动手实现一次。
每个学生都应该创建自己的数字证书,并在 Apache 中设置 https。(完成这个过程出奇地费力。)
学生还应该编写一个通过 SSL 连接的控制台 Web 客户端。
作为纯粹的实践,计算机科学家应该知道如何使用 GPG;如何为 ssh 使用公钥认证;以及如何加密一个目录或一个硬盘。
推荐阅读
- Ferguson、Schneier 和 Kohno 的 Cryptography Engineering
软件测试
软件测试必须贯穿整个课程体系。
一门软件工程课程可以涵盖测试的基本类型,但没有什么能替代实践这门艺术。
学生的评分应该基于他们提交的测试用例。
我用学生提交的测试用例来测试所有其他学生。
学生们似乎不太关心开发防御性的测试用例,但当涉及到给同学“挖坑”时,他们就会火力全开。
用户体验设计
程序员们常常为其他程序员,或者更糟,为他们自己编写软件。
用户界面设计(或更广泛地说,用户体验设计)可能是计算机科学中最被低估的方面。
即使在教授中也存在一种误解,认为用户体验是一种无法教授的“软”技能。
实际上,现代用户体验设计是基于人因工程学和工业设计中经过实证得出的原则的。
如果说非要学点什么,计算机科学家应该知道,界面需要使执行任何任务的难易程度与该任务的频率乘以其重要性成正比。
从实践角度来看,每个程序员都应该能熟练地用 HTML、CSS 和 JavaScript 设计可用的 Web 界面。
推荐阅读
- Paul Graham 关于 Web 2.0 的文章
- Spolsky 的 "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets"
- Duckett 的 HTML and CSS: Design and Build Websites
- Flanagan 的 JavaScript: The Definitive Guide
可视化
好的可视化是关于以一种让人们能将其感知为信息的方式来呈现数据。这并非易事。
现代世界是数据的海洋,利用人类感知的局部最优值是理解它的关键。
推荐阅读
并行
并行计算又回来了,而且比以往任何时候都更棘手。
不幸的是,要驾驭并行计算,需要对体系结构有深入的了解:多核、缓存、总线、GPU 等等。
而且,需要实践。大量的实践。
具体建议
并行编程的“最终”答案是什么还很不清楚,但一些特定领域的解决方案已经出现。
目前,学生应该学习 CUDA 和 OpenCL。
线程是对并行的一种脆弱的抽象,尤其是在涉及缓存和缓存一致性时。但是,线程很流行且很棘手,所以值得学习。Pthreads 是一个相当可移植的线程库,值得学习。
对于任何对大规模并行感兴趣的人来说,MPI 是一个先决条件。
在原则方面,map-reduce 似乎是持久的。
软件工程
软件工程的原则变化得和编程语言一样快。
一门关于团队软件构建实践的、动手性强的课程,可以让你对这项工作固有的陷阱有一个实际的了解。
一些读者建议,学生应该分成三人小组,领导者的角色在三个不同的项目中轮换。
学习如何攻克和驾驭一个庞大现有代码库是大多数程序员必须掌握的技能,最好是在学校里学,而不是在工作中。
具体建议
所有学生都需要了解像 svn 这样的集中式版本控制系统和像 git 这样的分布式版本控制系统。
当 gdb 和 valgrind 这样的调试工具最终变得必要时,对它们的熟练掌握会大有帮助。
推荐阅读
- Sink 的 Version Control by Example
形式化方法
随着对安全、可靠软件的需求增加,形式化方法有朝一日可能会成为交付此类软件的唯一手段。
目前,软件的形式化建模和验证仍然具有挑战性,但该领域的进展是稳步的:它每年都在变得更容易。
甚至在今天计算机科学专业学生的有生之年,可能会有一天,形式化软件构建成为一项必备技能。
每个计算机科学家都应该至少能比较熟练地使用一种定理证明器。(我认为是哪一种并不重要。)
学习使用定理证明器会立即影响编码风格。
例如,你会本能地对编写一个没有覆盖所有可能性的 match 或 switch 语句感到反感。
而且,在编写递归函数时,定理证明器的使用者会有强烈的冲动去消除无根基的情况。
推荐阅读
图形学与仿真
没有哪个学科比图形学更被“巧妙”所主导。
这个领域被“足够好”所驱动,甚至被其定义。
因此,没有比图形学和仿真更好的方式来教授巧妙的编程或对优化工作的深刻理解了。
我学到的编程技巧中,有一半以上来自我对图形学的研究。
具体建议
简单的光线追踪器可以用不到 100 行代码构建出来。
推导出在一个线框 3D 引擎中执行透视 3D 投影所需的变换,是一种很好的思维训练。
像 BSP 树这样的数据结构和像 z-buffer 渲染这样的算法是巧妙设计的绝佳例子。
在图形学和仿真中,还有更多这样的例子。
推荐阅读
机器人学
机器人学可能是教授入门编程最引人入胜的方式之一。
此外,随着机器人成本的持续下降,一些门槛正在被跨越,这将引发一场个人机器人革命。
对于那些会编程的人来说,难以想象的个人物理自动化程度即将来临。
相关文章
人工智能
即使仅仅因为其对早期计算历史的巨大影响,计算机科学家也应该学习人工智能。
虽然智能机器的最初梦想似乎还很遥远,但人工智能催生了许多实用的领域,如机器学习、数据挖掘和自然语言处理。
推荐阅读
- Russell 和 Norvig 的 Artificial Intelligence
机器学习
除了其卓越的技术价值外,仅“相关性工程师”这一职位的大量招聘空缺就表明,每个计算机科学家都应该掌握机器学习的基础知识。
机器学习加倍强调了理解概率和统计学的必要性。
具体建议
在本科阶段,核心概念应包括贝叶斯网络、聚类和决策树学习。
推荐阅读
- Mitchell 的 Machine Learning
数据库
数据库太普遍、太有用了,不容忽视。
理解驱动数据库引擎的基本数据结构和算法是很有用的,因为程序员经常会在一个更大的软件系统中重新实现一个数据库系统。
关系代数和关系演算在亚图灵计算模型中是杰出的成功案例。
与 UML 建模不同,ER 建模似乎是一种可视化编码软件工件设计和约束的合理机制。
具体建议
一个能够搭建和操作 LAMP 架构的计算机科学家,离创办自己的公司只差一个好点子和大量的辛勤工作。
推荐阅读
- Date 的 SQL and Relational Theory
非特定主题阅读推荐
- Hofstadter 的 Gödel, Escher, Bach
- Nick Black 给硕士生的建议
还有什么?
我的建议受限于我自己知识的盲点。
我在这里没有列出但应该包含的内容是什么?