左耳听风——笔记一:如何学习

技术变现

要去经历大多数人经历不到的,要把学习时间花在那些比较难的地方。
要写文章就要写没有人写过的,或是别人写过,但我能写得更好的。
更重要的是,技术和知识完全是可以变现的。
在学习技术的过程一定要多问自己两个问题:“一,这个技术解决什么问题?为什么别的同类技术做不到?二,为什么是这样解决的?有没有更好的方式?”
会挣钱的人一定是会投资的人。我一直认为,最宝贵的财富并不是钱,而是你的时间,时间比钱更宝贵,因为钱你不用还在那里,而时间你不用就浪费掉了。你把你的时间投资在哪些地方,就意味着你未来会走什么样的路。所以,利用好你的时间,投到一些有意义的地方吧。
1.不断学习,让自己变得稀缺。
2.去高速发展的公司,而不是初创业务未稳定或项目维护期的。
3.提升业务代码编写效率,争取时间学习。
4.关注技术的本质,新技术的出现解决了什么问题和不可替代性。
把宝贵的时间投身于一些实在的私人项目中,一个是让新技术得以实现,再来是为个人影响力做背书。

技术领导力

怎么让自己拥有技术领导力呢?总体来说,是四个方面,具体如下:

  • 扎实的基础技术;
  • 非同一般的学习能力;
  • 坚持做正确的事;
  • 不断得高对自己的要求标准;

怎样才能拥有技术领导力

一、吃透基础技术

吃透基础技术是为了更好地理解程序的运行原理,并基于这些基础技术进化出更优化的产品。

编程部分

C 语言:相对于很多其他高级语言来说,C 语言更接近底层。在具备跨平台能力的前提下,它可以比较容易地被人工翻译成相应的汇编代码。它的内存管理更为直接,可以让我们直接和内存地址打交道。
学习好 C 语言的好处是能掌握程序的运行情况,并能进行应用程序和操作系统编程(操作系统一般是汇编和 C 语言)。要学好 C 语言,你可以阅读 C 语言的经典书籍《C 程序设计语言(第 2 版)》,同时,肯定也要多写程序,多读一些优秀开源项目的源代码。
除了让你更为了解操作系统之外,学习 C 语言还能让你更清楚地知道程序是怎么精细控制底层资源的,比如内存管理、文件操作、网络通信……
这里需要说明的是,我们还是需要学习汇编语言的。因为如果你想更深入地了解计算机是怎么运作的,那么你是需要了解汇编语言的。虽然我们几乎不再用汇编语言编程了,但是如果你需要写一些如 lock free 之类高并发的东西,那么了解汇编语言,就能有助于你更好地理解和思考。

编程范式:各种编程语言都有它们各自的编程范式,用于解决各种问题。比如面向对象编程(C++、Java)、泛型编程(C++、Go、C#)、函数式编程(JavaScript、 Python、Lisp、Haskell、Erlang)等。
学好编程范式,有助于培养你的抽象思维,同时也可以提高编程效率,提高程序的结构合理性、可读性和可维护性,降低代码的冗余度,进而提高代码的运行效率。要学习编程范式,你还可以多了解各种程序设计语言的功能特性。

算法和数据结构:算法(及其相应的数据结构)是程序设计的有力支撑。适当地应用算法,可以有效地抽象问题,提高程序的合理性和执行效率。算法是编程中最最重要的东西,也是计算机科学中最重要的基础。
任何有技术含量的软件中一定有高级的算法和数据结构。比如 epoll 中使用了红黑树,数据库索引使用了 B+ 树……而就算是你的业务系统中,也一定使用各种排序、过滤和查找算法。学习算法不仅是为了写出运转更为高效的代码,而且更是为了能够写出可以覆盖更多场景的正确代码。

系统部分

计算机系统原理:CPU 的体系结构(指令集 [CISC/RISC]、分支预测、缓存结构、总线、DMA、中断、陷阱、多任务、虚拟内存、虚拟化等),内存的原理与性能特点(SRAM、DRAM、DDR-SDRAM 等),磁盘的原理(机械硬盘 [盘面、磁头臂、磁头、启停区、寻道等]、固态硬盘 [页映射、块的合并与回收算法、TRIM 指令等]),GPU 的原理等。
学习计算机系统原理的价值在于,除了能够了解计算机的原理之外,你还能举一反三地反推出高维度的分布式架构和高并发高可用的架构设计。
比如虚拟化内存就和今天云计算中的虚拟化的原理是相通的,计算机总线和分布式架构中的 ESB 也有相通之处,计算机指令调度、并发控制可以让你更好地理解并发编程和程序性能调优……这里,推荐书籍《深入理解计算机系统》(Randal E. Bryant)。

操作系统原理和基础:进程、进程管理、线程、线程调度、多核的缓存一致性、信号量、物理内存管理、虚拟内存管理、内存分配、文件系统、磁盘管理等。
学习操作系统的价值在于理解程序是怎样被管理的,操作系统对应用程序提供了怎样的支持,抽象出怎样的编程接口(比如 POSIX/Win32 API),性能特性如何(比如控制合理的上下文切换次数),怎样进行进程间通信(如管道、套接字、内存映射等),以便让不同的软件配合一起运行等。
要学习操作系统知识,一是要仔细观察和探索当前使用的操作系统,二是要阅读操作系统原理相关的图书,三是要阅读 API 文档(如 man pages 和 MSDN Library),并编写调用操作系统功能的程序。这里推荐三本书《UNIX 环境高级编程》、《UNIX 网络编程》和《Windows 核心编程》。
学习操作系统基础原理的好处是,这是所有程序运行的物理世界,无论上层是像 C/C++ 这样编译成机器码的语言,还是像 Java 这样有 JVM 做中间层的语言,再或者像 Python/PHP/Perl/Node.js 这样直接在运行时解释的语言,其在底层都逃离不了操作系统这个物理世界的“物理定律”。
所以,了解操作系统的原理,可以让你更能从本质理解各种语言或是技术的底层原理。一眼看透本质可以让你更容易地掌握和使用高阶技术。

网络基础:计算机网络是现代计算机不可或缺的一部分。需要了解基本的网络层次结构(ISO/OSI 模型、TCP/IP 协议栈),包括物理层、数据链路层(包含错误重发机制)、网络层(包含路由机制)、传输层(包含连接保持机制)、会话层、表示层、应用层(在 TCP/IP 协议栈里,这三层可以并为一层)。
比如,底层的 ARP 协议、中间的 TCP/UDP 协议,以及高层的 HTTP 协议。这里推荐书籍《TCP/IP 详解》,学习这些基础的网络协议,可以为我们的高维分布式架构中的一些技术问题提供很多的技术方案。比如 TCP 的滑动窗口限流,完全可以用于分布式服务中的限流方案。

数据库原理:数据库管理系统是管理数据库的利器。通常操作系统提供文件系统来管理文件数据,而文件比较适合保存连续的信息,如一篇文章、一个图片等。但有时需要保存一个名字等较短的信息。如果单个文件只保存名字这样的几个字节的信息的话,就会浪费大量的磁盘空间,而且无法方便地查询(除非使用索引服务)。
但数据库则更适合保存这种短的数据,而且可以方便地按字段进行查询。现代流行的数据库管理系统有两大类:SQL(基于 B+ 树,强一致性)和 NoSQL(较弱的一致性,较高的存取效率,基于哈希表或其他技术)。
学习了数据库原理之后便能了解数据库访问性能调优的要点,以及保证并发情况下数据操作原子性的方法。要学习数据库,你可以阅读各类数据库图书,并多做数据库操作以及数据库编程,多观察分析数据库在运行时的性能。

分布式技术架构:数据库和应用程序服务器在应对互联网上数以亿计的访问量的时候,需要能进行横向扩展,这样才能提供足够高的性能。为了做到这一点,要学习分布式技术架构,包括负载均衡、DNS 解析、多子域名、无状态应用层、缓存层、数据库分片、容错和恢复机制、Paxos、Map/Reduce 操作、分布式 SQL 数据库一致性(以 Google Cloud Spanner 为代表)等知识点。
学习分布式技术架构的有效途径是参与到分布式项目的开发中去,并阅读相关论文。

虽然说,你可以在一两年内看完相关的书籍或论文,但是,我想说的是,这些基础技术是需要你用一生的时间来学习的,因为基础上的技术和知识,会随着阅历和经验的增加而有不同的感悟。

二、提高学习能力

所谓学习能力,就是能够很快地学习新技术,又能在关键技术上深入的能力。只有在掌握了上述的基础原理之上,你才能拥有好的学习能力。
怎么提高?

学习的信息源。信息源很重要,有好的信息源就可以更快速地获取有价值的信息,并提升学习效率。常见的信息源有 Google 等搜索引擎,Stack Overflow、Quora 等社区,图书,API 文档,论文和博客等。
这么说吧,如果今天使用中文搜索就可以满足你的知识需求,那么你就远远落后于这个时代了。如果用英文搜索才能找到你想要的知识,那么你才能算跟得上这个时代。而如果说有的问题你连用英文搜索都找不到,只能到社区里去找作者或者其他人交流,那么可以说你已真正和时代同频了。

与高手交流。程序员可以通过技术社区以及参加技术会议与高手交流,也可以通过参加开源项目来和高手切磋。常闻“听君一席话,胜读十年书”便是如此。与高手交流对程序员的学习和成长很有益处,不仅有助于了解热门的技术方向及关键的技术点,更可以通过观察和学习高手的技术思维及解决问题的方式,提高自己的技术前瞻性和技术决策力。
我在 Amazon 的时候,就有人和我说,多和美国的 Principle SDE 以上的工程师交流,无论交流什么,你都会有收获的。其实,这里说的就是,学习这些牛人的思维方式和看问题的角度,这会让你有质的提高。

举一反三的思考。比如,了解了操作系统的缓存和网页缓存以后,你要思考其相同点和不同点。了解了 C++ 语言的面向对象特性以后,思考 Java 面向对象的相同点和不同点。遇到故障的时候,举一反三,把同类问题一次性地处理掉。

不怕困难的态度。遇到难点,有时不花一番力气,是不可能突破的。此时如果没有不怕困难的态度,你就容易打退堂鼓。但如果能坚持住,多思考,多下功夫,往往就能找到出路。绝大多数人是害怕困难的,所以,如果你能够不怕困难,并可以找到解决困难的方法和路径,时间一长,你就能拥有别人所不能拥有的能力。

开放的心态。实现一个目的通常有多种办法。带有开放的心态,不拘泥于一个平台、一种语言,往往能带来更多思考,也能得到更好的结果。而且,能在不同的方法和方案间做比较,比较它们的优缺点,那么你会知道在什么样的场景下用什么样的方案,你就会比一般人能够有更全面和更完整的思路。

三、坚持做正确的事

提高效率的事。你要学习和掌握良好的时间管理方式,管理好自己的时间,能显著提高自己的效率。

自动化的事。程序员要充分利用自己的职业特质,当看见有可以自动化的步骤时,编写程序来自动化操作,可以显著提高效率。

掌握前沿技术的事。掌握前沿的技术,有利于拓展自己的眼界,也有利于找到更好的工作。需要注意的是,有些技术虽然当下很火,但未必前沿,而是因为它比较易学易用,或者性价比高。由于学习一门技术需要花费不少时间,你应该选择自己最感兴趣的,有的放矢地去学习。

知识密集型的事。知识密集型是相对于劳动密集型来说的。基本上,劳动密集型的事都能通过程序和机器来完成,而知识密集型的事却仍需要人来完成,所以人的价值此时就显现出来了。虽然现在人工智能似乎能做一些知识密集型的事(包括下围棋的 AlphaGo),但是在开放领域中相对于人的智能来说还是相去甚远。掌握了领域知识的人的价值依然很高。

技术驱动的事。不仅是指用程序驱动的事,而且还包括一切技术改变生活的事。比如自动驾驶、火星登陆等。就算自己一时用不着,你也要了解这些,以便将来这些技术来临时能适应它们。

四、高标准要求自己

Google 的自我评分卡。Google 的评分卡是在面试 Google 时,要求应聘人对自己的技能做出评估的工具,它可以看出应聘人在各个领域的技术水平。我们可以参考 Google 的这个评分卡来给自己做评估,并通过它来不断地提高对自己的要求。(该评分卡见文末附录)。

敏锐的技术嗅觉。这是一个相对综合的能力,你需要充分利用信息源,GET 到新的技术动态,并通过参与技术社区的讨论,丰富自己了解技术的角度。思考一下是否是自己感兴趣的,能解决哪些实际问题,以及其背后的原因,新技术也好,旧技术的重大版本变化也罢。

强调实践,学以致用。学习知识,一定要实际用一用,可以是工作中的项目,也可以是自己的项目,不仅有利于吸收理解,更有利于深入到技术的本质。并可以与现有技术对比一下,同样的问题,用新技术解决有什么不同,带来了哪些优势,还有哪些有待改进的地方。

Lead by Example。永远在编程。不写代码,你就对技术细节不敏感,你无法做出可以实践的技术决策和方案。

谷歌自我评分卡

  • 0-您不熟悉主题领域。
  • 1-你可以阅读/理解主题领域的最基本方面。
  • 2-能够实施小的变化,理解基本原则,能够在最少的帮助下找出其他细节。
  • 3-在不依赖帮助的情况下,对学科领域的基本熟练程度。
  • 4-你对主题领域和所有日常工作都很满意:
    对于软件领域-能够使用所有基本语言功能(无书)开发媒体程序,了解更多深奥的功能(有书)。
    对于系统领域-了解网络和系统管理的许多基础知识,能够运行小型系统网络,包括恢复、调试和依赖于内部知识的非平凡故障排除。
  • 5-对参考材料的依赖程度更低。在某一领域或某一领域的特定技术方面有更深入的技能。
  • 6-能够从头开始开发大型程序和系统。了解底层细节和内部结构。能够从头开始设计/部署大多数大型分布式系统。
  • 7-您了解并使用大多数不太为人所知的语言特性、技术和相关内部构件。能够自动化大量系统管理。
  • 8-对角落案例、神秘特征、协议和系统(包括“操作理论”)的深入理解。具有设计、部署和拥有非常关键或大型基础设施的能力,并能构建相应的自动化。 9-本可以写一本关于主题领域的书,但没有;与标准委员会合作,确定新的标准和方法。
  • 10-写了一本关于主题领域的书(实际上必须有一本书)。该领域公认的行业专家可能发明了它。

主题领域:

  • TCP/IP网络(OSI堆栈、DNS等)
  • Unix/Linux内部
  • Unix/Linux系统管理
  • 算法和数据结构
  • C
  • C++
  • Python
  • Java
  • Perl
  • Go
  • Shell脚本(sh、Bash、ksh、csh)
  • SQL和/或数据库管理

建议

人生分为两个阶段。

一个是在 20-30 岁,这是打基础的阶段。在这个阶段,我们要的是开阔眼界,把基础打扎实,努力学习和成长。
另一个是在 30-40 岁,这是人生发展的阶段。因为整个社会一定会把社会的重担交给这群人,30-40 岁的人年富力强,既有经验又有精力,还敢想敢干,所以这群人才是整个社会的中流砥柱。在这个阶段,你需要明确自己奋斗的方向,需要做有挑战的事儿,需要提升自己的技术领导力(关于如何发展技术领导力,可以参看我在本专栏的相关文章)。

给自己的建议

  1. 客观地审视自己。找到自己的长处,不断地在自己的长处上发展自我。知道自己几斤几两才能清楚自己适合干什么。不然,目标设置得过高自己达不到,反而让自己难受。在职场上,审视自己的最佳方式,就是隔三差五就出去面试一把,看看自己在市场上能够到什么样的级别。如果你超过了身边的大多数人,你不妨选择得激进一些冒险一些,否则,还是按部就班地来吧。
  2. 确定自己想要什么。如果不确定这个事,你就会纠结,不知道自己要什么,也就不知道自己要去哪里。注意,你不可能什么都要,你需要极端地知道自己要什么。所谓“极端”,就是自己不会受到其它东西或其他人的影响,不会因为这条路上有人退出你会开始怀疑或者迷茫,也不会因为别的路上有人成功了,你就会羡慕。
  3. 注重长期的可能性,而不是短期的功利。20-30 岁应该多去经历一些有挑战的事,多去选择能给自己带来更多可能性的事。多去选择能让自己成长的事,尤其是能让自己开阔眼界的事情。人最害怕的不是自己什么都不会,而是自己不知道自己不会。
  4. 尽量关注自己会得到的东西,而不是自己会失去的东西。因为无论你怎么选,你都会有得有失。(绝大多数人都会考虑自己会失去的,而不是考虑自己会得到的。)
  5. 不要和大众的思维方式一样。因为,绝大多数人都是平庸的,所以,如果你的思维方式和大众一样,这意味着你做出来的选择也会和大众一样平庸。如果你和大众不一样,那么只有两种情况,一个是你比大多数人聪明,一个是你比大多数人愚蠢。

投资自己的时间

  • 花时间学习基础知识,花时间读文档。在参加工作的这 20 年时间里,我发现,很多程序员都把时间都浪费在了查错上。究其根本原因就是基础知识不完整,没有好好地把技术相关的用户文档读完就仓促上手做事情了。其实只要把基础打扎实,认真读一下文档,你会省出很多很多的时间。系统地学习一门技术是非常关键的,所以这个时间是值得投资的。
  • 花时间在解放自己生产力的事上。在自动化、可配置、可重用、可扩展上要多花时间。对于软件开发来说,能自动化的事,就算多花点时间也要自动化,因为下次就不用花时间了。让自己的软件模块可以更灵活地配置和扩展,这样如果有需求变更或是有新需求的时候,可以不用改代码,或者就算要改代码也很容易。
    这里,可能很多人会说不要过度设计,对于这个观点,我既同意,也反对。的确,过度设计不好,但是只要是能在未来节省时间的,宁可这个项目延期,我也会做的。花时间在解放自己的事上是最有意义的了。
  • 花时间在让自己成长的事上。注意,晋升并不代表成长,成长不应该只看在一个公司内,而是要看在行业内,在行业内的成长才是真正的成长。所以,把时间花在能让自己成长,能让自己有更强的竞争力,能让自己有更大的视野,能让自己有更多可能性的事情上。这样的时间投资才是有价值的。
  • 花时间在建立高效的环境上。我相信你和我会有一样的一个习惯,那就“工欲善其事,必先利其器”。我们程序员在做事之前都喜欢把自己的工作环境整理到自己喜欢的状态下。比如使用趁手的开发工具,使用趁手的设备。
    这里,我想把这个事扩大一下,花些时间在影响你身边的人上,比如你的同事,你的产品经理,你的老板,去影响他们,让他们理解你,让他们配合你来建立更好的流程和管理方法。在这个方向上花时间也是很值得的。

规划自己的时间

  • 定义好优先级。无论你写不写出来,你一定都会有一个自己的 to-do list。有 to-do list 并不是什么高深的事。更重要的是,你要知道什么事是重要的,什么事是紧急的,什么事重要但不紧急,什么事又重要又紧急。这有利于你划分优先级。
  • 最短作业优先。对于相同优先级的事,我个人喜欢的是“最短作业优先”的调度算法。理由是,先把可以快速做完的事做完,看到 to-do list 上划掉一个任务,看到任何的数据在减少,对于自己也好,对于老板也好。老板可以看到你的工作进度飞快,一方面有利于为后面复杂的工作争取更多的时间(老板只有在你有 Deliver 的时候才愿意给你更多的时间),另一方面,看到任务列表的减少会让你的心态更为积极。
    而反过来,你花太多的时间在长作业上,长作业通常很容易出现“意外情况”让你花更多的时间,但此时你发现还有很多别的事没有做,这会让你产生焦虑感,产生更多的压力,进而导致更慢的生产效率。
  • 想清楚再做。我发现很多时候,我们没有想清楚就开干了,边干边想,这样的工作方式其实很糟糕。你会发现,如果你没有想清楚,你总是要对已完成的工作进行返工,返工好几次,其实是非常浪费时间的。
    所以,对于一些没想清楚的事,或是自己不太有信心的事,还是先看看有没有已有的成熟解决方案,或是找更牛的人来给你把把关,帮你出出主意,看看有没有更好、更简单的方式。
  • 关注长期利益规划。要多关注长远可以节省多少时间,而不是当前会花费多少时间。长期成本会比短期成本大得多。所以,宁可在短期延期,也不要透支未来。这里的逻辑是,工作上的事你永远也做不完的,长痛不如短痛。
    我一年要做 10 个项目,我宁可第 1 或第 2 个项目被老板骂,但是我可以赢得后面 8 个项目,从后面 8 个项目上把之前失去的找回来。而如果反过来的话,我虽然一开始得到了老板的信任,但是后面越来越玩不动,最终搬起一块大石头砸了自己的脚。而且,不关注长远利益的人,基本上来说也是很难有成长的。
    也就是说,你要学会规划自己的行动计划,不是短期的,而是一个中长期的。我个人建议是按季度来规划,这个季度做什么,达到什么目标,一年往前走四步,而不是只考虑眼下。

高效学习

端正学习态度

学习是一件“逆人性”的事,就像锻炼身体一样,需要人持续付出,会让人感到痛苦,并随时想找理由放弃。
大部分人都认为自己爱学习,但是:

  • 他们都是只有意识没有行动,他们是行动力不足的人。
  • 他们都不知道自己该学什么,他们缺乏方向和目标
  • 他们都不具备自主学习的能力没有正确的方法和技能
  • 更要命的是,他们缺乏实践和坚持

主动学习和被动学习

  • 被动学习:如听讲、阅读、视听、演示,学习内容的平均留存率为 5%、10%、20% 和 30%。
  • 主动学习:如通过讨论、实践、教授给他人,会将原来被动学习的内容留存率从 5% 提升到 50%、75% 和 90%。

所以,学习不是努力读更多的书,盲目追求阅读的速度和数量,这会让人产生低层次的勤奋和成长的感觉,这只是在使蛮力。要思辨,要践行,要总结和归纳,否则,你只是在机械地重复某件事,而不会有质的成长的。

浅度学习和深度学习

老实说,对于当前这个社会:

  • 大多数人的信息渠道都被微信朋友圈、微博、知乎、今日头条、抖音占据着。这些信息渠道中有营养的信息少之又少
  • 大多数公司都是实行类似于 996 这样的加班文化,在透支和消耗着下一代年轻人,让他们成长不起来。
  • 因为国内互联网访问不通畅,加上英文水平受限,所以,大多数人根本没法获取到国外的第一手信息
  • 快餐文化盛行,绝大多数人都急于速成,心态比较浮燥,对事物不求甚解

所以,你看,在这种环境下,你根本不需要努力的。你只需要踏实一点,像以前那样看书,看英文资料,你只需要正常学习,根本不用努力,就可以超过你身边的绝大多数人。

只要你注意观察,就会发现,少数的精英人士,他们在训练自己获取知识的能力,他们到源头查看第一手的资料,然后,深度钻研,并通过自己的思考后,生产更好的内容。
而绝大部分受众享受轻度学习,消费内容

怎样进行深度学习呢?
  • 高质量的信息源和第一手的知识。 -> 官方文档
  • 把知识连成地图,将自己的理解反述出来。
  • 不断地反思和思辨与不同年龄段的人讨论
  • 举一反三,并践行之,把知识转换成技能
学习有三个步骤
  • 知识采集。信息源是非常重要的,获取信息源头、破解表面信息的内在本质、多方数据印证,是这个步骤的关键。 (一手资料 官方文档)
  • 知识缝合。所谓缝合就是把信息组织起来,成为结构体的知识。这里,连接记忆,逻辑推理,知识梳理是很重要的三部分。
  • 技能转换。通过举一反三、实践和练习,以及传授教导,把知识转化成自己的技能。这种技能可以让你进入更高的阶层。

源头、原理和知识地图

挑选知识和信息源

信息源要有下面几个特质。

  • 应该是第一手资料,不是被别人理解过、消化过的二手资料。尤其对于知识性的东西来说,更是这样。应该是原汁原味的,不应该是被添油加醋的。
  • 应该是有佐证、有数据、有引用的,或是有权威人士或大公司生产系统背书的资料。应该是被时间和实践检验过的,或是小心求证过的,不是拍脑袋野路子或是道听途说出来的资料。
  • 应该是加入了一些自己的经验和思考,可以引发人深思的,是所谓信息的密集很大的文章。

对于一个学习者来说,找到优质的信息源可以让你事半功倍。一方面,就像找到一本很好的武林秘籍一样,而不是被他人翻译过或消化过的,也不会有信息损失甚至有错误信息会让你走火入魔。另一方面,你需要的不只有知识和答案,更重要的是掌握学习的方法和技能。你要的是“渔”,而不是“鱼”。

注重基础和原理

《程序员练级攻略》一文中,我用了很大的篇幅给出了学习基础技术的路径。只要你努力学习那些基础知识,了解了其中的原理,就会发现这世界上的很多东西是大同的。

举个例子,如果你学习过底层的 Socket 编程,了解多路复用和各种 I/O 模型的话(select, poll, epoll, aio, windows completion port, libevent 等),那么,对于 Node.js、Java NIO、Nginx、C++ 的 ACE 框架等这些中间件或是编程框架,你就会发现,无论表现形式是什么样的,其底层原理都是一个样的。
无论是 JVM 还是 Node,或者是 Python 解释器里干了什么,它都无法逾越底层操作系统 API 对“物理世界”的限制。而当你了解了这个底层物理世界以后,无论那些技术玩成什么花样,它们都无法超出你的掌控(这种感觉是很爽的)。

再举一个例子,当学了足够多的语言,并有了丰富的实践后,你开始对编程语言的各种编程范式或是控制流有了原理上的了解,这时再学一门新语言的话,你会发现自己学得飞快。就像我 2010 年学习 Go 语言一样,除了那些每个语言都有的 if-else、 for/while-loop、function 等东西以外,我重点在看的就是,出错处理是怎么玩的?内存管理是怎么玩的?数据封装和扩展怎么玩的?多态和泛型怎么搞的?运行时识别和反射机制是怎么玩的?并发编程怎样玩?……

最最关键的是,这些基础知识和原理性的东西和技术,都是经历过长时间的考验的,所以,这些基础技术也有很多人类历史上的智慧结晶,会给你很多启示和帮助。

比如:TCP 协议的状态机,可以让你明白,如果你要设计一个异步通信协议,状态机是一件多么重要的事,还有 TCP 拥塞控制中的方式,让你知道,设计一个以响应时间来限流的中件间是什么样的。

当学习算法和数据结构到一定程度的时候,你就会知道,算法不仅对于优化程序很重要,而且,会让你知道,该如何设计数据结构和算法来让程序变得更为健壮和优雅。

有时候,学习就像拉弓蓄力一样,学习基础知识感觉很枯燥很不实用,工作上用不到,然而
学习这些知识是为了未来可以学得更快。基础打牢,学什么都快,而学得快就会学得多,学得多,就会思考得多,对比得多,结果是学得更快……这种感觉,对于想速成的人来说,很难体会。
这里我想再次强调一下,请一定要注重基础知识和原理上的学习!

使用知识图

联想记忆法

比如,在学习 C++ 的时候,面对《C++ Primer》这种厚得不行的书,我就使用联想记忆法。

  • 第一部分是 C++ 是用来解决 C 语言的问题的,那么 C 语言有什么问题呢?指针、宏、错误处理、数据拷贝…… C++ 用什么技术来解决这些问题呢?
  • 第二部分是 C++ 的面向对象特性:封装、继承、多态。封装,让我想到了构造函数、析构函数等。构造函数让我想到了初始化列表,想到了默认构造函数,想到了拷贝构造函数,想到了 new……多态,让我想到了虚函数,想到了 RTTI,RTTI 让我想到了 dynamic_cast 和 typeid 等。
  • 第三部分是 C++ 的泛型编程。我想到了 template,想到了操作符重载,想到了函数对象,想到 STL,想到数据容器,想到了 iterator,想到了通用算法,等等。

于是,我通过“顺藤摸瓜”的方式,从知识树的主干开始做广度或是深度遍历,于是我就得到了一整棵的知识树。这种“顺藤摸瓜”的记忆方式让我记住了很多知识。最重要的是,当出现一些我不知道的知识点时,我就会往这棵知识树上挂,而这样一来,也使得我的学习更为系统和全面。

这种画知识图的方式可以让你从一个技术最重要最主干的地方出发开始遍历所有的技术细节,也就是画地图的方式。如果你不想在知识的海洋中迷路,你需要有一份地图,所以,学习并不是为了要记忆那些知识点,而是为了要找到一个知识的地图,你在这个地图上能通过关键路径找到你想要的答案。

深度,归纳和坚持实践

系统地学习

学习模板:
在学习某个技术的时候,我除了会用到上篇文章中提到的知识图,还会问自己很多个为什么。于是,我形成了一个更高层的知识脑图。下面我把这这个方法分享出来。当然学习一门技术时,Go 语言也好,Docker 也好,我都有一个学习模板。只有把这个学习模板中的内容都填实了,我才罢休。这个模板如下。

  1. 这个技术出现的背景、初衷和要达到什么样的目标或是要解决什么样的问题。 这个问题非常关键,也就是说,你在学习一个技术的时候,需要知道这个技术的成因和目标,也就是这个技术的灵魂。如果不知道这些的话,那么你会看不懂这个技术的一些设计理念。
  2. 这个技术的优势和劣势分别是什么,或者说,这个技术的 trade-off 是什么。 任何技术都有其好坏,在解决一个问题的时候,也会带来新的问题。另外,一般来说,任何设计都有 trade-off(要什么和不要什么),所以,你要清楚这个技术的优势和劣势,以及带来的挑战。
  3. 这个技术适用的场景。 任何技术都有其适用的场景,离开了这个场景,这个技术可能会有很多槽点,所以学习技术不但要知道这个技术是什么,还要知道其适用的场景。没有任何一个技术是普适的。注意,所谓场景一般分别两个,一个是业务场景,一个是技术场景。
  4. 技术的组成部分和关键点。
    这是技术的核心思想和核心组件了,也是这个技术的灵魂所在了。学习技术的核心部分是快速掌握的关键。
  5. 技术的底层原理和关键实现。 任何一个技术都有其底层的关键基础技术,这些关键技术很有可能也是其它技术的关键基础技术。所以,学习这些关键的基础底层技术,可以让你未来很快地掌握其它技术。可以参看我在 CoolShell 上写的 Docker 底层技术那一系列文章。
  6. 已有的实现和它之间的对比。 一般来说,任何一个技术都会有不同的实现,不同的实现都会有不同的侧重。学习不同的实现,可以让你得到不同的想法和思路,对于开阔思维,深入细节是非常重要的。

基本上来说,如果你按照我上面所提的这 6 大点来学习一门技术,你一定会学习到技术的精髓,而且学习的高度在一开始就超过很多人了。如果你能这样坚持 2-3 年,我相信你一定会在某个领域成为炙手可热的佼佼者。

举一反三

  • 联想能力。 这种能力的锻炼需要你平时就在不停地思考同一个事物的不同的用法,或是联想与之有关的其他事物。对于软件开发和技术学习也一样。
  • 抽象能力。 抽象能力是举一反三的基本技能。平时你解决问题的时候,如果你能对这个问题进行抽象,你就可以获得更多的表现形式。抽象能力需要找到解决问题的通用模型,比如数学就是对现实世界的一种抽象。只要我们能把现实世界的各种问题建立成数据模型(如,建立各种维度的向量),我们就可以用数学来求解,这也是机器学习的本质。
  • 自省能力。 所谓自省能力就是自己找自己的难看。当你得到一个解的时候,要站在自己的对立面来找这个解的漏洞。有点像左右手互博。这种自己和自己辩论的能力又叫思辨能力。将自己分裂成正反方,左右方,甚至多方,站在不同的立场上来和自己辩论,从而做到不漏过一个 case,从而获得完整全面的问题分析能力。

在这方面,我对自己的训练如下。

  • 对于一个场景,制造出各种不同的问题或难题。
  • 对于一个问题,努力寻找尽可能多的解,并比较这些解的优劣。
  • 对于一个解,努力寻找各种不同的测试案例,以图让其健壮。

总结和归纳

学习的开始阶段,可以不急于总结归纳,不急于下判断,做结论,而应该保留部分知识的不确定性,保持对知识的开放状态。

当对整个知识的理解更深入,自己站的位置更高以后,总结和归纳才会更有条理。总结归纳更多是在复习中对知识的回顾和重组,而不是一边学习一边就总结归纳。

把你看到和学习到的信息,归整好,排列好,关联好,总之把信息碎片给结构化掉,然后在结构化的信息中,找到规律,找到相通之处,找到共同之处,进行简化、归纳和总结,最终形成一种套路,一种模式,一种通用方法。

实践出真知

实践是很累很痛苦的事,但只有痛苦才会让人反思,而反思则是学习和改变自己的动力。Grow up through the pain, 是非常有道理的。

坚持不懈

坚持不懈是一句正确的废话。前段时间,我在我的读者群中发起了一个名为 ARTS 的活动。每人每周写一个 ARTS:Algorithm 是一道算法题,Review 是读一篇英文文章,Technique/Tips 是分享一个小技术,Share 是分享一个观点。我希望大家可以坚持一年,但是我也相信,能够坚持下来的人一定很少,绝大多数人都是虎头蛇尾的,但是我依然相信会有人坚持下来的。

坚持是一件反人性的事,所以,它才难能可贵,也更有价值。我从 2003 年写 blog 到今天 15 年了,看书学习写代码,我都会一点一点的坚持。人不怕笨,怕的是懒,怕的是找到各种理由放弃。

这里,我想鼓励一下你。现在很多国外的在线视频课都是 3-5 分钟一节课,一共 20 节课,总时长不到两个小时。然而,你会发现,能坚持看完的不到千分之一。当年 Leetcode 只有 151 道题的时候,一共有十几万人上来做题,但全部做完的只有十几个,万分之一。所以,只要你能坚持,就可以超过这个世界上绝大多数人。想一想,如果全中国有 100 万个程序员,只要你能坚持学习技术 2-3 年,你就可以超过至少 99 万人了(可能还更多)。

当然,坚持也不是要苦苦地坚持,有循环有成就感的坚持才是真正可以持续的。 所以, 一方面你要把你的坚持形成成果晒出来,让别人来给你点赞,另一方面,你还要把坚持变成一种习惯,就像吃饭喝水一样,你感觉不到太多的成本付出。 只有做到这两点,你才能够真正坚持。

如何阅读文档和学习源码

阅读文档还是学习源码?

如果你想知道人为什么要这么搞,那么应该去看书 (像 Effective C++、Code Complete、Design Pattern、Thinking in Java 等),看文档。

如果你要知道让机器干了什么?那你应该看代码! (就像 Linus 去看 zlib 的代码来找性能问题。)

如果你想了解一种思想,一种方法,一种原理,一种思路,一种经验,恐怕,读书和读文档会更有效率一些,因为其中会有作者的思路描述。 像 Effective C++ 之类的书,里面有很多对不同用法和设计的推敲,TCP/IP 详解里面也会有对 TCP 算法好坏的比较……这些思维方式能让你对技术的把握力更强,而光看代码很难达到这种级别。(现在你知道什么样的书是好书了吧 😉)

如果你想了解的就是具体细节,比如某协程的实现,某个模块的性能,某个算法的实现 ,那么你还是要去读代码的,因为代码中会有更具体的处理细节(尤其是对于一些 edge case 或是代码技巧方面的内容)。

如何阅读源代码

首先,在阅读代码之前,我建议你需要有下面的这些前提再去阅读代码,这样你读起代码来会很顺畅。

  • 基础知识。相关的语言和基础技术的知识。
  • 软件功能。你先要知道这个软件完成的是什么样的功能,有哪些特性,哪些配置项。你先要读一遍用户手册,然后让软件跑起来,自己先用一下感受一下。
  • 相关文档。读一下相关的内部文档,Readme 也好,Release Notes 也好,Design 也好,Wiki 也好,这些文档可以让你明白整个软件的方方面面。如果你的软件没有文档,那么,你只能指望这个软件的原作者还在,而且他还乐于交流。
  • 代码的组织结构。也就是代码目录中每个目录是什么样的功能,每个文档是干什么的。如果你要读的程序是在某种标准的框架下组织的,比如:Java 的 Spring 框架,那么恭喜你,这些代码不难读了。

接下来,你要了解这个软件的代码是由哪些部分构成的,我在这里给你一个列表,供你参考。

  1. 接口抽象定义。任何代码都会有很多接口或抽象定义,其描述了代码需要处理的数据结构或者业务实体,以及它们之间的关系,理清楚这些关系是非常重要的。
  2. 模块粘合层。我们的代码有很多都是用来粘合代码的,比如中间件(middleware)、Promises 模式、回调(Callback)、代理委托、依赖注入等。这些代码模块间的粘合技术是非常重要的,因为它们会把本来平铺直述的代码给分裂开来,让你不容易看明白它们的关系。
  3. 业务流程。这是代码运行的过程。一开始,我们不要进入细节,但需要在高层搞清楚整个业务的流程是什么样的,在这个流程中,数据是怎么被传递和处理的。一般来说,我们需要画程序流程图或者时序处理图。
  4. 具体实现。了解上述的三个方面的内容,相信你对整个代码的框架和逻辑已经有了总体认识。这个时候,你就可以深入细节,开始阅读具体实现的代码了。对于代码的具体实现,一般来说,你需要知道下面一些事实,这样有助于你在阅读代码时找到重点。
    • 代码逻辑。代码有两种逻辑,一种是业务逻辑,这种逻辑是真正的业务处理逻辑;另一种是控制逻辑,这种逻辑只是用控制程序流转的,不是业务逻辑。比如:flag 之类的控制变量,多线程处理的代码,异步控制的代码,远程通讯的代码,对象序列化反序列化的代码等。这两种逻辑你要分开,很多代码之所以混乱就是把这两种逻辑混在一起了(详情参看《编程范式游记》)。
    • 出错处理。根据二八原则,20% 的代码是正常的逻辑,80% 的代码是在处理各种错误,所以,你在读代码的时候,完全可以把处理错误的代码全部删除掉,这样就会留下比较干净和简单的正常逻辑的代码。排除干扰因素,可以更高效地读代码。
    • 数据处理。只要你认真观察,就会发现,我们好多代码就是在那里倒腾数据。比如 DAO、DTO,比如 JSON、XML,这些代码冗长无聊,不是主要逻辑,可以不理。
    • 重要的算法。一般来说,我们的代码里会有很多重要的算法,我说的并不一定是什么排序或是搜索算法,可能会是一些其它的核心算法,比如一些索引表的算法,全局唯一 ID 的算法、信息推荐的算法、统计算法、通读算法(如 Gossip)等。这些比较核心的算法可能会非常难读,但它们往往是最有技术含量的部分。
    • 底层交互。有一些代码是和底层系统的交互,一般来说是和操作系统或是 JVM 的交互。因此,读这些代码通常需要一定的底层技术知识,不然,很难读懂。
  5. 运行时调试。很多时候,代码只有运行起来了,才能知道具体发生了什么事,所以,我们让代码运行进来,然后用日志也好,debug 设置断点跟踪也好。实际看一下代码的运行过程,是了解代码的一种很好的方式。

总结一下,阅读代码的方法如下:

  • 一般采用自顶向下,从总体到细节的“剥洋葱皮”的读法。
  • 画图是必要的,程序流程图,调用时序图,模块组织图……
  • 代码逻辑归一下类,排除杂音,主要逻辑才会更清楚。
  • debug 跟踪一下代码是了解代码在执行中发生了什么的最好方式。

枯燥的学习

如何面对枯燥的知识

我列举我的这个学习过程,就是想说,如果你发现有些知识太过于枯燥,那么可以通过下面的方法解决。

  • 这个知识对于你来说来太高级了,你可能不知道能用在什么地方
  • 人的认知是从感性认识向理性认识转化的,所以,你可能要先去找一下应用场景学点更实用的,再回来学理论
  • 学习需要有反馈,有成就感,带着相关问题去学习会更好。
  • 当然,找到牛人来给你讲解,也是一个很不错的手段。

如何面对大量知识

看过《程序员练级攻略》的朋友们,一定会有这样的疑问,东西太多了,怎么学。我给你的建议是,一点一点学,一口一口吃。你可以使用我前面说过的那些方法,注重基础,画知识图,多问为什么,多动手,然后坚持住,哪怕你每周就学一个知识点,你一年也可以学到 50 个知识点。只要你在进步,总有一天可以把这些知识学到手的。

当然,你的目的不是学完这些知识,因为学无止境,你永远也学不完,所以你在学习时,一定不要学在表面上,一定要学到本质,学到原理上,那些东西是不容易变的,也是经得住时间考验的。把学习当成投资,这是这个世界上回报最好的投资。

带着问题去学习,带着要解决的东西去学习,带着挑战去学习,于是每当你解决了一个问题,做了一个功能,完成了一个挑战,你就会感到兴奋和有成就感。这样,你也就找到了源源不断的学习驱动力。

把你学习的心得、过程、笔记、代码分享出来,找到和你一同学习的人,因为一个人长跑很辛苦,有人同行就会好很多,就算没有人同行,你的读者,你的观众也会为你鼓掌加油,这些也是让你持续前行的动力。

认真阅读文档

我发现很多技术问题都是出在技术人员不认真读技术手册上。

用户手册(User Manual)一定要好好地读一读,很多很多提示都在里面了,这是让你可以少掉很多坑的法宝。比如:Unix 和 Linux 的 man,Docker 和 Kubernetes 的官方文档,Git 的操作文档……你的很多很多问题的答案都在这些文档中。

其它几个实用的技巧

  1. 用不同的方式来学习同一个东西。比如:通过看书,听课,创建脑图,写博客,讲课,解决实际问题,等等。
  2. 不要被打断。被打断简直就是学习的天敌,所以,你在学习的时候,最好把手机设置成勿扰模式放在一边,然后把电脑上的所有通知也关掉,最好到一个别人找不到你的地方。
  3. 总结压缩信息。当你获得太多的信息时,你需要有一个“压缩算法”。我常用的压缩算法是只关心关键点,所以,你需要使用表格、图示、笔记或者脑图来帮助你压缩信息
  4. 把未知关联到已知。把你新学的知识点关联到已知的事物上来。比如,你在学习 Go 语言,你就把一些知识关联到自己已经学过的语言上比如 C 和 Java。通过类比,你会学得更扎实,也会思考得更多。
  5. 用教的方式来学习。你想想,如果你过几天要在公开场合对很多人讲一个技术,那么这个压力会让你学得更好。因为要教给别人,所以,这么高的标准需要你不但要把自己已掌握的东西学好,还要把周边的也一并学了,才可能做到百问不倒。你才敢去教别人,不是么?(试试教 6 岁的孩子编程,如果你掌握了这种技能,那么你一定是把知识吃得非常透彻了。)
  6. 学以致用。把学到的东西用起来,没有什么比用起来能让你的知识更巩固的了。在实践中,你才会有更为真实的体会,你才会遇到非常细节和非常具体的问题,这些都会让你重新思考,或深化学习。
  7. 不要记忆。聪明的人不会记忆知识的,他们会找方法,那些可以推导出知识或答案的方法。这也是为什么外国人特别喜欢方法论。
  8. 多犯错误。犯错会让你学得到更多,通过错误总结教训,你会比没有犯过错的人体会得更深。但是千万不要犯低级错误,也不要同一个错误犯两次。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值