├── .DS_Store ├── 01_开篇词 └── README.md ├── 02_零基础启蒙 └── README.md ├── 03_正式入门 └── README.md ├── 04_程序员修养 └── README.md ├── 05_编程语言 └── README.md ├── 06_理论学科 └── README.md ├── 07_系统知识 └── README.md ├── 08_软件设计 └── README.md ├── 09_Linux系统、内存和网络 └── README.md ├── 10_异步IO模型和Lock-Free编程 └── README.md ├── 11_Java底层知识 └── README.md ├── 12_数据库 └── README.md ├── 13_分布式架构入门 └── README.md ├── 14_分布式架构经典图书和论文 ├── README.md └── Transaction-Across-DataCenter.jpeg ├── 15_分布式架构工程设计 └── README.md ├── 16_微服务 └── README.md ├── 17_容器化和自动化运维 └── README.md ├── 18_机器学习和人工智能 └── README.md ├── 19_前端基础和底层原理 └── README.md ├── 20_前端性能优化和框架 └── README.md ├── 21_UI和UX设计 └── README.md ├── 22_技术资源集散地 └── README.md ├── 23_编程范式游记-起源 ├── README.md ├── pic_1.png └── pic_2.png ├── 24_编程范式游记-泛型编程 └── README.md ├── 25_编程范式游记-类型系统 └── README.md ├── 26_编程范式游记-函数式编程 ├── README.md ├── pic_1.png ├── pic_2.png └── pic_3.png ├── 27_编程范式游记-修饰器模式 └── README.md ├── 28_编程范式游记-面向对象编程 ├── README.md ├── pic_1.png ├── pic_2.jpeg ├── pic_3.jpeg └── pic_4.jpeg ├── 29_编程范式游记-基于原型的编程范式 ├── README.md ├── pic_1.png └── pic_2.png ├── 30_编程范式游记-Go语言的委托模式 ├── .DS_Store └── README.md ├── 31_编程范式游记-编程的本质 ├── README.md ├── pic_1.png └── pic_2.png ├── 32_编程范式游记-逻辑编程 ├── README.md └── pic_1.png ├── 33_编程范式游记-程序世界里的编程范式 ├── README.md ├── pic_1.png ├── pic_2.png ├── pic_3.png ├── pic_4.png └── pic_5.png ├── LICENSE └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/.DS_Store -------------------------------------------------------------------------------- /01_开篇词/README.md: -------------------------------------------------------------------------------- 1 | # 开篇词 2 | 3 | 2011 年,我在 [CoolShell](https://coolshell.cn/) 上发表了 [《程序员技术练级攻略》](https://coolshell.cn/articles/4990.html)一文,收到了很多读读者的追捧,同时,这几年时间里,我还陆续收到了一些人的反馈,说跟着这篇文章找到了不错的工作,他们希望我把这篇文章更新一下,因为毕竟行业的变化很快。 4 | 5 | 是的,**老实说,抛开这几年技术的更新迭代不说,那篇文章写得也不算特别系统,同时标准也有点低,当时是给一个想要入门的朋友写的。所以,非常有必要从头更新一下《程序员练级攻略》这一主题**。 6 | 7 | ## 前言导读 8 | 9 | 升级版的《程序员练级攻略》会比 Coolshell 上的内容更多,也更专业。这篇文章有【入门篇】、【修养篇】、【专业基础篇】、【软件设计篇】、【高手成长篇】五大篇章。它们会帮助你从零开始,一步步地,系统地,完成从陌生到熟悉,到理解掌握,从编码到设计再到架构,从码农到程序员再到工程师再到架构师的进阶,实现从普通到精通到卓越的完美转身…… 10 | 11 | 在入门篇中,我先推荐的是把 Python 和 JavaScript 作为入门语言,并给出了相应的学习资源和方法。Python 语法比较简单,有大量的库和语法糖,是零基础的人学习编程的不二之选。而 JavaScript 是前端语言,更容易让你获得编程的成就感。 12 | 13 | 随后,我们需要学习使用操作系统 Linux、编程工具 Visual Studio Code 等入门必学内容。Web 互联网作为第三次工业革命信息化浪潮中最大的发明,也是每个程序员都不能错过的。而学习编程还是要多多动手,因此我给出了 Web 编程入门的学习要点,并给出了一个实践项目,帮助你理解和巩固所学的内容。 14 | 15 | 如果你跟着我的这个教程走过来,并能自己去解决遇到的问题,那么,我相信你能够做一点东西了,而且你还可能会对编程非常感兴趣了。但是你千万不要以为自己已经入门了。我只是用这些内容给你一些成就感,并激发你持续学习的兴趣。 16 | 17 | 正式入门,我推荐的语言是 Java,因为我认为,它是所有语言里综合实力最强的。随后,推荐了更为专业实用的编程工具,如编程的 IDE、版本管理工具 Git、调试前端程序和数据库设计工具等,并且给出了一个实践项目。我同时设置了业务和技术两方面的需求,跟着做一遍,相信你对学习编程会有更多的理解和感悟。 18 | 19 | 接下来,我要带你进入更为专业更为复杂的编程世界中。进入之前,我们需要树立正确的三观和心态,这对于程序员来说至关重要。这就好像民工建筑队和专业的工程队的区别,就好像小作坊和工厂的差别,他们并不仅仅是差别在技能和技术上,更是差别在做事的心态和三观上。 20 | 21 | 因此,在学习专业的软件开发知识之前,我们来谈谈**程序员修养**。它看似与程序员练级关系不大,实际上却能反映出程序员的工程师特质和价值观,决定了这条路你到底能走多远,是精髓所在。**有修养的程序员才可能成长为真正的工程师和架构师,而没有修养的程序员只能沦为码农,这是码农和工程师的关键区分点**。 22 | 23 | 在“修养篇”,我给出了一些相关的学习资料,并指出了我认为比较重要的几个方面:英文能力、提问的能力、写代码的修养、安全防范意识、软件工程和上线规范、编程规范等。这些能力的训练和培养将为后续的学习和发展夯实基础。 24 | 25 | 此时,相信你已经迫不及待地想进入**专业基础篇**了。这部分内容主要涵盖编程语言、理论学科和系统知识三方面知识。在编程语言方面,推荐学习 C、C++ 和 Java 这三个工业级的编程语言。理论学科方面,需要学习算法、数据结构、网络模型、计算机原理等内容。系统知识方面会讲述 Unix/Linux、TCP/IP、C10K 挑战等专业的系统知识。最后给出了你可以选择的几大从业方向。 26 | 27 | * 如果你对操作系统、文件系统、数据库、网络等比较感兴趣,那么可以考虑从事底层方面的工作。 28 | * 如果对分布式系统架构、微服务、DevOps、Cloud Native 等有热情,那么可以从事架构方面的工作。 29 | * 如果是对大数据、机器学习、人工智能等比较关注,那么数据领域可以成为你一展身手的地方。 30 | * 如果你对用户体验或者交互等更感兴趣,那么前端工程师也是个不错的选择。 31 | * 此外,安全开发、运维开发、嵌入式开发等几大方向中,也为你提供了丰富多彩的发展空间。 32 | 33 | 以我之见,该如何选择应该完全遵从于你的本心,你更愿意在哪个领域里持续奋斗和学习。这个答案,在你的手中,在你的心中。**这里我只想和你说两个观点:各种技术方向不是鱼和熊掌,是可以兼得的;很多技术是相通的,关键是你是学在表面还是深入本质。** 34 | 35 | **软件设计**能力是每个程序员都需要具备的基本素质。我结合各主流语言讲述了泛型编程、函数式编程、面向对象编程等多种编程范式,分享了 DRY- 避免重复原则、KISS- 简单原则、迪米特法则(又称“最少知识原则”)、 面向对象的 S.O.L.I.D 原则等等多个经典的软件设计原则。 36 | 37 | 同时,给出了软件设计领域的一些重要的学习资料。**软件设计是工程师非常重要的能力,这里描述了软件工程自发展以来的各种设计方法,这是从工程师通往架构师的必备技能。** 38 | 39 | 登峰造极,是每个武林高手都渴望达到的境界,对于每个有理想有追求的程序员也是如此。因此,我特意在《程序员练级攻略(2018)》这一系列内容的最后设置了**高手成长篇**。 40 | 41 | 相较前面的内容,这部分内容相当全面和丰富,涵盖系统、数据库、分布式架构、微服务、容器化和自动化运维、机器学习、前端方向和技术论文等几方面内容,而且深度一下子拔高了好几个数量级。 42 | 43 | 同时,这也是我留给你的再一次做选择的机会,平凡还是卓越?自在悠闲,还是猛啃书本,不破楼兰终不还?还是遵循你内心的选择吧。偷偷地告诉你,我选择的是后者。 44 | 45 | 你应该不难看出这一系列文章比我在 CoolShell 上的那一篇更为专业,标准也会更高,当然,难度也会更大。但是,也会让你有更坚固的技术基础,并能有更高更广泛的提高。 46 | 47 | 通过这一系列文章,我主要想回答以下几个问题。 48 | 49 | * **理论和现实的差距**。你是否觉得自己从学校毕业的时候只做过小玩具一样的程序?走入职场后哪怕没有什么经验也可以把文中提到的这些课外练习走一遍。学校课程总是从理论出发,作业项目都看不出有什么实际作用,到了工作上发现自己什么也不会干。 50 | 51 | * **技术能力的瓶颈**。你又是否觉得,在工作当中需要的技术只不过是不断地堆业务功能,完全没有什么技术含量。而你工作一段时间后,自己都感觉得非常地迷茫和彷徨,感觉到达了提高的瓶颈,完全不知道怎么提升了。 52 | 53 | * **技术太多学不过来**。你是否又觉得,要学的技术多得都不行了,完全不知道怎么学?感觉完全跟不上。有没有什么速成的方法? 54 | 55 | 对此,我有如下的一些解释,以端正一下你的态度。 56 | 57 | 并不是理论和现实的差距大,而是你还没有找到相关的场景,来感受到那些学院派知识的强大威力。算法与数据结构、操作系统原理、编译原理、数据库原理、计算机原理……这些原理上的东西,是你想要成为一个专家必须要学的东西。**这就是“工人”和“工程师”的差别,是“建筑工人”和“建筑架构师”的差别**。如果你觉得这些理论上的东西无用,那么只能说明,你只不过在从事工人的工作,而不是工程师的工作。 58 | 59 | **技术能力的瓶颈,以及技术太多学不过来,只不过是你为自己的能力不足或是懒惰找的借口罢了**。技术的东西都是死的,这些死的知识只要努力就是可以学会的。只不过聪明的人花得时间少,笨点的人花得时间多点罢了。这其中的时间差距主要是由学习方法的不同,基础知识储备的不同决定的。只要你方法得当,多花点时间在基础知识上,会让你未来学习应用知识的时间大大缩短。**以绝大多数人努力的程度,和为自己不努力找借口的程度为参考,只要你坚持正常的学习就可以超过大多数人了**。 60 | 61 | **这里没有学习技术的速成的方法,真正的牛人不是能够培训出来的,一切都是要靠你自己去努力和持续地付出**。如果你觉得自己不是一个能坚持的人,也不是一个想努力的人,而是一个想找捷径的人,那么,这篇文章并不适合你。**这篇文章中的成长路径是需要思考、精力和相关的经验的,这都需要时间,而且是不短的时间。你先问问自己有没有花十年磨一剑的决心,如果没有,那这篇文章对你没有任何作用**。 62 | 63 | 这里有一篇传世之文[《Teach Yourself Programming in Ten Years》](http://norvig.com/21-days.html)([中英对照版](http://daiyuwen.freeshell.org/gb/misc/21-days-cn.html))。还有在我 Cooslhell 上的这篇[《程序员的荒谬之言还是至理名言?》](https://coolshell.cn/articles/4235.html)。 64 | 65 | 我希望你在学习编程之前先读一读这两篇文章。如果你觉得可以坚持的话,那么,我这一系列文章会对你很有帮助。否则,我相信你只要大致浏览一下目录及其中的某些章节,就会选择放弃走这条路的。是的,这个系列的文内容也会让一些想入行但又不愿意付出努力的同学早点放弃。 66 | 67 | 最后,给出我的几点**学习建议**。 68 | 69 | * 一定要坚持,要保持长时间学习,甚至终生学习的态度。 70 | 71 | * 一定要动手,不管例子多么简单,建议至少自己动手敲一遍看看是否理解了里头的细枝末节。 72 | 73 | * 一定要学会思考,思考为什么要这样,而不是那样。还要举一反三地思考。 74 | 75 | * 不要乱买书,不要乱追新技术新名词,基础的东西经过很长时间积累,会在未来至少 10 年通用。 76 | 77 | * 回顾一下历史,看看历史时间线上技术的发展,你才能明白明天会是什么样的。 78 | 79 | 另外,这篇文章的标准会非常高。希望不会把你吓坏了。《易经》有云:“**取法其上,得乎其中,取法其中,得乎其下,取法其下,法不得也**”。所以,我这里会给你立个比较高标准,你要努力达到。相信我,就算是达不到,也会比你一开始期望的要高很多…… -------------------------------------------------------------------------------- /02_零基础启蒙/README.md: -------------------------------------------------------------------------------- 1 | # 零基础启蒙 2 | 3 | 如果你从来没有接触过程序语言,这里给你两个方面的教程,一个偏后端,一个偏前端。对从零基础开始的人来说,最重要的是能够对编程有兴趣,而要对编程有兴趣,就要有成就感。而成就感又来自于用程序打造东西,所以,我推荐下面这份不错的入门教程。 4 | 5 | 第一份入门教程,主要是让你体会到编程是什么。 6 | 7 | * [《与孩子一起学编程》](https://book.douban.com/subject/5338024/) ,这本书以 Python 语言教你如何写程序,是一本老少咸宜的编程书。其中会教你编一些小游戏,还会和你讲基本的编程知识,相当不错。 8 | 9 | * 两个在线编程入门的网站:[Codecademy: Learn Python](https://www.codecademy.com/catalog) 和 [People Can Program](https://www.peoplecanprogram.com/) ,你也可以在这两个网站上学习 Python,只不过是英文的。 10 | 11 | 然后,你可以到 [CodeAbbey](http://www.codeabbey.com/index/task_list) 上去做一些在线编程的小练习。 12 | 13 | 第二份入门教程,主要是让你做点实际有用的东西。嗯,做个网页吧。 14 | 15 | [MDN 的 Web 开发入门](https://developer.mozilla.org/zh-CN/docs/Learn/Getting_started_with_the_web) ,MDN 全称是 Mozilla Developer Network,你可以认为是 Web 方面的官方技术网站。这个教程会带着你建立一个网站。然后,你可以把你的网页发布在 GitHub 上。 16 | 17 | 这两份教程都很不错,都是从零开始,带着你从环境准备开始,一点一点地从一些简单又有意思的东西入手,让你感觉一下编程世界是什么样的,相信这两个教程可以让零基础的你喜欢上编程。 18 | 19 | ## 编程入门 20 | 21 | 在这时,我们使用 Python 和 JavaScript 作为入门语言。Python 就不用多说了,语法比较简单,有大量的库和语法糖,是零基础的人学习编程的不二之选。而 JavaScript 则是前端的语言,为了让你更有编程的成就感,所以,这也成了一门要学习的语言。(注意:对于计算机专业的学生来说,一般会使用 Pascal 做为入门的编程语言,但我觉得编程入门还是要以培养兴趣为主,所以,还是选一些能让人有成就感的语言会更好)。 22 | 23 | ## 入门语言 Python 24 | 25 | 如果你想更为系统地学习一下 Python 编程,我强烈推荐你阅读下面这两本书。它们是零基础入门非常不错的图书,里面有大量的更为实用的示例和项目,可以快速给你正反馈。 26 | 27 | [Python 编程快速上手](https://book.douban.com/subject/26836700/) 28 | 29 | [Python 编程:从入门到实践](https://book.douban.com/subject/26829016/) 30 | 31 | 这两本书除了编程语法方面的讲述有所不同之外,其他都差不多,主要是通过书中的示例来强化你对编程的学习。第一本偏文本处理,包括处理 Word、Excel 和 PDF,第二本中有一些 Web 项目和代码部署方面的内容。如果可能的话,你可以把两本书中的示例都跑一遍。如果你时间有限的话,我推荐你看第二本。 32 | 33 | ## 入门语言 JavaScript 34 | 35 | 如果想入门学习 JavaScript,我主要推荐以下在线教程。 36 | 37 | * [MDN JavaScript 教程](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript),你可以认为这是最权威的 JavaScript 官方教程了,从初级到中级再到高级。 38 | 39 | * [W3School JavaScript 教程](https://www.w3school.com.cn/js/index.asp),这个教程比较偏 Web 方面的编程。 40 | 41 | * [JavaScript 全栈教程(廖雪峰)](https://www.liaoxuefeng.com/wiki/1022910821149312),这是廖雪峰的一个比较偏应用的教程,也是偏 Web 方面的编程,同时包括涉及后端的 Node.js 方面的教程。 42 | 43 | ## 操作系统入门 44 | 45 | Linux学习编程你还需要会玩 Linux,虽然 Windows 占据着更多的桌面市场,但是你还是要了解 Linux。这里,你可以看一下,W3CSchool 上的在线教程 [Linux 教程](https://www.w3cschool.cn/linux/)。 46 | 47 | ## 编程工具 Visual Studio Code 48 | 49 | 这里主要推荐时下最流行也是最好用的 Visual Studio Code,这个工具潜力十足,用它开发 Python、JavaScript、Java、Go、C/C++ 都能得心应手([教程](https://jeasonstudio.gitbooks.io/vscode-cn-doc/content/)) 。 50 | 51 | ## Web 编程入门 52 | 53 | 如果玩到这里,你觉得有趣的话,可以学习一下 Web 方面的入门知识。**为什么是 Web 而不是别的其他技术呢?因为你正身处于第三次工业革命的信息化浪潮中,在这个浪潮中,Web 互联网是其中最大的发明,所以,这是任何一个程序员都不能错过的**。 54 | 55 | 关于 Web 编程,有下面几个方向你要学习一下。 56 | 57 | * **前端基础**。要系统地学习一下前端的知识,也就是 CSS、HTML 和 JavaScript 这三个东西。这里还是给出 MDN 的相关的技术文档页面[CSS 文档](https://developer.mozilla.org/zh-CN/docs/Web/CSS) 和 [HTML 文档](https://developer.mozilla.org/zh-CN/docs/Web/HTML) 。文档很大,你要学习的并不是所有的东西,而是了解 CSS 和 HTML 是怎么相互作用来展示数据的,然后,不用记忆文档中的内容,这两个文档是用来查找知识的。 另外,你可以简单地学习使用 JavaScript 操纵 HTML。理解 DOM 和动态网页(可以参看 [W3Schools 的 JavaScript HTML DOM 的教程](https://www.w3schools.com/js/js_htmldom.asp))。 58 | 59 | * **后端基础**。如果你想省点事,不想再学一门新的语言了,那么你可以直接用 Python 或者 Node.js,这两个技术在前面提到的廖雪峰的那个教程里提到过。当然,如果你想试试另外一种脚本型的也是比较主流的编程语言,那么可以搞搞 PHP,它也是很快就可以上手的语言。学习 PHP 语言,你可以先跟着 W3School 的 [PHP 教程](https://www.w3school.com.cn/php/index.asp) 玩玩(其中有连接数据库的 MySQL 的教程)。然后,以 [PHP 的官网文档](https://www.php.net/manual/zh/) 作为更全的文档来学习或查找相关的技术细节。 60 | 61 | 下面是一些学习要点: 62 | 63 | * 学习 HTML 基本语法。 64 | 65 | * 学习 CSS 如何选中 HTML 元素并应用一些基本样式。 66 | 67 | * 学会用 Firefox + Firebug 或 Chrome 查看你觉得很炫的网页结构,并动态修改。 68 | 69 | * 在一台 Linux 机器上配置 LEMP - Ubuntu/Nginx/PHP/MySQL 这个环境。 70 | 71 | * 学习 PHP,让后台 PHP 和前台 HTML 进行数据交互,对服务器响应浏览器请求形成初步认识,并实现一个表单提交和反显的功能。 72 | 73 | * 把 PHP 连接本地或者远程数据库 MySQL(MySQL 和 SQL 现学现用够了)。 74 | 75 | 这里,你可能会问我,入门时有三个后端语言,一个是 Python,一个是 Node.js,一个是 PHP,你对这三门语言怎么看?老实说,Python 我还看好一些,PHP 次之,Node.js 最后。原因是: 76 | 77 | * Python 语言的应用面还是很广的。(当然,性能可能会有一些问题,但是用于一些性能不敏感的和运维或是一些小工具相关的,还是非常好用的。另外,Python 的应用场景其实还是很多的,包括机器学习和 AI 也有 Python 的身影。用 Python 来做一些爬虫、简单的中间件、应用或是业务服务也是很不错的。) 78 | 79 | * PHP 也是一个比较主流的简单的语言(PHP 在目前来说还是一个比较主流的语言,但其发展潜力有限,虽然可以让你找得到工作,但是一般玩玩就行了)。 80 | 81 | * Node.js 号称 JavaScript 的后端版,但从目前发展来说,在后端的世界里,并不能承担大任,而且问题很多。一些前端程序员用它来做后端的粘合层,我个人觉得这样做法只是掩盖前后端配合有问题,或是接口设计思维上的懒惰,我还是希望前端程序员应该认真学习一门真正的后端语言。 82 | 83 | 当然,这里只是让你感觉一下,**Web 前端编程的感觉,只是为了入门而已。所以,对于这些语言你也不用学得特别精通,感觉一下这几个不同的语言就可以了,然后知道相关的文档和知识在哪里,这样有助于你查阅相应的知识点**。 84 | 85 | ## 实践项目 86 | 87 | 无论你用 Python,还是 Node.js,还是 PHP,我希望你能做一个非常简单的 Blog 系统,或是 BBS 系统,需要支持如下功能: 88 | 89 | * 用户登录和注册(不需密码找回)。 90 | 91 | * 用户发贴(不需要支持富文本,只需要支持纯文本)。 92 | 93 | * 用户评论(不需要支持富文本,只需要支持纯文本)。 94 | 95 | 你需要从前端一直做到后端,也就是说,从 HTML/CSS/JavaScript,到后面的 PHP(Python/Node.js),再到数据库。这其中需要你查阅很多的知识。 96 | 97 | 这里有几个技术点你需要关注一下。 98 | 99 | 1. 用户登录时的密码不应该保存为明文,应该用 MD5+Salt 来保存(关于这个是什么,希望你能自行 Google)。 100 | 2. 用户登录后,对于用户自己的贴子可以有“重新编辑”或 “删除”的功能,但是无权编辑或删除其它用户的贴子。 101 | 3. 数据库的设计,你需要三张表:用户表、文章表和评论表,它们之间是怎么关联的,你需要学习一下。这里有个 PHP 的 blog 教你怎么建表,你可以 [前往一读](https://code.tutsplus.com/tutorials/how-to-create-a-phpmysql-powered-forum-from-scratch--net-10188)。 102 | 103 | 如果你有兴趣,你可以顺着这个小项目,研究一下下面这几个事。 104 | 105 | 图片验证码。 106 | 107 | 上传图片。 108 | 109 | 阻止用户在发文章或评论时输入带 HTML 或 JavaScript 的内容。 110 | 111 | 防范 SQL 注入。参看[PHP 官方文档](https://www.php.net/manual/zh/security.database.sql-injection.php) 或 [微软官方文档](https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008-r2/ms161953(v=sql.105)?redirectedfrom=MSDN),或者你自己 Google 一下。 112 | 113 | 上面这些东西,不是什么高深的东西,但是可以让你从中学到很多。相信你只需要自己 Google 一下就能搞定。 114 | 115 | ## 小结 116 | 117 | 接下来,我总结下今天的内容。首先,我推荐了 Python 和 JavaScript 作为入门语言,以让你尽快上手,获得成就感,从而激发你想持续学习的热情。随后介绍了 Linux 操作系统、Visual Studio Code 编程工具、Web 编程入门等方面的学习资料,并给出了学习要点。最后,我给出了一个实践项目,帮助你理解和巩固今天所学的内容。 118 | 119 | 消化好了今天的内容,就准备好精力迎接后面的挑战吧。下篇文章中,我们将正式入门学习该如何编程。 -------------------------------------------------------------------------------- /03_正式入门/README.md: -------------------------------------------------------------------------------- 1 | # 正式入门 2 | 3 | 学习了前面文章中的入门级经验和知识后,你可能会有两种反应。 4 | 5 | * 一种反应可能是,你对编程有一点的兴趣了,甚至有一点点小骄傲,可能还会四处炫耀。我想说,请保持这种感觉,但是你也要清醒一下,上面的那些东西,还不算真正的入门,你只是入门了一条腿。 6 | 7 | * 另一种反应也可能是,你被吓着了,觉得太难了。感觉不是一般人能玩的,如果是这样的话,我想鼓励你一下–“**无论你做什么事,你都会面对各式各样的困难,这对每个人来说都是一样的,而只有兴趣、热情和成就感才能让你不畏惧这些困难**”。所以,你问问你自己,是否从中收获了成就感,如果没有的话,可能这条路并不适合你。如果有的话,哪怕一丁点儿,你也应该继续坚持下来。 8 | 9 | 这篇文章,我主要是让你成为更为专业的入门程序员。请注意,此时,你可能需要读一些比较枯燥的书,但我想说,这些是非常非常重要的。你一定要坚持住。 10 | 11 | ## 编程技能 12 | 13 | 在系统地学习编程技能之前,我希望你能先看一下 "[The Key To Accelerating Your Coding Skills](http://blog.thefirehoseproject.com/posts/learn-to-code-and-be-self-reliant/)", 这篇文章会告诉你如何有效地快速提高自己的编程能力。 14 | 15 | 然后接下来是下面几大块内容,但还只是入门级的。 16 | 17 | * **编程技巧方面** - 你可以开始看怎么把程序写好的书了,这里推荐的是《[代码大全](https://book.douban.com/subject/1477390/)》。这本书好多年没有更新了,其中有一些内容可能有点过时,但还是一本非常好的书,有点厚,你不需要马上就看完。在你的编程路上,这本书可以陪你走很久,因为当你有更多的编程经验时,踩过更多的坑后,再把这本书拿出来看看,你会有更多的体会。**好的书和不好的书最大的区别就是,好的书在你不同的阶段来读,你会有不同的收获,而且还会产生更多的深层次的思考**! 《代码大全》就是这样的一本书。 18 | 19 | * **编程语言方面** - 这个阶段,你可以开始了解一下 Java 语言了,我个人觉得 Java 是世界上目前为止综合排名最好的语言。你一定要学好这门语言。推荐《[Java 核心技术(卷 1)](https://book.douban.com/subject/26880667/)》,除了让你了解 Java 的语法,它还会让你了解面向对象编程是个什么概念(如果你觉得这本书有点深,那么,你可以降低难度看更为基础的《[Head First Java](https://book.douban.com/subject/2000732/)》)。然后,既然开始学习 Java 了,那就一定要学 Spring,推荐看看《[Spring in Action](https://book.douban.com/subject/26767354/)》或是直接从最新的 Spring Boot 开始,推荐看看《[Spring Boot 实战](https://book.douban.com/subject/26857423/)》。关于 Spring 的这两本书,里面可能会有很多你从来没有听说过的东西,比如,IoC 和 AOP 之类的东西,能看懂多少就看懂多少,没事儿。 20 | 21 | * **操作系统** - 这里你可以看看《[鸟哥的 Linux 私房菜](https://book.douban.com/subject/4889838/)》,这本书会让你对计算机和操作系统,以及 Linux 有一个非常全面的了解,并能够管理或是操作好一个 Linux 系统。当然,这本书有很多比较专业的知识,你可能会看不懂,没关系,就暂时略过就好了。这本书的确并不适合初学者,你能看多少就看多少吧。 22 | 23 | * **网络协议** - 你需要系统地了解一下 HTTP 协议,请到 MDN 阅读一下其官方的 [HTTP 的文档](https://developer.mozilla.org/zh-CN/docs/Web/HTTP)。你需要知道 HTTP 协议的几个关键点:1)HTTP 头,2)HTTP 的请求方法,3)HTTP 的返回码。还有,HTTP 的 Cookie、缓存、会话,以及链接管理,等等,在 MDN 的这个文档中都有了。对于 HTTP 协议,你不需要知道所有的东西,你只需要了解这个协议的最关键的那些东西就好了。 24 | 25 | * **数据库设计** - 你需要系统地了解一下数据库设计中的那些东西,这里推荐慕课网的一个在线课程:[数据库设计的那些事](https://www.imooc.com/learn/117)。每个小课程不过 5-6 分钟,全部不到 2 个小时,我相信你一定能跟下来。你需要搞清楚数据的那几个范式,还有 SQL 语句的一些用法。当然,你还要学习和使用一下数据库,这里推荐学习开源的 MySQL。你可以看官方文档,也可以看一下这本书《[MySQL 必知必会](https://book.douban.com/subject/3354490/)》。 26 | 27 | * **前端方面** - 前端的东西不算复杂,你需要学习几个东西。一个是和 JavaScript 相关的 [jQuery](https://jquery.com/),另一个是和 CSS 相关的 [Bootstrap](https://getbootstrap.com/),学习这两个东西都不复杂,直接上其官网看文档就好了。最重要的是,你要学习一下如何使用 JavaScript Ajax 请求后端的 API 接口,而不是再像前面那样用后端来向前端返回 HTML 页面的形式了。这里,你需要学习一下,JavaScript 的 Promise 模式。[阮一峰翻译的 ES6 的教程中有相关的内容](https://es6.ruanyifeng.com/#docs/promise)。当然,你 Google 一下,也可以找到一堆学习资料。 28 | 29 | * **字符编码方面** - 在你处理中文时有时会发现有乱码出现,此时需要了解 ASCII 和 Unicode 这样的字符编码。这里推荐一篇文章 - “[关于字符编码,你所需要知道的(ASCII,Unicode,Utf-8,GB2312…)](http://www.imkevinyang.com/2010/06/%E5%85%B3%E4%BA%8E%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81%EF%BC%8C%E4%BD%A0%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84.html)” 或是英文文章 “[The history of Character Encoding](http://latel.upf.edu/morgana/altres/cibres/unicode/pnoerr1.htm)” 以及 [Wikipedia - Character encoding](https://en.wikipedia.org/wiki/Character_encoding)。还有 GitHub 上的这两个 Awesome 仓库:[Awesome Unicode](https://github.com/jagracey/Awesome-Unicode) 和 [Awesome Code Points](https://github.com/Codepoints/awesome-codepoints)。 30 | 31 | ## 为什么转成 Java 语言? 32 | 33 | 相信你可能会问,为什么之前学习的 Python 和 JavaScript 不接着学,而是直接切到 Java 语言上来,这样会不会切得太快了。这是一个好问题,这里需要说明一下,为什么我会切到 Java 这个语言上来,主要是有以下几方面考虑。 34 | 35 | 1. Java 是所有语言里面综合实力最强的,这也是为什么几乎所有大型的互联网或是分布式架构基本上都是 Java 技术栈。所以,这是一个工业级的编程语言(Python 和 JavaScript 还达不到这样的水准)。 36 | 37 | 2. 之所以没有用 Java 来做入门语言而是用了 Python,这是因为编程是一件比较费脑子的事,一开始学习时,兴趣的培养很重要。Python 比较简单,容易上手,能够比较容易地提起兴趣,而用 Java 则可能比较难。 38 | 39 | 3. 在你有了一些编程语言的基础后,有了一些代码的逻辑后,切到工业级的编程语言上来,更为专业地学习编程,是非常有帮助的。像 Python 和 JavaScript 这样的动态语言用着是很爽,但是,只有像 C、C++ 和 Java 这样的静态语言才可以让你真正地进阶。 40 | 41 | 4. 对于一个合格的程序员,掌握几门语言是非常正常的事情。一方面,这会让你对不同的语言进行比较,让你有更多的思考。另一方面,这也是一种学习能力的培养。很多时候,一些程序员只在自己熟悉的技术而不是合适的技术上工作,这其实并不好,这会让你的视野受限,而视野会决定你的高度。综上所述,这就是在入门的时候我故意让你多学几门语言的原因。 42 | 43 | ## 编程工具 44 | 45 | 编程工具方面,你需要开始学习使用下面这些工具了。 46 | 47 | * **编程的 IDE**。传统一点的,你可以使用 Eclipse([教程](https://www.runoob.com/eclipse/eclipse-tutorial.html))。当然,我推荐你使用 Intellij IDEA([教程](https://dancon.gitbooks.io/intellij-idea/content/))。这两个工具都可以开发各种语言,但是主要用在 Java。**如果你想玩得更时髦一些的话,使用 Visual Studio Code 也不错,这个工具潜力十足**,用其开发 Python、JavaScript、Java、Go、C 和 C++ 都能得心应手([教程](https://jeasonstudio.gitbooks.io/vscode-cn-doc/content/))。 48 | 49 | * **版本管理工具**。版本管理工具是非常重要的编程工具。传统的有 P4、 SVN、CVS 等,但都会被 Git 取代,所以,你就只用学习 Git 就好了。学习 Git 的教程网上有很多,这里我推荐非常系统的 [Pro Git 第二版](https://git-scm.com/book/zh/v2/) (如果你觉得 Pro Git 比较枯燥的话,备选[猴子都能懂的 Git 入门](https://backlog.com/git-tutorial/cn/)),然后你要学会使用 GitHub。关于一些 Git 环境安装和准备以及 GitHub 使用,你可以自行 Google(比如:这篇[GitHub and Git 图文教程](https://github.com/JiapengLi/GitTutorial) 或是这篇[Git 图文教程及详解](https://www.jianshu.com/p/1b65ed31da97))。 50 | 51 | * **调试前端程序**。你需要学会使用 Chrome 调试前端程序,Google 一下会有很多文章,你可以看看 [超完整的 Chrome 浏览器客户端调试大全](http://www.igeekbar.com/igeekbar/post/156.htm)。 52 | 53 | 数据库设计工具。你需要学会使用 MySQL WorkBench,这个工具很容易使用。相关的手册,你可以看一下[官方文档](https://dev.mysql.com/doc/refman/5.7/en/)。 54 | 55 | ## 实践项目 56 | 57 | 这回我们需要设计一个投票系统的项目。 58 | 59 | 业务上的需求如下: 60 | 61 | * 用户只有在登录后,才可以生成投票表单。 62 | 63 | * 投票项可以单选,可以多选。 64 | 65 | * 其它用户投票后显示当前投票结果(但是不能刷票)。 66 | 67 | * 投票有相应的时间,页面上需要出现倒计时。 68 | 69 | * 投票结果需要用不同颜色不同长度的横条,并显示百分比和人数。 70 | 71 | 技术上的需求如下: 72 | 73 | * 这回要用 Java Spring Boot 来实现了,然后,后端不返回任何的 HTML,只返回 JSON 数据给前端。 74 | 75 | * 由前端的 JQuery 来处理并操作相关的 HTML 动态生成在前端展示的页面。 76 | 77 | * 前端的页面还要是响应式的,也就是可以在手机端和电脑端有不同的呈现。 这个可以用 Bootstrap 来完成。 78 | 79 | 如果你有兴趣,还可以挑战以下这些功能。 80 | 81 | * 在微信中,通过微信授权后记录用户信息,以防止刷票。 82 | 83 | * 可以不用刷页面,就可以动态地看到投票结果的变化。 84 | 85 | * Google 一些画图表的 JavaScript 库,然后把图表画得漂亮一些。 86 | 87 | ## 小结 88 | 89 | 上面那些书和知识你要看完,还要能理解并掌握,我估计你最少也要花 1-2 年左右的时间。如果你能够走到这里,把前面的那些知识都了解了,不用精通,能独立地做出上面的那些实践项目,那么,你就算是真正的入门了。 90 | 91 | 而且,你已经是一个“全栈工程师”的样子了,在这里我要给你一个大大的赞。如果这个时候,你对编程还有很大的热情,那么我要恭喜你了,你可能会是一个非常不错的程序员。加油啊! 92 | 93 | 上面的那些技术已经算是比较专业的了。如果你已经大致掌握了,我相信你可以找到至少年薪 20 万以上的工作了,而且你的知识面算是有不错的广度了。但是深度还不够,这个时候,是一个比较关键点了。 94 | 95 | 你可能已经沉醉在沾沾自喜的骄傲的情绪中,那么你也可以就此止步,加入一些公司,在那里按部就班地完成一些功能性的开发,成为一个搬砖的码农。你也可以开始选择一个方向开始深入。 96 | 97 | 我给你的建议是选择一个方向开始深入。**因为你并不知道你未来会有多大的可能性,也不知道你会成为什么样的人,所以为什么不再更努力一把呢?** 98 | 99 | 后面,我们就开始非常专业的程序员之路了。这也是一般程序员和高级程序员的分水岭了,能不能过去就看你的了。 -------------------------------------------------------------------------------- /05_编程语言/README.md: -------------------------------------------------------------------------------- 1 | # 编程语言 2 | 3 | 为了进入专业的编程领域,我们需要认真学习以下三方面的知识。 4 | 5 | **编程语言**。你需要学习 C、C++ 和 Java 这三个工业级的编程语言。为什么说它们是工业级的呢?主要是,C 和 C++ 语言规范都由 ISO 标准化过,而且都有工业界厂商组成的标准化委员会来制定工业标准。次要原因是,它们已经在业界应用于许多重要的生产环境中。 6 | 7 | * C 语言不用多说,现今这个世界上几乎所有重要的软件都跟 C 有直接和间接的关系,操作系统、网络、硬件驱动等等。说得霸气一点儿,这个世界就是在 C 语言之上运行的。 8 | 9 | * 而对于 C++ 来说,现在主流的浏览器、数据库、Microsoft Office、主流的图形界面、著名的游戏引擎等都是用 C++ 编写的。而且,很多公司都用 C++ 开发核心架构,如 Google、腾讯、百度、阿里云等。 10 | 11 | * 而金融电商公司则广泛地使用 Java 语言,因为 Java 的好处太多了,代码稳定性超过 C 和 C++,生产力远超 C 和 C++。有 JVM 在,可以轻松地跨平台,做代码优化,做 AOP 和 IoC 这样的高级技术。以 Spring 为首的由庞大的社区开发的高质量的各种轮子让你只需关注业务,是能够快速搭建企业级应用的不二之选。 12 | 13 | 此外,我推荐学习 Go 语言。一方面,Go 语言现在很受关注,它是取代 C 和 C++ 的另一门有潜力的语言。C 语言太原始了,C++ 太复杂了,Java 太高级了,所以 Go 语言就在这个夹缝中出现了。这门语言已经 10 多年了,其已成为云计算领域事实上的标准语言,尤其是在 Docker/Kubernetes 等项目中。Go 语言社区正在不断地从 Java 社区移植各种 Java 的轮子过来,Go 社区现在也很不错。 14 | 15 | 如果你要写一些 PaaS 层的应用,Go 语言会比 C 和 C++ 更好,目前和 Java 有一拼。而且,Go 语言在国内外一些知名公司中有了一定的应用和实践,所以,是可以学习的(参看:《[Go 语言、Docker 和新技术](https://coolshell.cn/articles/18190.html)》一文)。此外,Go 语言语法特别简单,你有了 C 和 C++ 的基础,学习 Go 的学习成本基本为零。 16 | 17 | **理论学科**。你需要学习像算法、数据结构、网络模型、计算机原理等计算机科学专业需要学习的知识。为什么要学好这些理论上的知识呢? 18 | 19 | * 其一,这些理论知识可以说是计算机科学这门学科最精华的知识了。说得大一点,这些是人类智慧的精华。你只要想成为高手,这些东西是你必需要掌握和学习的。 20 | * 其二,当你在解决一些很复杂或是很难的问题时,这些基础理论知识可以帮到你很多。我过去这 20 年从这些基础理论知识中受益匪浅。 21 | * 其三,这些理论知识的思维方式可以让你有触类旁通,一通百通的感觉。虽然知识比较难啃,但啃过以后,你将获益终生。另外,你千万不要觉得在你的日常工作或是生活当中根本用不上,学了也白学,这样的思维方式千万不要有,因为这是平庸的思维方式。如果你想等我用到了再学也不晚,那么你有必要看一下这篇文章《[程序员的荒谬之言还是至理名言?](https://coolshell.cn/articles/4235.html)》。 22 | 23 | **系统知识**。系统知识是理论知识的工程实践,这里面有很多很多的细节。比如像 Unix/Linux、TCP/IP、C10K 挑战等这样专业的系统知识。这些知识是你能不能把理论应用到实际项目当中,能不能搞定实际问题的重要知识。 24 | 25 | 当你在编程的时候,如何和系统进行交互或是获取操作系统的资源,如何进行通讯,当系统出了性能问题,当系统出了故障等,你有大量需要落地的事需要处理和解决。这个时候,这些系统知识就会变得尤为关键和重要了。 26 | 27 | 这些东西,你可以认为是计算机世界的物理世界,上层无论怎么玩,无论是 Java NIO,还是 Nginx,还是 Node.js,它们都逃脱不掉最下层的限制。所以,你要好好学习这方面的知识。 28 | 29 | ## 编程语言 30 | 31 | ### Java 语言 32 | 33 | 学习 Java 语言有以下入门级的书(注意:下面一些书在入门篇中有所提及,但为了完整性,还是要在这里提一下,因为可能有朋友是跳着看的)。 34 | 35 | * 《[Java 核心技术:卷 1 基础知识](https://book.douban.com/subject/26880667/)》,这本书本来是 Sun 公司的官方用书,是一本 Java 的入门参考书。对于 Java 初学者来说,是一本非常不错的值得时常翻阅的技术手册。书中有较多地方进行 Java 与 C++ 的比较,因为当时 Java 面世的时候,又被叫作 "C++ Killer"。而我在看这本书的时候,发现书中有很多 C++ 的东西,于是又去学习了 C++。学习 C++ 的时候,发现有很多 C 的东西不懂,又顺着去学习了 C。然后,C -> C++ -> Java 整条线融汇贯通,这对我未来的技术成长有非常大的帮助。 36 | * 有了上述的入门后,Java 的 Spring 框架是你玩 Java 所无法回避的东西,所以接下来是两本 Spring 相关的书,《[Spring 实战](https://book.douban.com/subject/26767354/)》和《[Spring Boot 实战](https://book.douban.com/subject/26857423/)》。前者是传统的 Spring,后者是新式的微服务的 Spring。如果你只想看一本的话,那么就看后者吧。前面推荐的几本书可以帮你成功入门 Java,但想要进一步成长,就要看下面我推荐的几本进阶级别的书了。 37 | 38 | 前面推荐的几本书可以帮你成功入门 Java,但想要进一步成长,就要看下面我推荐的几本进阶级别的书了。 39 | 40 | * 接下来,你需要了解了一下如何编写高效的代码,于是必需看一下《[Effective Java](https://book.douban.com/subject/27047716/)》(注意,这里我给的引用是第三版的,也是 2017 年末出版的书),这本书是模仿 Scott Meyers 的经典图书《Effective C++》的。Effective 这种书基本上都是各种经验之谈,所以,这是一本非常不错的书,你一定要读。这里需要推荐一下 [Google Guava](https://github.com/google/guava) 库 ,这个库不但是 JDK 的升级库,其中有如:集合(collections)、缓存(caching)、原生类型支持(primitives support)、并发库(concurrency libraries)、通用注解(common annotations)、字符串处理(string processing)、I/O 等库,其还是 Effective Java 这本书中的那些经验的实践代表。 41 | 42 | * 《[Java 并发编程实战](https://book.douban.com/subject/10484692/)》,是一本完美的 Java 并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险、构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子系统的响应性,如何确保并发程序执行预期任务,如何提高并发代码的性能和可伸缩性等内容。最后介绍了一些高级主题,如显式锁、原子变量、非阻塞算法以及如何开发自定义的同步工具类。 43 | 44 | * 了解如何编写出并发的程序,你还需要了解一下如何优化 Java 的性能。我推荐《[Java 性能权威指南](https://book.douban.com/subject/26740520/)》。通过学习这本书,你可以比较大程度地提升性能测试的效果。其中包括:使用 JDK 中自带的工具收集 Java 应用的性能数据,理解 JIT 编译器的优缺点,调优 JVM 垃圾收集器以减少对程序的影响,学习管理堆内存和 JVM 原生内存的方法,了解如何最大程度地优化 Java 线程及同步的性能,等等。看完这本书后,如果你还有余力,想了解更多的底层细节,那么,你有必要去读一下《[深入理解 Java 虚拟机](https://book.douban.com/subject/24722612/)》。 45 | 46 | * 《[Java 编程思想](https://book.douban.com/subject/2130190/)》,真是一本透着编程思想的书。上面的书让你从微观角度了解 Java,而这本书则可以让你从一个宏观角度了解 Java。这本书和 Java 核心技术的厚度差不多,但这本书的信息密度比较大。所以,读起来是非常耗大脑的,因为它会让你不断地思考。对于想学好 Java 的程序员来说,这是一本必读的书。 47 | 48 | * 《[精通 Spring 4.x](https://book.douban.com/subject/26952826/)》,也是一本很不错的书,就是有点厚,一共有 800 多页,都是干货。我认为其中最不错的是在分析原理,尤其是针对前面提到的 Spring 技术,应用与原理都讲得很透彻,IOC 和 AOP 也分析得很棒,娓娓道来。其对任何一个技术都分析得很细致和全面,不足之处就是内容太多了,所以导致很厚,但这并不影响它是一本不错的工具书。 49 | 50 | 当然,学 Java 你一定要学面向对象的设计模式,这里就只有一本经典的书《[设计模式](https://book.douban.com/subject/1052241/)》。如果你觉得有点儿难度了,那么可以看一下《[Head First 设计模式](https://book.douban.com/subject/2243615/)》。学习面向对象的设计模式时,你不要迷失在那 23 个设计模式中,你一定要明白这两个原则: 51 | 52 | * Program to an ‘interface’, not an 'implementation’ 53 | * 使用者不需要知道数据类型、结构、算法的细节。 54 | * 使用者不需要知道实现细节,只需要知道提供的接口。 55 | * 利于抽象、封装,动态绑定,多态。符合面向对象的特质和理念。 56 | 57 | * Favor ‘object composition’ over 'class inheritance’ 58 | * 继承需要给子类暴露一些父类的设计和实现细节。 59 | * 父类实现的改变会造成子类也需要改变。 60 | * 我们以为继承主要是为了代码重用,但实际上在子类中需要重新实现很多父类的方法。 61 | * 继承更多的应该是为了多态。 62 | 63 | 至此,如果你把上面的这些知识都融汇贯通的话,那么,你已是一个高级的 Java 程序员了,我保证你已经超过了绝大多数程序员了。基本上来说,你在技术方面是可以进入到一线公司的,而且还不是一般的岗位,至少是高级程序员或是初级架构师的级别了。 64 | 65 | ## C/C++ 语言 66 | 67 | 不像我出道那个时候,几乎所有的软件都要用 C 语言来写。现在,可能不会有多少人学习 C 语言了,因为一方面有 Java、Python 这样的高级语言为你屏蔽了很多的底层细节,另一方面也有像 Go 语言这样的新兴语言可以让你更容易地写出来也是高性能的软件。但是,我还是想说,C 语言是你必须学习的语言,因为这个世界上绝大多数编程语言都是 C-like 的语言,也是在不同的方面来解决 C 语言的各种问题。**这里,我想放个比较武断话——如果你不学 C 语言,你根本没有资格说你是一个合格的程序员**! 68 | 69 | * 这里尤其推荐,已故的 C 语言之父 Dennis M. Ritchie 和著名科学家 Brian W. Kernighan 合作的圣经级的教科书《[C 程序设计语言](https://book.douban.com/subject/1139336/)》。注意,这本书是 C 语言原作者写的,其 C 语言的标准不是我们平时常说的 ANSI 标准,而是原作者的标准,又被叫作 K&R C。但是这本书很轻薄,也简洁,不枯燥,是一本你可以拿着躺在床上看还不会看着看着睡着的书。 70 | * 然后,还有一本非常经典的 C 语言的书《[C 语言程序设计现代方法](https://book.douban.com/subject/2280547/)》。有人说,这本书配合之前的 [The C Programming Language](https://en.wikipedia.org/wiki/The_C_Programming_Language) 那本书简真是无敌。我想说,这本书更实用,也够厚,完整覆盖了 C99 标准,习题的质量和水准也比较高。更好的是,探讨了现代编译器的实现,以及和 C++ 的兼容,还揭穿了各种古老的 C 语言的神话和信条……是相当相当干的一本学习 C 语言的书。 71 | 72 | **对了,千万不要看谭浩强的 C 语言的书。各种误导,我大学时就是用这本书学的 C,后来工作时被坑得不行。** 73 | 74 | 在学习 C 语言的过程中,你一定会感到,C 语言这么底层,而且代码经常性地崩溃,经过一段时间的挣扎,你才开始觉得你从这个烂泥坑里快要爬出来了。但你还需要看看《[C 陷阱与缺陷](https://book.douban.com/subject/2778632/)》这本书,你会发现,这里面的坑不是一般大。 75 | 76 | 此时,如果你看过我的《编程范式游记》那个系列文章,你可能会发现 C 语言在泛型编程上的各种问题,这个时候我推荐你学习一下 C++ 语言。可能会有很多人觉得我说的 C++ 是个大坑。是的,这是世界目前来说最复杂也是最难的编程语言了。**但是,C++ 是目前世界上范式最多的语言了,其做得最好的范式就是 " 泛型编程 ",这在静态语言中,是绝对地划时代的一个事**。 77 | 78 | 所以,你有必要学习一下 C++,看看 C++ 是如何解决 C 语言中的各种问题的。你可以先看看我的这篇文章 “[C++ 的坑真的多吗?](https://coolshell.cn/articles/7992.html)” ,有个基本认识。下面推荐几本 C++ 的书。 79 | 80 | * 《[C++ Primer 中文版](https://book.douban.com/subject/25708312/)》,这本书是久负盛名的 C++ 经典教程。书是有点厚,前面 1/3 讲 C 语言,后面讲 C++。C++ 的知识点实在是太多了,而且又有点晦涩。但是你主要就看几个点,一个是面向对象的多态,一个是模板和重载操作符,以及一些 STL 的东西。看看 C++ 是怎么玩泛型和函数式编程的。 81 | 82 | * 如果你想继续研究,你需要看另外两本更为经典的书《[Effective C++](https://book.douban.com/subject/5387403/)》和《[More Effective C++](https://book.douban.com/subject/5908727/)》。 这两本书不厚,但是我读了 10 多年,每过一段时间再读一下,就会发现有更多的收获。这两本书的内容会随着你经历的丰富而变得丰富,这也是对我影响最大的两本书,其中影响最大的不是书中的那些 C++ 的东西,而是作者的思维方式和不断求真的精神,这真是太赞了。 83 | 84 | * 学习 C/C++ 都是需要好好了解一下编译器到底干了什么事的。就像 Java 需要了解 JVM 一样,所以,这里还有一本非常非常难啃的书你可以挑战一下《[深度探索 C++ 对象模型](https://book.douban.com/subject/10427315/) 》。这本书是非常之经典的,看完后,C++ 对你来说就再也没有什么秘密可言。我以前写过的《[C++ 虚函数表解析](https://coolshell.cn/articles/12165.html)》,还有《[C++ 对象内存布局](https://coolshell.cn/articles/12176.html)》属于这个范畴。 85 | 86 | * 还有 C++ 的作者 Bjarne Stroustrup 写的 [C++ FAQ](https://www.stroustrup.com/bs_faq.html) ([中文版](https://www.stroustrup.com/bsfaqcn.html)),也是非常值得一读的。 87 | 88 | ## 学习 Go 语言 89 | 90 | C 语言太原始了,C++ 太复杂了,Go 语言是不二之选。有了 C/C++ 的功底,学习 Go 语言非常简单。 91 | 92 | 首推 [Go by Example](https://gobyexample.com/) 作为你的入门教程。然后,[Go 101](https://go101.org/article/101.html) 也是一个很不错的在线电子书。如果你想看纸书的话,[The Go Programming Language](https://book.douban.com/subject/26337545/) 一书在豆瓣上有 9.2 分,但是国内没有卖的。(当然,我以前也写过两篇入门的供你参考 “[GO 语言简介(上)- 语法](https://coolshell.cn/articles/8460.html)” 和 “[GO 语言简介(下)- 特性](https://coolshell.cn/articles/8489.html)”)。 93 | 94 | 另外,Go 语言官方的 [Effective Go](https://golang.org/doc/effective_go) 是必读的,这篇文章告诉你如何更好地使用 Go 语言,以及 Go 语言中的一些原理。 95 | 96 | Go 语言最突出之处是并发编程,Unix 老牌黑客罗勃·派克(Rob Pike)在 Google I/O 上的两个分享,可以让你学习到一些并发编程的模式。 97 | 98 | * Go Concurrency Patterns( [幻灯片](https://talks.golang.org/2012/concurrency.slide#1)和[演讲视频](https://www.youtube.com/watch?v=f6kdp27TYZs))。 99 | 100 | * Advanced Go Concurrency Patterns([幻灯片](https://talks.golang.org/2013/advconc.slide#1)、[演讲视频](https://www.youtube.com/watch?v=QDDwwePbDtw))。 101 | 102 | 然后,Go 在 GitHub 的 wiki 上有好多不错的学习资源,你可以从中学习到多。比如: 103 | 104 | * [Go 精华文章列表](https://github.com/golang/go/wiki/Articles)。 105 | 106 | * [Go 相关博客列表](https://github.com/golang/go/wiki/Blogs)。 107 | 108 | * [Go Talks](https://github.com/golang/go/wiki/GoTalks)。 109 | 110 | 此外,还有个内容丰富的 Go 资源列表 [Awesome Go](https://github.com/avelino/awesome-go),推荐看看。 111 | 112 | ## 小结 113 | 114 | 好了,最后我们来总结一些今天分享的内容。在编程语言方面,我推荐学习 C、C++、Java 和 Go 四门语言,并分别阐释了推荐的原因。 115 | 116 | * 我认为,C 语言是必须学习的语言,因为这个世界上绝大多数编程语言都是 C-like 的语言,也是在不同的方面来解决 C 语言的各种问题。 117 | 118 | * 而 C++ 虽然复杂难学,但它几乎是目前世界上范式最多的语言了,其做得最好的范式就是 " 泛型编程 ",这在静态语言中,是绝对地划时代的一个事。尤其要看看 C++ 是如何解决 C 语言中的各种问题的。 119 | 120 | * Java 是我认为综合能力最强的语言。其实我是先学了 Java,然后又去学了 C++,之后去学了 C 语言的。C -> C++ -> Java 整条线融汇贯通,这对我未来的技术成长有非常大的帮助。 121 | 122 | * 在文章最末,我推荐了 Go 语言,并给出了相关的学习资料。 123 | 124 | 我认为,一个合格的程序员应该掌握几门语言。一方面,这会让你对不同的语言进行比较,让你有更多的思考。另一方面,这也是一种学习能力的培养,会让你对于未来的新技术学习得更快。 125 | 126 | 下篇文章中,我们将分享每个程序员都需要掌握的理论知识。敬请期待。 -------------------------------------------------------------------------------- /06_理论学科/README.md: -------------------------------------------------------------------------------- 1 | # 理论学科 2 | 3 | 进入专业的编程领域,算法、数据结构、网络模型、计算机原理等这样的计算机科学专业需要学习的理论知识是必须要学习的。下面我们先来看看数据结构和算法。 4 | 5 | ## 数据结构和算法 6 | 7 | 算法是比较难学习的,而且学习“算法”是需要智商的。数组、链表、哈希表、二叉树、排序算法等一些基础知识,对大多数人来说是没什么问题的。但是一旦进入到路径规划、背包问题、字符串匹配、动态规划、递归遍历等一些比较复杂的问题上,就会让很多人跟不上了,不但跟不上,而且还会非常痛苦。是的,解决算法问题的确是可以区分人类智商的一个比较好的方式,这也是为什么好些公司用算法题当面试题来找到智商比较高的程序员。 8 | 9 | 然而,在很多时候,我们在工作中却发现根本用不到算法,或是一些基本的算法也没有必要实现,只需要使用一下第三方的库就好了。于是,导致社会上出现很多“算法无用论”的声音。 10 | 11 | 对此,我想说,算法真的很重要。我这 20 年的经历告诉我,无论是做业务还是做底层系统,经常需要使用算法处理各种各样的问题。比如,业务上我需要用算法比较两个数组中差异的布隆过滤器,或是在做监控系统时实时计算过去一分钟的 P99 统计时的蓄水池算法,或是数据库的 B+ 树索引,还有 Linux 内核中的 epoll 的红黑树,还有在做服务调度里的“背包问题”等都会用算法,真的是会本质上帮助到你,也是会让你瞬间会产生成就感的事情。 12 | 13 | 虽然算法很难,需要智商,但我还是想鼓励你,这其中是有很多的套路是可以学习的,一旦学会这些套路,你会受益无穷的。 14 | 15 | 这里有几本书着重推荐一下。 16 | 17 | * **基础知识**。《[算法](https://book.douban.com/subject/10432347/)》,是算法领域经典的参考书,不但全面介绍了关于算法和数据结构的必备知识,还给出了每位程序员应知应会的 50 个算法,并提供了实际代码。最不错的是,其深入浅出的算法介绍,让一些比较难的算法也变得容易理解,尤其是书中对红黑树的讲解非常精彩。其中,还有大量的图解,详尽的代码和讲解,也许是最好的数据结构入门图书。不好的是不深,缺乏进一步的算法设计内容,甚至连动态规划都未提及。另外,如果你觉得算法书比较枯燥的话,你可以看看这本有趣的《[算法图解](https://book.douban.com/subject/26979890/)》。 18 | 19 | * **理论加持**。如果说上面这本书偏于实践和工程,而你看完后,对算法和数据结构的兴趣更浓了,那么你可以再看看另一本也是很经典的偏于理论方面的书——《[算法导论](https://book.douban.com/subject/20432061/)》。虽然其中的一些理论知识在《算法》那本书中也有提过,但《算法导论》这本书更为专业一些,是美国计算机科学本科生的教科书。 20 | 21 | * **思维改善**。还有一本叫《[编程珠玑](https://book.douban.com/subject/3227098/)》的书,写这本书的人是世界著名计算机科学家乔恩·本特利(Jon Bentley),被誉为影响算法发展的十位大师之一。你可能不认识这个人,但是你知道他的学生有多厉害吗?我例举几个,一个是 Tcl 语言设计者约翰·奥斯德奥特(John Ousterhout),另一个是 Java 语言设计者詹姆斯·高斯林(James Gosling),还有一个是《算法导论》作者之一查尔斯·雷斯尔森(Charles Leiserson),还有好多好多。这本书也是很经典的算法书,其中都是一些非常实际的问题,并以其独有的洞察力和创造力,来引导读者理解并学会解决这些问题的方法,也是一本可以改善你思维方式的书。 22 | 23 | 然后,你需要去做一些题来训练一下自己的算法能力,这里就要推荐 [LeetCode](https://leetcode.com/) 这个网站了。它是一个很不错的做算法训练的地方。现在也越做越好了。基本上来说,这里会有两类题。 24 | 25 | * **基础算法题**。其中有大量的算法题,解这些题都是有套路的,不是用递归(深度优先 DFS、广度优先 BFS),就是要用动态规划(Dynamic Programming),或是折半查找(Binary Search),或是回溯(Back tracing),或是分治法(Divide and Conquer),还有大量的对树、数组、链表、字符串和 hash 表的操作。通过做这些题能让你对这些最基础的算法的思路有非常扎实的了解和训练。对我而言,Dynamic Programming 是我的短板,尤其是一些比较复杂的问题,在推导递推公式上总是有思维的缺陷(数学是我的硬伤)。做了这些题后,我能感到我在动态编程的思路上受到了很大的启发。 26 | 27 | * **编程题**。比如:atoi、strstr、add two nums、括号匹配、字符串乘法、通配符匹配、文件路径简化、Text Justification、反转单词等,这些题的 Edge Case 和 Corner Case 有很多。这些题需要你想清楚了再干,只要你稍有疏忽,就会有几个 case 让你痛不欲生,而且一不小心就会让你的代码写得又臭又长,无法阅读。通过做这些题,可以非常好地训练你对各种情况的考虑,以及你对程序代码组织的掌控(其实就是其中的状态变量)。 28 | 29 | 我觉得每个程序员都应该花时间和精力做这些题,因为你会从这些题中得到很大的收益。我在 Leetcode 上做的一些题的代码在这——我的 [GitHub](https://github.com/haoel/leetcode) 上,可以给你一些参考。 30 | 31 | 如果能够把这些算法能力都掌握了,那么你就有很大的概率可以很容易地通过这世界上最优的公司的面试,比如:Google、Amazon、Facebook 之类的公司。对你来说,如果能够进入到这些公司里工作,那么你未来的想像空间也会大很多。 32 | 33 | 最后,我们要知道这个世界上的数据结构和算法有很多,下面给出了两个网站。 34 | 35 | * [List of Algorithms](https://www.wikiwand.com/en/List_of_algorithms) ,这个网站罗列了非常多的算法,完全可以当成一个算法字典,或是用来开阔眼界。 36 | 37 | * 还有一个数据结构动画图的网站 [Data Structure Visualizations](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html)。 38 | 39 | ## 其它理论基础知识 40 | 41 | 下面这些书,基本上是计算机科学系的大学教材。如果你想有科班出身的理论基础,那么这些书是必读的。当然,这些理论基础知识比较枯燥,但我觉得如果你想成为专业的程序员,那么应该要找时间读一下。 42 | 43 | * 《[数据结构与算法分析](https://book.douban.com/subject/1139426/)》,这本书曾被评为 20 世纪顶尖的 30 部计算机著作之一,作者 Mark Allen Weiss 在数据结构和算法分析方面卓有建树,他在数据结构和算法分析等方面的著作尤其畅销,并广受好评,已被世界 500 余所大学用作教材。 44 | 45 | * 《[数据库系统概念](https://book.douban.com/subject/1929984/)》,它是数据库系统方面的经典教材之一。国际上许多著名大学包括斯坦福大学、耶鲁大学、德克萨斯大学、康奈尔大学、伊利诺伊大学、印度理工学院等都采用本书作为教科书。这本书全面介绍了数据库系统的各种知识,透彻阐释数据库管理的基本概念。不仅讨论了数据库查询语言、模式设计、数据仓库、数据库应用开发、基于对象的数据库和 XML、数据存储和查询、事务管理、数据挖掘与信息检索以及数据库系统体系结构等方面的内容,而且对性能评测标准、性能调整、标准化以及空间与地理数据、事务处理监控等高级应用主题进行了广泛讨论。 46 | 47 | * 《[现代操作系统](https://book.douban.com/subject/3852290/)》,这本书是操作系统领域的经典之作,书中集中讨论了操作系统的基本原理,包括进程、线程、存储管理、文件系统、输入 / 输出、死锁等,同时还包含了有关计算机安全、多媒体操作系统、掌上计算机操作系统、微内核、多核处理机上的虚拟机以及操作系统设计等方面的内容。 48 | 49 | * 《[计算机网络](https://book.douban.com/subject/1391207/)》,这本书采用了独创的自顶向下方法,即从应用层开始沿协议栈向下讲解计算机网络的基本原理,强调应用层范例和应用编程接口,内容深入浅出,注重教学方法,理论与实践相结合。新版中还增加了无线和移动网络一章,并扩充了对等网络、BGP、MPLS、网络安全、广播选路和因特网编址及转发方面的材料。是一本不可多得的教科书。 50 | 51 | * 《[计算机程序的构造和解释](https://book.douban.com/subject/1148282/)》,这本书也很经典,是 MIT 的计算机科学系的教材。这本书中主要证实了很多程序是怎么构造出来的,以及程序的本质是什么。整本书主要是使用 Scheme/Lisp 语言,从数据抽象、过程抽象、迭代、高阶函数等编程和控制系统复杂性的思想,到数据结构和算法,到编译器 / 解释器、编程语言设计。 52 | 53 | * 《[编译原理](https://book.douban.com/subject/3296317/)》,这本书又叫 " 龙书 ",其全面、深入地探讨了编译器设计方面的重要主题,包括词法分析、语法分析、语法制导定义和语法制导翻译、运行时刻环境、目标代码生成、代码优化技术、并行性检测以及过程间分析技术,并在相关章节中给出大量的实例。与上一版相比,本书进行了全面的修订,涵盖了编译器开发方面的最新进展。每章中都提供了大量的系统及参考文献。 54 | 55 | ## 小结 56 | 57 | 好了,最后我们来总结一些今天分享的内容。在这篇文章中,我建议想进入专业编程领域的人,一定要学习算法、数据结构、网络模型、计算机原理等理论知识,并推荐了相应的学习素材,给出了我的思考和建议。 58 | 59 | 我认为,虽然这些理论知识枯燥难学,而且通常学完了在工作中也并不是马上就能用上,但这些知识是必须要学好的。**这些理论知识可以说是计算机科学这门学科最精华的知识了,认真学习,理解其背后的逻辑和思维方式,会让你受益匪浅**。不管是未来你是要学习新技能,还是解决什么疑难问题,都能在这些知识中获得灵感或者启发。 60 | 61 | 下篇文章中,我们将分享每个程序员都需要掌握的系统知识。敬请期待。 -------------------------------------------------------------------------------- /07_系统知识/README.md: -------------------------------------------------------------------------------- 1 | # 系统知识 2 | 3 | 进入专业的编程领域,学习系统知识是非常关键的一部分。 4 | 5 | 首先推荐的是翻译版图书《[深入理解计算机系统](https://book.douban.com/subject/5333562/)》,原书名为《Computer Systems A Programmer’s Perspective》。不过,这本书叫做《程序员所需要了解的计算机知识》更为合适。 6 | 7 | 本书的最大优点是为程序员描述计算机系统的实现细节,帮助其在大脑中构造一个层次型的计算机系统。从最底层的数据在内存中的表示到流水线指令的构成,到虚拟存储器,到编译系统,到动态加载库,到最后的用户态应用。通过掌握程序是如何映射到系统上,以及程序是如何执行的,你能够更好地理解程序的行为为什么是这样的,以及效率低下是如何造成的。 8 | 9 | **再强调一下,这本书是程序员必读的一本书**! 10 | 11 | 然后就是美国计算机科学家 [理查德·史蒂文斯(Richard Stevens)](https://zh.wikipedia.org/wiki/%E7%90%86%E6%9F%A5%E5%BE%B7%C2%B7%E5%8F%B2%E8%92%82%E6%96%87%E6%96%AF) 的三套巨经典无比的书。(理查德·史蒂文斯于 1999 年 9 月 1 日离世,终年 48 岁。死因不详,有人说是滑雪意外,有人说是攀岩意外,有人说是滑翔机意外。总之,家人没有透露。大师的 [个人主页](http://www.kohala.com/start/) 今天还可以访问。) 12 | 13 | * 《[Unix 高级环境编程](https://book.douban.com/subject/1788421/)》。 14 | 15 | * 《Unix 网络编程》 [第 1 卷 套接口 API](https://book.douban.com/subject/1500149/) 、[第 2 卷 进程间通信](https://book.douban.com/subject/4118577/) 。 16 | 17 | * 《[TCP/IP 详解 卷 I 协议](https://book.douban.com/subject/1088054/)》。 18 | 19 | 这几本书的地位我就不多说了,你可以自己看相关的书评。但是,这三本书可能都不容易读,一方面是比较厚,另一方面是知识的密度太大了,所以,读起来有点枯燥和乏味。但是,这没办法,你得忍住。 20 | 21 | 这里要重点说一下《TCP/IP 详解》这本书,是一本很奇怪的书。这本书迄今至少被 [近五百篇学术论文引用过](https://dl.acm.org/doi/book/10.5555/161724) 。这本写给工程师看的书居然被各种学院派的论文来引用,也是很神奇的一件事了。而且,虽然理查德·史蒂文斯不是 TCP 的发明人,但是这本书中把这个协议深入浅出地讲出来,还画了几百张时序图,也是令人叹为观止了。 22 | 23 | 如果你觉得上面这几本经典书比较难啃,你可以试试下面这些通俗易懂的(当然,如果读得懂上面那三本的,下面的这些也就不需要读了)。 24 | 25 | * 《[Linux C 编程一站式学习](https://book.douban.com/subject/4141733/)》。 26 | 27 | * 《[TCP/IP 网络编程](https://book.douban.com/subject/25911735/)》。 28 | 29 | * 《[图解 TCP/IP](https://book.douban.com/subject/24737674/)》,这本书其实并不是只讲了 TCP/IP,应该是叫《计算机网络》才对,主要是给想快速入门的人看的。 30 | 31 | * 《[The TCP/IP Guide](http://www.tcpipguide.com/free/index.htm)》,这本书在豆瓣上的评分 9.2,这里给的链接是这本书的 HTML 英文免费版的,里面的图画得很精彩。 32 | 33 | 另外,学习网络协议不单只是看书,你最好用个抓包工具看看这些网络包是什么样的。所以,这里推荐一本书《[Wireshark 数据包分析实战](https://book.douban.com/subject/21691692/)》。在这本书中,作者结合一些简单易懂的实际网络案例,图文并茂地演示使用 Wireshark 进行数据包分析的技术方法,可以让我们更好地了解和学习网络协议。当然,也拥有了一定的黑客的技能。 34 | 35 | 看完《Unix 高级环境编程》后,你可以趁热打铁看看《[Linux/Unix 系统编程手册](https://book.douban.com/subject/25809330/)》或是罗伯特·拉姆(Robert Love)的 [Linux System Programming](http://igm.univ-mlv.fr/~yahya/progsys/linux.pdf) 英文电子版 。其中文翻译版[Linux 系统编程](https://book.douban.com/subject/25828773/)也值得一读,虽然和《Unix 高级环境编程》很像,不过其主要突出的是 Linux 的一些关键技术和相关的系统调用。 36 | 37 | 关于 TCP 的东西,你还可以看看下面这一系列的文章。 38 | 39 | * [Let’s code a TCP/IP stack, 1: Ethernet & ARP](http://www.saminiir.com/lets-code-tcp-ip-stack-1-ethernet-arp/) 40 | 41 | * [Let’s code a TCP/IP stack, 2: IPv4 & ICMPv4](http://www.saminiir.com/lets-code-tcp-ip-stack-2-ipv4-icmpv4/) 42 | 43 | * [Let’s code a TCP/IP stack, 3: TCP Basics & Handshake](http://www.saminiir.com/lets-code-tcp-ip-stack-3-tcp-handshake/) 44 | 45 | * [Let’s code a TCP/IP stack, 4: TCP Data Flow & Socket API](http://www.saminiir.com/lets-code-tcp-ip-stack-4-tcp-data-flow-socket-api/) 46 | 47 | * [Let’s code a TCP/IP stack, 5: TCP Retransmission](http://www.saminiir.com/lets-code-tcp-ip-stack-5-tcp-retransmission/) 48 | 49 | **对于系统知识,我认为主要有以下一些学习要点**。 50 | 51 | * 用这些系统知识操作一下文件系统,实现一个可以拷贝目录树的小程序。 52 | 53 | * 用 fork / wait / waitpid 写一个多进程的程序,用 pthread 写一个多线程带同步或互斥的程序。比如,多进程购票的程序。 54 | 55 | * 用 signal / kill / raise / alarm / pause / sigprocmask 实现一个多进程间的信号量通信的程序。 56 | 57 | * 学会使用 gcc 和 gdb 来编程和调试程序(参看我的《用 gdb 调试程序》[一](https://blog.csdn.net/haoel/article/details/2879)、[二](https://blog.csdn.net/haoel/article/details/2880)、[三](https://blog.csdn.net/haoel/article/details/2881)、[四](https://blog.csdn.net/haoel/article/details/2882)、[五](https://blog.csdn.net/haoel/article/details/2883)、[六](https://blog.csdn.net/haoel/article/details/2884)、[七](https://blog.csdn.net/haoel/article/details/2885))。 58 | 59 | * 学会使用 makefile 来编译程序(参看我的《跟我一起写 makefile》[一](https://blog.csdn.net/haoel/article/details/2886)、[二](https://blog.csdn.net/haoel/article/details/2887)、[三](https://blog.csdn.net/haoel/article/details/2888)、[四](https://blog.csdn.net/haoel/article/details/2889)、[五](https://blog.csdn.net/haoel/article/details/2890)、[六](https://blog.csdn.net/haoel/article/details/2891)、[七](https://blog.csdn.net/haoel/article/details/2892)、[八](https://blog.csdn.net/haoel/article/details/2893)、[九](https://blog.csdn.net/haoel/article/details/2894)、[十](https://blog.csdn.net/haoel/article/details/2895)、[十一](https://blog.csdn.net/haoel/article/details/2896)、[十二](https://blog.csdn.net/haoel/article/details/2897)、[十三](https://blog.csdn.net/haoel/article/details/2898)、[十四](https://blog.csdn.net/haoel/article/details/2899))。 60 | 61 | * Socket 的进程间通信。用 C 语言写一个 1 对 1 的聊天小程序,或是一个简单的 HTTP 服务器。 62 | 63 | ## C10K 问题 64 | 65 | 然后,当你读完《Unix 网络编程》后,千万要去读一下 “[C10K Problem](http://www.kegel.com/c10k.html) ([中文翻译版](https://www.oschina.net/translate/c10k))”。提出这个问题的人叫丹·凯格尔(Dan Kegel),目前在 Google 任职。 66 | 67 | 他从 1978 年起开始接触计算机编程,是 Winetricks 的作者,也是 Wine 1.0 的管理员,同时也是 Crosstool( 一个让 gcc/glibc 编译器更易用的工具套件)的作者。还是 Java JSR 51 规范的提交者并参与编写了 Java 平台的 NIO 和文件锁,同时参与了 RFC 5128 标准中有关 NAT 穿越(P2P 打洞)技术的描述和定义。 68 | 69 | C10K 问题本质上是**操作系统处理大并发请求的问题**。对于 Web 时代的操作系统而言,对于客户端过来的大量的并发请求,需要创建相应的服务进程或线程。这些进程或线程多了,导致数据拷贝频繁(缓存 I/O、内核将数据拷贝到用户进程空间、阻塞), 进程 / 线程上下文切换消耗大,从而导致资源被耗尽而崩溃。这就是 C10K 问题的本质。 70 | 71 | 了解这个问题,并了解操作系统是如何通过多路复用的技术来解决这个问题的,有助于你了解各种 I/O 和异步模型,这对于你未来的编程和架构能力是相当重要的。 72 | 73 | 另外,现在,整个世界都在解决 C10M 问题,推荐看看 [The Secret To 10 Million Concurrent Connections -The Kernel Is The Problem, Not The Solution](http://highscalability.com/blog/2013/5/13/the-secret-to-10-million-concurrent-connections-the-kernel-i.html) 一文。 74 | 75 | ## 实践项目 76 | 77 | 我们已经学习完了编程语言、理论学科和系统知识三部分内容,下面就来做几个实践项目,小试牛刀一下。实现语言可以用 C、C++ 或 Java。 78 | 79 | 实现一个 telnet 版本的聊天服务器,主要有以下需求。 80 | 81 | * 每个客户端可以用使用telnet ip:port的方式连接到服务器上。 82 | 83 | * 新连接需要用用户名和密码登录,如果没有,则需要注册一个。 84 | 85 | * 然后可以选择一个聊天室加入聊天。 86 | 87 | * 管理员有权创建或删除聊天室,普通人员只有加入、退出、查询聊天室的权力。 88 | 89 | * 聊天室需要有人数限制,每个人发出来的话,其它所有的人都要能看得到。 90 | 91 | 实现一个简单的 HTTP 服务器,主要有以下需求。 92 | 93 | * 解释浏览器传来的 HTTP 协议,只需要处理 URL path。 94 | 95 | * 然后把所代理的目录列出来。 96 | 97 | * 在浏览器上可以浏览目录里的文件和下级目录。 98 | 99 | * 如果点击文件,则把文件打开传给浏览器(浏览器能够自动显示图片、PDF,或 HTML、CSS、JavaScript 以及文本文件)。 100 | 101 | * 如果点击子目录,则进入到子目录中,并把子目录中的文件列出来。 102 | 103 | 实现一个生产者 / 消费者消息队列服务,主要有以下需求。 104 | 105 | * 消息队列采用一个 Ring-buffer 的数据结构。 106 | 107 | * 可以有多个 topic 供生产者写入消息及消费者取出消息。 108 | 109 | * 需要支持多个生产者并发写。 110 | 111 | * 需要支持多个消费者消费消息(只要有一个消费者成功处理消息就可以删除消息)。 112 | 113 | * 消息队列要做到不丢数据(要把消息持久化下来)。 114 | 115 | * 能做到性能很高。 116 | 117 | ## 小结 118 | 119 | 到今天,我们已经学习完了专业编程方面最为重要的三部分内容:编程语言、理论学科和系统知识,我们针对这些内容做个小结。如果想看完我推荐的那些书和知识,并能理解和掌握,我估计怎么也得需要 4-5 年的时间。嗯,是的,就是一个计算机科学系科班出身的程序员需要学习的一些东西。这其中,最重要的是下面这几点。 120 | 121 | **编程语言**。以工业级的 C、C++、Java 这三门语言为主,这三门语言才是真正算得上工业级的编程语言,因为有工业级的标准化组织在控制着这几门语言,而且也有工业级的企业应用。尤其是 Java,还衍生出了大量的企业级架构上的开源生态。你至少需要掌握 C 语言和 Java 语言,这对你以后面对各式各样的编程语言是非常重要的。 122 | 123 | 此外,还推荐学习 Go 语言,它已成为云计算领域事实上的标准语言,尤其是在 Docker、Kubernetes 等项目中。而且,Go 语言在国内外一些知名公司中有了一定的应用和实践,并且其生态圈也越来越好。 124 | 125 | **算法和数据结构**。这个太重要了,尤其是最基础的算法和数据结构,这是任何一个称职的程序员都需要学习和掌握的。你必需要掌握。 126 | 127 | **计算机的相关系统**。你至少要掌握三个系统的基础知识,一个是操作系统,一个是网络系统,还有一个是数据库系统。它们分别代表着计算机基础构架的三大件——计算、存储、网络。 128 | 129 | 如果你能够走到这里,把前面的那些知识都了解了(不用精通,因为精通是需要时间和实践来慢慢锤炼出来的,所以,你也不用着急),那么你已经是一个合格的程序员了,而且你的潜力和可能性是非常非常高的。 130 | 131 | 如果经历过这些比较枯燥的理论知识,而且你还能有热情和成就感,那么我要恭喜你了。因为你已经超过了绝大多数人,而且还是排在上游的比较抢手的程序员了。我相信你至少可以找到年薪 50 万以上的工作了。 132 | 133 | 但是,你还需要很多的经验或是一些实践,以及一些大系统大项目的实际动手的经验。没关系,我们后面会有教你怎么实操的方法和攻略。 134 | 135 | 但是,往后面走,你需要开始需要术业有专攻了。下面给一些建议的方向。 136 | 137 | * 底层方向:操作系统、文件系统、数据库、网络…… 138 | 139 | * 架构方向:分布式系统架构、微服务、DevOps、Cloud Native…… 140 | 141 | * 数据方向:大数据、机器学习、人工智能…… 142 | 143 | * 前端方向:你对用户体验或是交互更感兴趣,那么你走前端的路吧。 144 | 145 | * 其它方向:比如,安全开发、运维开发、嵌入式开发…… 146 | 147 | 这些方向你要仔细选择,因为一旦选好,就要勇往直前地走下去,当然,你要回头转别的方向也没什么问题,因为你有前面的这些基础知识在身,所以,不用害怕。只是不同的方向上会有不同的经验积累,经验积累是看书看不来的,这个是转方向的成本。 148 | 149 | 下篇文章,我们将进入《软件设计篇》。敬请期待。 -------------------------------------------------------------------------------- /09_Linux系统、内存和网络/README.md: -------------------------------------------------------------------------------- 1 | # Linux系统、内存和网络 2 | 3 | 这一篇章,是本系列中最长的一篇,其中包括了如下的内容。 4 | 5 | * **系统底层相关**。 主要是以 Linux 系统为主,其中有大量的文章可以让你学习到 Linux 内核,以及内存、网络、异步 I/O 模型、Lock-free 的无锁编程,还有其它和系统底层相关的东西。注意,系统底层要是深下去是可以完全不见底的。而且内存方面的知识也是比较多的,所以,这里还是主要给出一些非常有价值的基础性的知识和技术。学好这些东西,你会对系统有很深的理解,而且可以把这些知识反哺到架构设计上来。 6 | * **数据库相关**。数据库方面主要是 MySQL 和各种开源 NoSQL 的一些相关的有价值的文章和导读,主要是让你对这些数据库的内在有一定的了解,但又不会太深。真正的深入是需要扎入到源代码中的。需要说明的是,这块技术不是我的长项,但又是每个架构师需要知道的,所以,我在这里给的学习资源可能会比较浅,这点还希望你来补充和指正。 7 | * **分布式架构**。这一部分是最长最多的。其中有架构入门、分布式理论中各种非常有价值的经典论文,然后是一些分布式工程设计方面的文章,其中包括设计模式和工程应用,最后还有各大公司的架构供参考。 8 | * **微服务**。有了分布式架构理论和工程的基础,接下来是对微服务的学习。在这部分内容中,我会罗列几个介绍微服务架构非常系统的文章,然后比较一下微服务和 SOA 的差别,最后则是一些工程实践和最佳实践。 9 | * **容器化和自动化运维**。在容器化和自动化运维中,主要是学习 Docker 和 Kubernetes 这两个自动化运维的杀手型技术。而不是 Salt、Puppet、Chef 和 Ansible 这样比较传统的工具。原因很简单,因为自动化部署根本不够,还需要对环境和运行时的管理和运维才够,而只有 Docker 和 Kubernetes 才是未来。所以,这里重点让你学习这两个技术,其中有很多文章需要一些系统底层的知识。 10 | * **机器学习和人工智能**。机器学习和人工智能,也不是我的长项,我也只是一个入门者。这里,我主要给了一些基础性的知识,其中包括基本原理、图书、课程、文章和相关的算法。你顺着我画的这路走,不能说能成为一个人工智能专家,但成为一个机器学习的高级工程师甚至准专家还是可能的。 11 | * **前端开发**。这里的前端主要是 HTML 5 的前端了,这一节会带你学习一下前端开发所需要知道的基础知识,尤其是对前端开发语言 JavaScript 的学习,我花费了相当的篇幅列出了很多很经典的学习资料,必定会让你成为一个 JavaScript 高手。然后你还需要了解浏览器是怎样工作的,还有相关的网络协议和一些性能优化的技巧。最后则是 JavaScript 框架的学习,这里我只给了 React.js 和 Vue.js,并通过 React.js 带出来函数式编程的学习。我虽然不是一个前端程序员,但是,我相信我这个后端程序员给出来的这组前端开发的学习资料和路径会比前端程序员更靠谱一些。 12 | * **信息源**。最后,则是一些信息源,其中包括各大公司的技术 Blog,还有相关的论文集散地。 13 | 14 | 另外,这里需要说明几点。 15 | 16 | * 我假设你在前面已经打下了非常扎实的基础,但是要成为一个高手,基础知识只是一个地基,你还需要很多更为具体的技术。对我来说,就是看各种各样的文章、手册、论文、分享…… 其实,学习到一定程度,就是要从书本中走出去,到社区里和大家一起学习,而且还需要自己找食吃了。所以,对于这里面的文章,有很多都是在罗列各种文章和资源,只是为你梳理信息源,而不是喂你吃饭。 17 | 18 | * **老实说,我已经为你梳理并过滤掉了很多的信息,这里只留下了 30% 我觉得最经济也最有价值的信息**。虽然对于不同定位和不同需求的人还可以再对这些信息进行删减,但是觉得我这么一做就会对其它人不公平了。所以,这也是我觉得最小数量集的信息和资源吧。**你也可以把我这里的东西当成一个索引来对待**。 19 | 20 | * 这些内容,不能说是隔离开来的,应该说是相辅相成的。也没什么顺序,可以各取所需。虽然看上去内容很多,但你也别害怕,真的不用害怕,你会越学越快,越实践越有感觉,也越有效率。在一开始可能会很慢,但是坚持住,积累一段时间后就会越来越快的。 而且,我要告诉你,绝大多数人是坚持不下来的。只要你能坚持下来,我保证,你一定会成为各个大公司的抢手货,这点你一定要相信我。**你不需要特别努力,只需要日进一步,3-5 年后,你就会发现,绝大多数人都在你身后很远的地方了**。 21 | 22 | 今天分享的内容为系统底层知识中的 Linux 系统、内存和网络等方面的相关知识及推荐的学习资料。 23 | 24 | ## Linux 系统相关 25 | 26 | 学习 Linux 操作系统的原理是通向系统工程师的必经之路。我觉得,Unix/Linux 操作系统里的东西并不难学。你千万不要一下子扎到源代码里去,那样没用——你还是要在上层先通过读一些不错的文档来学习。下面我罗列了一些很不错的站点,其中有很多内容供你去钻研和探索。 27 | 28 | 我在这里默认你前面已经读过并读懂了我推荐的那些和 Unix/Linux 相关的图书了。所以,我相信你对 Unix/Linux 下的编程已经是有一些基础了,因此,你继续深挖 Linux 下的这些知识应该也不是很难的事了。 29 | 30 | * [Red Hat Enterprise Linux 文档](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/)。Red Hat Enterprise Linux(RHEL)是老牌 Linux 厂商 Red Hat 出品的面向商业的 Linux 发行版。Red Hat 网站上的这个文档中有很多很有价值的内容,值得一看。 31 | 32 | * [Linux Insides](https://github.com/0xAX/linux-insides),GitHub 上的一个开源电子书,其中讲述了 Linux 内核是怎样启动、初始化以及进行管理的。 33 | 34 | * [LWN’s kernel page](https://lwn.net/Kernel/Index/) ,上面有很多非常不错的文章来解释 Linux 内核的一些东西。 35 | 36 | * [Learn Linux Kernel from Android Perspective](https://learnlinuxconcepts.blogspot.com/2014/10/this-blog-is-to-help-those-students-and.html) ,从 Android 的角度来学习 Linux 内核,这个站点上的 Blog 相对于前面的比较简单易读一些。 37 | 38 | * [Linux Kernel Doc](https://www.kernel.org/doc/), Linux 的内核文档也可以浏览一下。 39 | 40 | * [Kernel Planet](https://planet.kernel.org/) ,Linux 内核开发者的 Blog,有很多很不错的文章和想法。 41 | 42 | * [Linux Performance and Tuning Guidelines](https://lenovopress.com/redp4285.pdf),这是 IBM 出的红皮书,虽然有点老了,但还是非常值得一读的。 43 | 44 | * [TLK: The Linux Kernel](https://tldp.org/LDP/tlk/tlk.html),这是一本相对比较老的书了,Linux 内核版本为 2.0.33,但了解一下前人的思路,也是很有帮助的。 45 | 46 | * [Linux Performance](http://www.brendangregg.com/linuxperf.html),这个网站上提供了和 Linux 系统性能相关的各种工具和文章收集,非常不错。 47 | 48 | * [Optimizing web servers for high throughput and low latency](https://dropbox.tech/infrastructure/optimizing-web-servers-for-high-throughput-and-low-latency),这是一篇非常底层的系统调优的文章,来自 DropBox,从中你可以学到很多底层的性能调优的经验和知识。 49 | 50 | ## 内存相关 51 | 52 | 计算机内存管理是每一个底层程序员需要了解的非常重要的事儿。当然,这里我们重点还是 Linux 操作系统相关的内存管理上的知识。 53 | 54 | 首先,LWN.net 上有一系列的 “**What every programmer should know about memory**” 文章你需要读一下。当然,你可以直接访问一个完整的 [PDF](http://futuretech.blinkenlights.nl/misc/cpumemory.pdf) 文档。下面是这个系列文章的网页版列表。读完这个列表的内容,你基本上就对内存有了一个比较好的知识体系了。 55 | 56 | * [Part 1: Introduction](https://lwn.net/Articles/250967/) ,中译版为 “[每个程序员都应该了解的内存知识【第一部分】](https://www.oschina.net/translate/what-every-programmer-should-know-about-memory-part1)” 57 | 58 | * [Part 2: CPU caches](https://lwn.net/Articles/252125/) 59 | 60 | * [Part 3 (Virtual memory)](https://lwn.net/Articles/253361/) 61 | 62 | * [Part 4 (NUMA systems)](https://lwn.net/Articles/254445/) 63 | 64 | * [Part 5 (What programmers can do - cache optimization)](https://lwn.net/Articles/255364/) 65 | 66 | * [Part 6 (What programmers can do - multi-threaded optimizations)](https://lwn.net/Articles/256433/) 67 | 68 | * [Part 7 (Memory performance tools)](https://lwn.net/Articles/257209/) 69 | 70 | * [Part 8 (Future technologies)](https://lwn.net/Articles/258154/) 71 | 72 | * [Part 9 (Appendices and bibliography)](https://lwn.net/Articles/258188/) 73 | 74 | 然后是几篇和内存相关的论文。下面这三篇论文是我个人觉得能对你非常有帮助的文章,尤其是你要做一些程序的性能优化方面。 75 | 76 | * [Memory Barriers: a Hardware View for Software Hackers](http://irl.cs.ucla.edu/~yingdi/web/paperreading/whymb.2010.06.07c.pdf)。内存的读写屏障是线程并发访问共享的内存数据时,从程序本身、编译器到 CPU 都必须遵循的一个规范。有了这个规范,才能保证访问共享的内存数据时,一个线程对该数据的更新能被另一个线程以正确的顺序感知到。在 SMP(对称多处理)这种类型的多处理器系统(包括多核系统)上,这种读写屏障还包含了复杂的缓存一致性策略。这篇文章做了详细解释。 77 | 78 | * [A Tutorial Introduction to the ARM and POWER Relaxed Memory Models](https://www.cl.cam.ac.uk/~pes20/ppc-supplemental/test7.pdf),对 ARM 和 POWER 的宽松内存模型的一个教程式的简介。本篇文章的焦点是 ARM 和 POWER 体系结构下多处理器系统内存并发访问一致性的设计思路和使用方法。与支持较强的 TSO 模型的 x86 体系结构不同,ARM 和 POWER 这两种体系结构出于对功耗和性能的考虑,使用了一种更为宽松的内存模型。本文详细讨论了 ARM 和 POWER 的模型。 79 | 80 | * [x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors](https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf),介绍 x86 的多处理器内存并发访问的一致性模型 TSO。 81 | 82 | 接下来是开发者最关心的内存管理方面的 lib 库。通常来说,我们有三种内存分配管理模块。就目前而言,BSD 的 jemalloc 有很大的影响力。后面我们可以看到不同公司的实践性文章。 83 | 84 | * [ptmalloc](http://www.malloc.de/en/) 是 glibc 的内存分配管理。 85 | 86 | * [tcmalloc](https://github.com/gperftools/gperftools) 是 Google 的内存分配管理模块,全称是 Thread-Caching malloc,基本上来说比 glibc 的 ptmalloc 快两倍以上。 87 | 88 | * [jemalloc](http://jemalloc.net/) 是 BSD 提供的内存分配管理。其论文为 [A Scalable Concurrent malloc(3) Implementation for FreeBSD](https://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf),这是一个可以并行处理的内存分配管理器。 89 | 90 | 关于 C 的这些内存分配器,你可以参看 Wikipedia 的 “[C Dynamic Memory Allocation](https://en.wikipedia.org/wiki/C_dynamic_memory_allocation#Thread-caching_malloc_(tcmalloc))”这个词条。 91 | 92 | 下面是几篇不错的文章,让你感觉一下上面那三种内存分配器的一些比较和工程实践。 93 | 94 | * [ptmalloc,tcmalloc 和 jemalloc 内存分配策略研究](https://owent.net/2013/867.html) 95 | 96 | * [内存优化总结:ptmalloc、tcmalloc 和 jemalloc](http://www.cnhalo.net/2016/06/13/memory-optimize/) 97 | 98 | * [Scalable memory allocation using jemalloc](https://www.facebook.com/notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919) 99 | 100 | * [Decreasing RAM Usage by 40% Using jemalloc with Python & Celery](https://zapier.com/engineering/celery-python-jemalloc/) 101 | 102 | ## 计算机网络 103 | 104 | ### 网络学习 105 | 106 | 首先,推荐一本书——《[计算机网络(第五版)](https://book.douban.com/subject/10510747/)》,这本“计算机网络”和前面推荐的那本计算机网络不一样,前面那本偏扫盲,这本中有很多细节。这本书是国内外使用最广泛、最权威的计算机网络经典教材。全书按照网络协议模型自下而上(物理层、数据链路层、介质访问控制层、网络层、传输层和应用层)有系统地介绍了计算机网络的基本原理,并结合 Internet 给出了大量的协议实例。 107 | 108 | 这本书还与时俱进地引入了最新的网络技术,包括无线网络、3G 蜂窝网络、RFID 与传感器网络、内容分发与 P2P 网络、流媒体传输与 IP 语音,以及延迟容忍网络等。另外,本书针对当前网络应用中日益突出的安全问题,用了一整章的篇幅对计算机网络的安全性进行了深入讨论,而且把相关内容与最新网络技术结合起来阐述。这本书读起来并不枯燥,因为其中有很多小故事和小段子。 109 | 110 | 然后,有两个网上的教程和讲义也可以让人入门。 111 | 112 | * 渥汰华大学的一个课程讲义你也可以一看 [Computer Network Design](http://www.site.uottawa.ca/~shervin/courses/ceg4185/lectures/) 。 113 | 114 | * GeeksforGeeks 上也有一个简单的 [Computer Network Tutorials](https://www.geeksforgeeks.org/computer-network-tutorials/) 。 115 | 116 | ### 网络调优 117 | 118 | 接下来,你可能需要一些非常实用的可以操作的技术,下面的几篇文章相信可以帮助到你。 119 | 120 | * 《Linux 的高级路由和流量控制 HowTo》([Linux Advanced Routing & Traffic Control HOWTO](https://lartc.org/) ),这是一个非常容易上手的关于 iproute2、流量整形和一点 netfilter 的指南。 121 | 122 | * 关于网络调优,你可以看一下这个文档 [Red Hat Enterprise Linux Network Performance Tuning Guide](https://access.redhat.com/sites/default/files/attachments/20150325_network_performance_tuning.pdf)。 123 | 124 | * 还有一些网络工具能够帮上你的大忙,这里有一个网络工具的 Awesome 列表 [Awesome Pcap Tools](https://github.com/caesar0301/awesome-pcaptools) ,其中罗列了各种网络工具,能够让你更从容地调试网络相关的程序。 125 | 126 | * [Making Linux TCP Fast](https://netdevconf.info/1.2/papers/bbr-netdev-1.2.new.new.pdf) ,一篇非常不错的 TCP 调优的论文。 127 | 128 | * 下面是在 PackageCloud 上的两篇关于 Linux 网络栈相关的底层文章,非常值得一读。 129 | * [Monitoring and Tuning the Linux Networking Stack: Receiving Data](https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/) 130 | 131 | * [Monitoring and Tuning the Linux Networking Stack: Sending Data](https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/) 132 | 133 | ### 网络协议 134 | 135 | 接下来,想要学习网络协议最好的方式就是学习通讯相关的 RFC。所以,在这里我会推荐一系列值得读的 RFC 给你。读 RFC 有几个好处,一方面可以学习技术,另一方面,你可以通过 RFC 学习到一个好的技术文档是怎么写的,还能看到各种解决问题的方案和思路。 136 | 137 | 对于第 2 层链路层,你可能需要了解一下 ARP: 138 | 139 | * [RFC 826 - An Ethernet Address Resolution Protocol](https://tools.ietf.org/html/rfc826) 140 | 141 | 以及 Tunnel 相关的协议: 142 | 143 | * [RFC 1853 - IP in IP Tunneling](https://tools.ietf.org/html/rfc1853) 144 | 145 | * [RFC 2784 - Generic Routing Encapsulation (GRE)](https://tools.ietf.org/html/rfc2784) 146 | 147 | * [RFC 2661 - Layer Two Tunneling Protocol “L2TP”](https://tools.ietf.org/html/rfc2661) 148 | 149 | * [RFC 2637 - Point-to-Point Tunneling Protocol (PPTP)](https://tools.ietf.org/html/rfc2637) 150 | 151 | 对于第 4 层,你最需要了解的是 TCP/IP 了。和 TCP 相关的 RFC 相当多,这里给一系列经典的 RFC。这些 RFC 我都引用在了我在 CoolShell 上的《[TCP 的那些事儿(上)](https://coolshell.cn/articles/11564.html)》和《[TCP 的那些事儿(下)](https://coolshell.cn/articles/11609.html)》两篇文章中。如果你看不懂 RFC,你也可以去看我上述的文章。 152 | 153 | * [RFC 793 - Transmission Control Protocol](https://tools.ietf.org/html/rfc793) - 最初的 TCP 标准定义,但不包括 TCP 相关细节。 154 | 155 | * [RFC 813 - Window and Acknowledgement Strategy in TCP](https://tools.ietf.org/html/rfc813) - TCP 窗口与确认策略,并讨论了在使用该机制时可能遇到的问题及解决方法。 156 | 157 | * [RFC 879 - The TCP Maximum Segment Size and Related Topics](https://tools.ietf.org/html/rfc879) - 讨论 MSS 参数对控制 TCP 分组大小的重要性,以及该参数与 IP 分段大小的关系等。 158 | 159 | * [RFC 896 - Congestion Control in IP/TCP Internetworks](https://tools.ietf.org/html/rfc896) - 讨论拥塞问题和 TCP 如何控制拥塞。 160 | 161 | * [RFC 2581 - TCP Congestion Control](https://tools.ietf.org/html/rfc2581) - 描述用于拥塞控制的四种机制:慢启动、拥塞防御、快重传和快恢复。后面这个 RFC 被 [RFC 5681](https://tools.ietf.org/html/rfc5681) 所更新。还有 [RFC 6582 - The NewReno Modification to TCP’s Fast Recovery Algorithm](https://tools.ietf.org/html/rfc6582) 中一个改进的快速恢复算法。 162 | 163 | * [RFC 2018 - TCP Selective Acknowledgment Options](https://tools.ietf.org/html/rfc2018) - TCP 的选择确认。 164 | 165 | * [RFC 2883 - An Extension to the Selective Acknowledgement (SACK) Option for TCP](https://tools.ietf.org/html/rfc2883) - 对于 RFC 2018 的改进。 166 | 167 | * [RFC 2988 - Computing TCP’s Retransmission Timer](https://tools.ietf.org/html/rfc2988) - 讨论与 TCP 重传计时器设置相关的话题,重传计时器控制报文在重传前应等待多长时间。也就是经典的 TCP Karn/Partridge 重传算法。 168 | 169 | * [RFC 6298 - Computing TCP’s Retransmission Timer](https://tools.ietf.org/html/rfc6298) - TCP Jacobson/Karels Algorithm 重传算法。 170 | 171 | 我个人觉得 TCP 最牛的不是不丢包,而是拥塞控制。对此,如果你感兴趣,可以读一下经典论文《[Congestion Avoidance and Control](https://ee.lbl.gov/papers/congavoid.pdf)》。 172 | 173 | 关于 Linux 下的 TCP 参数,你需要仔仔细细地读一下[TCP 的 man page](https://man7.org/linux/man-pages/man7/tcp.7.html) 。 174 | 175 | 对于第 7 层协议,HTTP 协议是重点要学习的。 176 | 177 | 首先推荐的是《[HTTP 权威指南](https://book.douban.com/subject/10746113/) 》,这本书有点厚,可以当参考书来看。这本书中没有提到 HTTP/2 的事,但是可以让你了解到 HTTP 协议的绝大多数特性。 178 | 179 | HTTP 1.1 的原始 RFC 是 1999 年 6 月的 [RFC 2616](https://tools.ietf.org/html/rfc2616),但其在 2014 后很快被下面这些 RFC 给取代了。 180 | 181 | * [RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing](https://tools.ietf.org/html/rfc7230) 182 | 183 | * [RFC 7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content](https://tools.ietf.org/html/rfc7231) 184 | 185 | * [RFC 7232 - Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests](https://tools.ietf.org/html/rfc7232) 186 | 187 | * [RFC 7233 - Hypertext Transfer Protocol (HTTP/1.1): Range Requests](https://tools.ietf.org/html/rfc7233) 188 | 189 | * [RFC 7234 - Hypertext Transfer Protocol (HTTP/1.1): Caching](https://tools.ietf.org/html/rfc7234) 190 | 191 | * [RFC 7235 - Hypertext Transfer Protocol (HTTP/1.1): Authentication](https://tools.ietf.org/html/rfc7235) 192 | 193 | 关于[HTTP/2](https://en.wikipedia.org/wiki/HTTP/2),这是 HTTP 的一个比较新的协议,它于 2015 年被批准通过,现在基本上所有的主流浏览器都默认启用这个协议。所以,你有必要学习一下这个协议。下面是相关的学习资源。 194 | 195 | * [Gitbook - HTTP/2 详解](https://ye11ow.gitbooks.io/http2-explained/content/) 196 | 197 | * [http2 explained](https://daniel.haxx.se/http2/)([中译版](https://ye11ow.gitbooks.io/http2-explained/content/)) 198 | 199 | * [HTTP/2 for a Faster Web](https://cascadingmedia.com/insites/2015/03/http-2.html) 200 | 201 | * [Nginx HTTP/2 白皮书](https://www.nginx.com/wp-content/uploads/2015/09/NGINX_HTTP2_White_Paper_v4.pdf) 202 | 203 | * HTTP/2 的两个 RFC: 204 | * [RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2) ](https://httpwg.org/specs/rfc7540.html),HTTP/2 的协议本身 205 | * [RFC 7541 - HPACK: Header Compression for HTTP/2](https://httpwg.org/specs/rfc7541.html) ,HTTP/2 的压缩算法 206 | 207 | 最后,你可以上 Wikipedia 的 [Internet Protocol Suite](https://en.wikipedia.org/wiki/Internet_protocol_suite) 上看看,这是一个很不错的网络协议的词条汇集地。顺着这些协议,你可以找到很多有用的东西。 208 | 209 | ## 小结 210 | 211 | 好了,总结一下今天的内容。这是程序员练级攻略 2018 版第五篇章——高手成长篇的第一篇文章。前面的内容先介绍了一些这一系列内容的总体构成,及每一部分的学习重点。后面是这一篇章第一个主题系统底层知识中的部分内容,即 Linux 系统、内存和计算机网络,并给出了相应的学习资料。 212 | 213 | 我认为,学习到一定程度,就是要从书本中走出去,到社区里和大家一起学习,而且还需要自己找食吃了。所以,这篇文章中,我罗列了各种文章和资源,并给出了简短的推荐语言,就是在为你梳理信息源,而不是喂你吃饭。我更希望看到你自趋势地成长。 214 | 215 | 下篇文章中,我们分享的内容为系统底层知识中的异步 I/O 模型、Lock-Free 编程以及其他一些相关的知识点和学习资源。敬请期待。 -------------------------------------------------------------------------------- /10_异步IO模型和Lock-Free编程/README.md: -------------------------------------------------------------------------------- 1 | # 异步I/O模型和Lock-Free编程 2 | 3 | ## 异步 I/O 模型 4 | 5 | 异步 I/O 模型是我个人觉得所有程序员都必需要学习的一门技术或是编程方法,这其中的设计模式或是解决方法可以借鉴到分布式架构上来。再说一遍,学习这些模型,是非常非常重要的,你千万要认真学习。 6 | 7 | 史蒂文斯(Stevens)在《[UNIX 网络编程](https://time.geekbang.org/column/article/9851)》一书 6.2 I/O Models 中介绍了五种 I/O 模型。 8 | 9 | * 阻塞 I/O 10 | 11 | * 非阻塞 I/O 12 | 13 | * I/O 的多路复用(select 和 poll) 14 | 15 | * 信号驱动的 I/O(SIGIO) 16 | 17 | * 异步 I/O(POSIX 的 aio_functions) 18 | 19 | 然后,在前面我们也阅读过了 - [C10K Problem](https://en.wikipedia.org/wiki/C10k_problem) 。相信你对 I/O 模型也有了一定的了解。 这里,我们需要更为深入地学习 I/O 模型,尤其是其中的异步 I/O 模型。 20 | 21 | 首先,我们看一篇和 Java 相关的 I/O 模型的文章来复习一下之前的内容。[Thousands of Threads and Blocking I/O: The Old Way to Write Java Servers Is New Again (and Way Better)](https://www.slideshare.net/e456/tyma-paulmultithreaded1) ,这个 PPT 中不仅回顾和比较了各种 I/O 模型,而且还有各种比较细节的方案和说明,是一篇非常不错的文章。 22 | 23 | 然后,你可以看一篇 Java 相关的 PPT - 道格·莱亚(Doug Lea)的 [Scalable IO in Java](http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf),这样你会对一些概念有个了解。 24 | 25 | 接下来,我们需要了解一下各种异步 I/O 的实现和设计方式。 26 | 27 | * [IBM - Boost application performance using asynchronous I/O](https://developer.ibm.com/technologies/linux/articles/l-async/) ,这是一篇关于 AIO 的文章。 28 | 29 | * [Lazy Asynchronous I/O For Event-Driven Servers](https://www.usenix.org/legacy/event/usenix04/tech/general/full_papers/elmeleegy/elmeleegy_html/html.html) ,这篇文章也很不错。 30 | * 另外,异步 I/O 模型中的 [Windows I/O Completion Ports](https://docs.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports) , 你也需要了解一下。如果 MSDN 上的这个手册不容易读,你可以看看这篇文章 [Inside I/O Completion Ports](http://mirrors.arcadecontrols.com/www.sysinternals.com/Information/IoCompletionPorts.html)。另外,关于 Windows,[Windows Internals](https://book.douban.com/subject/6935552/) 这本书你可以仔细读一下,非常不错的。其中有一节 I/O Processing 也是很不错的,这里我给一个网上免费的链接[I/O Processing](https://flylib.com/books/en/4.491.1.85/1/) 你可以看看 Windows 是怎么玩的。 31 | 32 | * 接下来是 Libevent。你可以看一下其主要维护人员尼克·马修森(Nick Mathewson)写的 [Libevent 2.0 book](http://www.wangafu.net/~nickm/libevent-book/)。还有一本国人写的电子书 《[Libevent 深入浅出](https://aceld.gitbooks.io/libevent/content/)》。 33 | 34 | * 再接下来是 Libuv。你可以看一下其官网的 [Libuv Design Overview](http://docs.libuv.org/en/v1.x/design.html) 了解一下。 35 | 36 | 我简单总结一下,基本上来说,异步 I/O 模型的发展技术是: select -> poll -> epoll -> aio -> libevent -> libuv。Unix/Linux 用了好几十年走过这些技术的变迁,然而,都不如 Windows I/O Completion Port 设计得好(免责声明:这个观点纯属个人观点。相信你仔细研究这些 I/O 模型后,你会有自己的判断)。 37 | 38 | 看过这些各种异步 I/O 模式的实现以后,相信你会看到一个编程模式——Reactor 模式。下面是这个模式的相关文章(读这三篇就够了)。 39 | 40 | * [Understanding Reactor Pattern: Thread-Based and Event-Driven](https://dzone.com/articles/understanding-reactor-pattern-thread-based-and-eve) 41 | 42 | * [Reactor Pattern](https://www.dre.vanderbilt.edu/~schmidt/PDF/Reactor2-93.pdf) 43 | 44 | * [The reactor pattern and non-blocking IO](https://my.oschina.net/u/138995/blog/178954) 45 | 46 | 然后是几篇有意思的延伸阅读文章。 47 | 48 | * [The Secret To 10 Million Concurrent Connections -The Kernel Is The Problem, Not The Solution](http://highscalability.com/blog/2013/5/13/the-secret-to-10-million-concurrent-connections-the-kernel-i.html) - C10M 问题来了…… 49 | 50 | * 还有几篇可能有争议的文章,让你从不同的角度思考。 51 | * [Select is fundamentally broken](https://idea.popcount.org/2017-01-06-select-is-fundamentally-broken/) 52 | * [Epoll is fundamentally broken 1/2](https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/) 53 | * [Epoll is fundamentally broken 2/2](https://idea.popcount.org/2017-03-20-epoll-is-fundamentally-broken-22/) 54 | 55 | ## Lock-Free 编程相关 56 | 57 | Lock-Free - 无锁技术越来越被开发人员重视,因为锁对于性能的影响实在是太大了,所以如果想开发出一个高性能的程序,你就非常有必要学习 Lock-Free 的编程方式。 58 | 59 | 关于无锁的数据结构,有几篇教程你可以看一下。 60 | 61 | * [Dr.Dobb’s: Lock-Free Data Structures](https://www.drdobbs.com/lock-free-data-structures/184401865) 62 | 63 | * [Andrei Alexandrescu: Lock-Free Data Structures](https://erdani.com/publications/cuj-2004-10.pdf) 64 | 65 | 然后强烈推荐一本免费的电子书:[Is Parallel Programming Hard, And, If So, What Can You Do About It?](https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html) ,这是大牛 [保罗·麦肯尼(Paul E. McKenney)](https://www.linkedin.com/in/paulmckenney/) 写的书。这本书堪称并行编程的经典书,必看。 66 | 67 | 此时,Wikipedia 上有三个词条你要看一下,以此了解并发编程中的一些概念:[Non-blocking algorithm](https://en.wikipedia.org/wiki/Non-blocking_algorithm) 、[Read-copy-update](https://en.wikipedia.org/wiki/Read-copy-update) 和 [Seqlock](https://en.wikipedia.org/wiki/Seqlock)。 68 | 69 | 接下来,读一下以下两篇论文 。 70 | 71 | * [Implementing Lock-Free Queues](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.53.8674&rep=rep1&type=pdf), 这也是一篇很不错的论文,我把它介绍在了我的网站上 ,文章为“[无锁队列的实现](https://coolshell.cn/articles/8239.html)”。 72 | 73 | * [Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms](https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf) ,这篇论文给出了一个无阻塞和阻塞的并发队列算法。 74 | 75 | 最后,有几个博客你要订阅一下。 76 | 77 | * [1024cores](https://www.1024cores.net/) - 德米特里·伐由科夫(Dmitry Vyukov)的和 lock-free 编程相关的网站。 78 | 79 | * [Paul E. McKenney](https://paulmck.livejournal.com/) - 保罗(Paul)的个人网站。 80 | 81 | * [Concurrency Freaks](http://concurrencyfreaks.blogspot.com/) - 关于并发算法和相关模式的网站。 82 | 83 | * [Preshing on Programming](https://preshing.com/) - 加拿大程序员杰夫·普莱辛(Jeff Preshing)的技术博客,主要关注 C++ 和 Python 两门编程语言。他用 C++11 实现了类的反射机制,用 C++ 编写了 3D 小游戏 Hop Out,还为该游戏编写了一个游戏引擎。他还讨论了很多 C++ 的用法,比如 C++14 推荐的代码写法、新增的某些语言构造等,和 Python 很相似。阅读这个技术博客上的内容能够深深感受到博主对编程世界的崇敬和痴迷。 84 | 85 | * [Sutter’s Mill](https://herbsutter.com/) - 赫布·萨特(Herb Sutter)是一位杰出的 C++ 专家,曾担任 ISO C++ 标准委员会秘书和召集人超过 10 年。他的博客有关于 C++ 语言标准最新进展的信息,其中也有他的演讲视频。博客中还讨论了其他技术和 C++ 的差异,如 C# 和 JavaScript,它们的性能特点、怎样避免引入性能方面的缺陷等。 86 | 87 | * [Mechanical Sympathy](https://mechanical-sympathy.blogspot.com/) - 博主是马丁·汤普森(Martin Thompson),他是一名英国的技术极客,探索现代硬件的功能,并提供开发、培训、性能调优和咨询服务。他的博客主题是 Hardware and software working together in harmony,里面探讨了如何设计和编写软件使得它在硬件上能高性能地运行。非常值得一看。 88 | 89 | 接下来,是一些编程相关的一些 C/C++ 的类库,这样你就不用从头再造轮子了(对于 Java 的,请参看 JDK 里的 Concurrent 开头的一系列的类)。 90 | 91 | * [Boost.Lockfree](https://www.boost.org/doc/libs/1_60_0/doc/html/lockfree.html) - Boost 库中的无锁数据结构。 92 | 93 | * [ConcurrencyKit](https://github.com/concurrencykit/ck) - 并发性编程的原语。 94 | 95 | * [Folly](https://github.com/facebook/folly) - Facebook 的开源库(它对 MPMC 队列做了一个很好的实现)。 96 | 97 | * [Junction](https://github.com/preshing/junction) - C++ 中的并发数据结构。 98 | 99 | * [MPMCQueue](https://github.com/rigtorp/MPMCQueue) - 一个用 C++11 编写的有边界的“多生产者 - 多消费者”无锁队列。 100 | 101 | * [SPSCQueue](https://github.com/rigtorp/SPSCQueue) - 一个有边界的“单生产者 - 单消费者”的无等待、无锁的队列。 102 | 103 | * [Seqlock](https://github.com/rigtorp/Seqlock) - 用 C++ 实现的 Seqlock。 104 | 105 | * [Userspace RCU](http://liburcu.org/) - liburcu 是一个用户空间的 RCU(Read-copy-update,读 - 拷贝 - 更新)库。 106 | 107 | * [libcds](https://github.com/khizmax/libcds) - 一个并发数据结构的 C++ 库。 108 | 109 | * [liblfds](https://liblfds.org/) - 一个用 C 语言编写的可移植、无许可证、无锁的数据结构库。 110 | 111 | ## 其它 112 | 113 | * 关于 64 位系统编程,只要去一个地方就行了: [All about 64-bit programming in one place](https://software.intel.com/content/www/us/en/develop/blogs/all-about-64-bit-programming-in-one-place.html),这是一个关于 64 位编程相关的收集页面,其中包括相关的文章、28 节课程,还有知识库和相关的 blog。 114 | 115 | * [What Scalable Programs Need from Transactional Memory](https://dl.acm.org/doi/10.1145/3093336.3037750) ,事务性内存(TM)一直是许多研究的重点,它在诸如 IBM Blue Gene/Q 和 Intel Haswell 等处理器中得到了支持。许多研究都使用 STAMP 基准测试套件来评估其设计。然而,我们所知的所有 TM 系统上的 STAMP 基准测试所获得的加速比较有限。例如,在 IBM Blue Gene/Q 上有 64 个线程,我们观察到使用 Blue Gene/Q 硬件事务内存(HTM)的中值加速比为 1.4 倍,使用软件事务内存(STM)的中值加速比为 4.1 倍。什么限制了这些 TM 基准的性能?在本论文中,作者认为问题在于用于编写它们的编程模型和数据结构上,只要使用合适的模型和数据结构,程序的性能可以有 10 多倍的提升。 116 | 117 | * [Improving OpenSSL Performance](https://software.intel.com/content/www/us/en/develop/articles/improving-openssl-performance.html) ,这篇文章除了教你如何提高 OpenSSL 的执行性能,还讲了一些底层的性能调优知识。 118 | 119 | * 关于压缩的内容。为了避免枯燥,主要推荐下面这两篇实践性很强的文章。 120 | * [How eBay’s Shopping Cart used compression techniques to solve network I/O bottlenecks](https://tech.ebayinc.com/engineering/how-ebays-shopping-cart-used-compression-techniques-to-solve-network-io-bottlenecks/) ,这是一篇很好的文章,讲述了 eBay 是如何通过压缩数据来提高整体服务性能的,其中有几个比较好的压缩算法。除了可以让你学到相关的技术知识,还可以让你看到一种比较严谨的工程师文化。 121 | * [Linkedin: Boosting Site Speed Using Brotli Compression](https://engineering.linkedin.com/blog/2017/05/boosting-site-speed-using-brotli-compression) ,LinkedIn 在 2017 年早些时候开始使用 [Brotli](https://en.wikipedia.org/wiki/Brotli) 来替换 gzip,以此带来更快的访问,这篇文章讲述了什么是 Brotli 以及与其它压缩程序的比较和所带来的性能提升。 122 | 123 | * 这里有两篇关于 SSD 硬盘性能测试的文章。[Performance Testing with SSDs, Part 1](https://mailchimp.com/developer/) 和 [Performance Testing with SSDs Part 2](https://mailchimp.com/developer/) ,这两篇文章介绍了测试 SSD 硬盘性能以及相关的操作系统调优方法。 124 | 125 | * [Secure Programming HOWTO - Creating Secure Software](https://www.dwheeler.com/secure-programs/) ,这是一本电子书,其中有繁体中文的翻译,这本电子书讲了 Linux/Unix 下的一些安全编程方面的知识。 126 | 127 | ## 相关论文 128 | 129 | * [Hints for Computer System Design](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/acrobat-17.pdf) ,计算机设计的忠告,这是 ACM 图灵奖得主 [Butler Lampson](https://en.wikipedia.org/wiki/Butler_Lampson) 在 Xerox PARC 工作时的一篇论文。这篇论文简明扼要地总结了他在做系统设计时的一些想法,非常值得一读。(用他的话来说,“Studying the design and implementation of a number of computer has led to some general hints for system design. They are described here and illustrated by many examples, ranging from hardware such as the Alto and the Dorado to application programs such as Bravo and Star“。) 130 | 131 | * [The 5 minute rule for trading memory for disc accesses and the 5 byte rule for trading memory for CPU time](https://www.hpl.hp.com/techreports/tandem/TR-86.1.pdf) ,根据文章名称也可以看出,5 分钟法则是用来衡量内存与磁盘的,而 5 字节法则则是在内存和 CPU 之间的权衡。这两个法则是 Jim Gray 和 Franco Putzolu 在 1986 年的文章。在该论文发表 10 年后的 1997 年,Jim Gray 和 Goetz Graefe 又在 [The Five-Minute Rule Ten Years Later and Other Computer Storage Rules of Thumb](http://jimgray.azurewebsites.net/5_min_rule_sigmod.pdf) 中对该法则进行了重新审视。2007 年,也就是该论文发表 20 年后,这年的 1 月 28 日,Jim Gray 驾驶一艘 40 英尺长的船从旧金山港出海,目的是航行到附近的费拉隆岛,在那里撒下母亲的骨灰。出海之后,他就同朋友和亲属失去了联系。为了纪念和向大师致敬,时隔 10 多年后的 2009 年 Goetz Graefe 又发表了 [The Five-Minute Rule 20 Years Later (and How Falsh Memory Changes the Rules)](https://cacm.acm.org/magazines/2009/7/32091-the-five-minute-rule-20-years-later/fulltext)。 132 | 133 | 注明一下,Jim Gray 是关系型数据库领域的大师。因在数据库和事务处理研究和实现方面的开创性贡献而获得 1998 年图灵奖。美国科学院、工程院两院院士,ACM 和 IEEE 两会会士。他 25 岁成为加州大学伯克利分校计算机科学学院第一位博士。在 IBM 工作期间参与和主持了 IMS、System R、SQL/DS、DB2 等项目的开发。后任职于微软研究院,主要关注应用数据库技术来处理各学科的海量信息。 134 | 135 | ## 小结 136 | 137 | 好了,总结一下今天的内容。异步 I/O 模型是我个人觉得所有程序员都必需要学习的一门技术或是编程方法,这其中的设计模式或是解决方法可以借鉴到分布式架构上来。而且我认为,学习这些模型非常重要,你千万要认真学习。 138 | 139 | 接下来是 Lock-Free 方面的内容,由于锁对于性能的影响实在是太大了,所以它越来越被开发人员所重视。如果想开发出一个高性能的程序,你非常有必要学习 Lock-Free 的编程方式。随后,我给出系统底层方面的其它一些重要知识,如 64 位编程、提高 OpenSSL 的执行性能、压缩、SSD 硬盘性能测试等。最后介绍了几篇我认为对学习和巩固这些知识非常有帮助的论文,都很经典,推荐你务必看看。 -------------------------------------------------------------------------------- /11_Java底层知识/README.md: -------------------------------------------------------------------------------- 1 | # Java底层知识 2 | 3 | 前两篇文章分享的是系统底层方面的内容,今天我们进入高手成长篇的第二部分——Java 底层知识。 4 | 5 | ## Java 字节码相关 6 | 7 | 首先,Java 最黑科技的玩法就是字节码编程,也就是动态修改或是动态生成 Java 字节码。Java 的字节码相当于汇编,其中的一些细节你可以从下面的这几个教程中学习。 8 | 9 | * [Java Zone: Introduction to Java Bytecode](https://dzone.com/articles/introduction-to-java-bytecode) ,这篇文章图文并茂地向你讲述了 Java 字节码的一些细节,是一篇很不错的入门文章。 10 | 11 | * [IBM DeveloperWorks: Java bytecode](https://developer.ibm.com/technologies/web-development/) ,虽然这篇文章很老了,但是这篇文章是一篇非常好的讲 Java 字节码的文章。 12 | 13 | * [Java Bytecode and JVMTI Examples](https://github.com/jon-bell/bytecode-examples),这是一些使用 [JVM Tool Interface](https://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html) 操作字节码的比较实用的例子。包括方法调用统计、静态字节码修改、Heap Taggin 和 Heap Walking。 14 | 15 | 当然,一般来说,我们不使用 JVMTI 操作字节码,而是用一些更好用的库。这里有三个库可以帮你比较容易地做这个事。 16 | 17 | * [asmtools](https://wiki.openjdk.java.net/display/CodeTools/asmtools) - 用于生产环境的 Java .class 文件开发工具。 18 | 19 | * [Byte Buddy](https://bytebuddy.net/#/) - 代码生成库:运行时创建 Class 文件而不需要编译器帮助。 20 | 21 | * [Jitescript](https://github.com/qmx/jitescript) - 和 [BiteScript](https://github.com/headius/bitescript) 类似的字节码生成库。 22 | 23 | 就我而言,我更喜欢 Byte Buddy,它在 2015 年还获了 Oracle 的 “[Duke’s Choice](https://www.oracle.com/corporate/pressrelease/dukes-award-102815.html)”大奖,其中说 Byte Buddy 极大地发展了 Java 的技术。 24 | 25 | 使用字节码编程可以玩出很多高级玩法,最高级的还是在 Java 程序运行时进行字节码修改和代码注入。听起来是不是一些很黑客,也很黑科技的事?是的,这个方式使用 Java 这门静态语言在运行时可以进行各种动态的代码修改,而且可以进行无侵入的编程。 26 | 27 | 比如, 我们不需要在代码中埋点做统计或监控,可以使用这种技术把我们的监控代码直接以字节码的方式注入到别人的代码中,从而实现对实际程序运行情况进行统计和监控。如果你看过我的《编程范式游记》,你就知道这种技术的威力了,其可以很魔法地把业务逻辑和代码控制分离开来。 28 | 29 | 要做到这个事,你还需要学习一个叫 Java Agent 的技术。Java Agent 使用的是 “[Java Instrumentation API](https://stackoverflow.com/questions/11898566/tutorials-about-javaagents)”,其主要方法是实现一个叫 premain() 的方法(嗯,一个比 main() 函数还要超前执行的 main 函数),然后把你的代码编译成一个 jar 文件。 30 | 31 | 在 JVM 启动时,使用这样的命令行来引入你的 jar 文件:java -javaagent:yourAwesomeAgent.jar -jar App.jar。更为详细的文章你可以参看:“[Java Code Geeks: Java Agents](https://www.javacodegeeks.com/2015/09/java-agents.html)”,你还可以看一下这个示例项目:[jvm-monitoring-agent](https://github.com/toptal/jvm-monitoring-agent) 或是 [EntryPointKR/Agent.java](https://gist.github.com/EntryPointKR/152f089f6f3884047abcd19d39297c9e)。如果想用 ByteBuddy 来玩,你可以看看这篇文章 “[通过使用 Byte Buddy,便捷地创建 Java Agent](https://www.infoq.cn/article/Easily-Create-Java-Agents-with-ByteBuddy/)”。如果你想学习如何用 Java Agent 做监控,你可以看一下这个项目 [Stage Monitor](https://www.stagemonitor.org/)。 32 | 33 | ## JVM 相关 34 | 35 | 接下来讲讲 Java 底层知识中另一个非常重要的内容——JVM。 36 | 37 | 说起 JVM,你有必要读一下 JVM 的规格说明书,我在这里放一个 Java 8 的, [The Java Virtual Machine Specification Java SE 8 Edition](https://docs.oracle.com/javase/specs/jvms/se8/jvms8.pdf) 。对于规格说明书的阅读,我认为是系统了解 JVM 规范的最佳文档,这个文档可以让你对于搞不清楚或是诡异的问题恍然大悟。关于中文翻译,有人在 GitHub 上开了个 Repo - “[java-virtual-machine-specification](https://github.com/waylau/java-virtual-machine-specification)”。 38 | 39 | 另外,也推荐一下 [JVM Anatomy Park](https://shipilev.net/jvm/anatomy-quarks/) JVM 解剖公园,这是一个系列的文章,每篇文章都不长,但是都很精彩,带你一点一点地把 JVM 中的一些技术解开。 40 | 41 | 学习 Java 底层原理还有 Java 的内存模型,官方文章是 [JSR 133](https://www.jcp.org/en/jsr/detail?id=133)。还有马里兰大学的威廉·皮尤(William Pugh)教授收集的和 Java 内存模型相关的文献 - [The Java Memory Model](http://www.cs.umd.edu/~pugh/java/memoryModel/) ,你可以前往浏览。 42 | 43 | 对于内存方面,道格·利(Doug Lea)有两篇文章也是很有价值的。 44 | 45 | * [The JSR-133 Cookbook for Compiler Writers](http://gee.cs.oswego.edu/dl/jmm/cookbook.html),解释了怎样实现 Java 内存模型,特别是在考虑到多处理器(或多核)系统的情况下,多线程和读写屏障的实现。 46 | 47 | * [Using JDK 9 Memory Order Modes](http://gee.cs.oswego.edu/dl/html/j9mm.html),讲了怎样通过 VarHandle 来使用 plain、opaque、release/acquire 和 volatile 四种共享内存的访问模式,并剖析了底层的原理。 48 | 49 | 垃圾回收机制也是需要好好学习的,在这里推荐一本书 《[The Garbage Collection Handbook](https://book.douban.com/subject/6809987/)》,在豆瓣上的得分居然是 9.9(当然,评价人数不多)。这本书非常全面地介绍了垃圾收集的原理、设计和算法。但是这本书也是相当难啃的。中文翻译《[垃圾回收算法手册](https://book.douban.com/subject/26740958/)》翻译得很一般,有人说翻译得很烂。所以,如果可能,还是读英文版的。如果你对从事垃圾回收相关的工作有兴趣,那么你需要好好看一下这本书。 50 | 51 | 当然,更多的人可能只需要知道怎么调优垃圾回收, 那么推荐读读 [Garbage Collection Tuning Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/) ,它是 Hotspot Java 虚拟机的垃圾回收调优指南,对你很有帮助。 52 | 53 | [Quick Tips for Fast Code on the JVM](https://gist.github.com/djspiewak/464c11307cabc80171c90397d4ec34ef) 也是一篇很不错的文章,里面有写出更快的 Java 代码的几个小提示,值得一读。 54 | 55 | ## 小结 56 | 57 | 好了,总结一下今天学到的内容。Java 最黑科技的玩法就是字节码编程,也就是动态修改或是动态生成 Java 字节码。Java 的字节码相当于汇编,学习其中的细节很有意思,为此我精心挑选了 3 篇文章,供你学习。我们一般不使用 JVMTI 操作字节码,而是用一些更好用的库,如 asmtools、Byte Buddy 和 BiteScript 等。使用字节码编程可以玩出很多高级玩法,其中最高级的玩法是在 Java 程序运行时进行字节码修改和代码注入。同时,我介绍了 Java Agent 技术,帮助你更好地实现这种高级玩法。 58 | 59 | JVM 也是学习 Java 过程中非常重要的一部分内容。我推荐阅读一下 JVM 的规格说明书,我认为,它是系统了解 JVM 规范的最佳文档,可以让你对于搞不清楚或是诡异的问题恍然大悟。同时推荐了[JVM Anatomy Park](https://shipilev.net/jvm/anatomy-quarks/) 系列文章,也非常值得一读。 60 | 61 | 随后介绍的是 Java 的内存模型和垃圾回收机制,尤其给出了如何调优垃圾回收方面的资料。这些内容都很底层,但也都很重要。对于想成为高手的你来说,还是有必要花时间来啃一啃的。 62 | 63 | 下篇文章是数据库方面的内容,我们将探讨各种类型的数据库,非常有意思。敬请期待。 -------------------------------------------------------------------------------- /13_分布式架构入门/README.md: -------------------------------------------------------------------------------- 1 | # 分布式架构入门 2 | 3 | 学习分布式系统跟学习其它技术非常不一样,分布式系统涵盖的面非常广,具体来说涵盖如下几方面: 4 | 5 | * **服务调度**,涉及服务发现、配置管理、弹性伸缩、故障恢复等。 6 | * **资源调度**,涉及对底层资源的调度使用,如计算资源、网络资源和存储资源等。 7 | * **流量调度**,涉及路由、负载均衡、流控、熔断等。 8 | * **数据调度**,涉及数据复本、数据一致性、分布式事务、分库、分表等。 9 | * **容错处理**,涉及隔离、幂等、重试、业务补偿、异步、降级等。 10 | * **自动化运维**,涉及持续集成、持续部署、全栈监控、调用链跟踪等。 11 | 12 | 所有这些形成了分布式架构的整体复杂度,也造就了分布式系统中的很多很多论文、图书以及很多很多的项目。要学好分布式系统及其架构,我们需要大量的时间和实践才能真正掌握这些技术。 13 | 14 | 这里有几点需要你注意一下。 15 | 16 | * **分布式系统之所以复杂,就是因为它太容易出错了**。这意味着,**你要把处理错误的代码当成正常功能的代码来处理**。 17 | 18 | * **开发一个健壮的分布式系统的成本是单体系统的几百倍甚至几万倍**。这意味着,**我们要自己开发一个,需要能力很强的开发人员**。 19 | 20 | * **非常健壮的开源的分布式系统并不多,或者说基本没有**。这意味着,**如果你要用开源的,那么你需要 hold 得住其源码**。 21 | 22 | * **管理或是协调多个服务或机器是非常难的**。这意味着,**我们要去读很多很多的分布式系统的论文**。 23 | 24 | * **在分布式环境下,出了问题是很难 debug 的**。这意味着,**我们需要非常好的监控和跟踪系统,还需要经常做演练和测试**。 25 | 26 | * **在分布式环境下,你需要更科学地分析和统计**。这意味着,**我们要用 P90 这样的统计指标,而不是平均值,我们还需要做容量计划和评估**。 27 | 28 | * **在分布式环境下,需要应用服务化**。这意味着,**我们需要一个服务开发框架,比如 SOA 或微服务**。 29 | 30 | * **在分布式环境下,故障不可怕,可怕的是影响面过大,时间过长**。这意味着,**我们需要花时间来开发我们的自动化运维平台**。 31 | 32 | 总之,在分布式环境下,一切都变得非常复杂。要进入这个领域,你需要有足够多的耐性和足够强的心态来接受各式各样的失败。当拥有丰富的实践和经验后,你才会有所建树。这并不是一日之功,你可能要在这个领域花费数年甚至数十年的时间。 33 | 34 | ## 分布式架构入门 35 | 36 | 学习如何设计可扩展的架构将会有助于你成为一个更好的工程师。系统设计是一个很宽泛的话题。在互联网上,关于架构设计原则的资源也是多如牛毛。所以,你需要知道一些基本概念,对此,这里你先阅读下面两篇文章。 37 | 38 | * [Scalable Web Architecture and Distributed Systems](http://www.aosabook.org/en/distsys.html) ,这篇文章会给你一个大概的分布式架构是怎么来解决系统扩展性问题的粗略方法。 39 | 40 | * [Scalability, Availability & Stability Patterns](https://www.slideshare.net/jboner/scalability-availability-stability-patterns) ,这个 PPT 能在扩展性、可用性、稳定性等方面给你一个非常大的架构设计视野和思想,可以让你感受一下大概的全景图。 41 | 42 | 然后,我更强烈推荐 GitHub 上的一篇文档 - [System Design Primer](https://github.com/donnemartin/system-design-primer) ,这个仓库主要组织收集分布式系统的一些与扩展性相关的资源,它可以帮助你学习如何构建可扩展的架构。 43 | 44 | 目前这个仓库收集到了好些系统架构和设计的基本方法。其中包括:CAP 理论、一致性模型、可用性模式、DNS、CDN、负载均衡、反向代理、应用层的微服务和服务发现、关系型数据库和 NoSQL、缓存、异步通讯、安全等。 45 | 46 | 我认为,上面这几篇文章基本足够可以让你入门了,因为其中基本涵盖了所有与系统架构相关的技术。这些技术,足够这世上 90% 以上的公司用了,只有超级巨型的公司才有可能使用更高层次的技术。 47 | 48 | ## 分布式理论 49 | 50 | 下面,我们来学习一下分布式方面的理论知识。 51 | 52 | 首先,你需要看一下 [An introduction to distributed systems](https://github.com/aphyr/distsys-class)。 这只是某个教学课程的提纲,我觉得还是很不错的,几乎涵盖了分布式系统方面的所有知识点,而且辅以简洁并切中要害的说明文字,非常适合初学者提纲挈领地了解知识全貌,快速与现有知识结合,形成知识体系。这也是一个分布式系统的知识图谱,可以让你看到分布式系统的整体全貌。你可以根据这个知识图 Google 下去,然后你会学会所有的东西。 53 | 54 | 然后,你需要了解一下拜占庭将军问题([Byzantine Generals Problem](https://en.wikipedia.org/wiki/Byzantine_fault))。这个问题是莱斯利·兰波特(Leslie Lamport)于 1982 年提出用来解释一致性问题的一个虚构模型([论文地址](https://www.microsoft.com/en-us/research/uploads/prod/2016/12/The-Byzantine-Generals-Problem.pdf))。拜占庭是古代东罗马帝国的首都,由于地域宽广,守卫边境的多个将军(系统中的多个节点)需要通过信使来传递消息,达成某些一致的决定。但由于将军中可能存在叛徒(系统中节点出错),这些叛徒将努力向不同的将军发送不同的消息,试图会干扰一致性的达成。拜占庭问题即为在此情况下,如何让忠诚的将军们能达成行动的一致。 55 | 56 | 对于拜占庭问题来说,假如节点总数为 N,叛变将军数为 F,则当 N >= 3F + 1 时,问题才有解,即拜占庭容错(Byzantine Fault Tolerant,BFT)算法。拜占庭容错算法解决的是,网络通信可靠但节点可能故障情况下一致性该如何达成的问题。 57 | 58 | 最早由卡斯特罗(Castro)和利斯科夫(Liskov)在 1999 年提出的实用拜占庭容错(Practical Byzantine Fault Tolerant,PBFT)算法,是第一个得到广泛应用的 BFT 算法。只要系统中有 2/3 的节点是正常工作的,则可以保证一致性。PBFT 算法包括三个阶段来达成共识:预准备(Pre-Prepare)、准备(Prepare)和提交(Commit)。 59 | 60 | 这里有几篇和这个问题相关的文章,推荐阅读。 61 | 62 | * [Dr.Dobb’s - The Byzantine Generals Problem]( https://www.drdobbs.com/cpp/the-byzantine-generals-problem/206904396) 63 | 64 | * [The Byzantine Generals Problem](http://blog.jameslarisch.com/the-byzantine-generals-problem) 65 | 66 | * [Practicle Byzantine Fault Tolerance](http://pmg.csail.mit.edu/papers/osdi99.pdf) 67 | 68 | 拜占庭容错系统研究中有三个重要理论:CAP、FLP 和 DLS。 69 | 70 | * [CAP 定理](https://en.wikipedia.org/wiki/CAP_theorem),CAP 理论相信你应该听说过不下 N 次了。CAP 定理是分布式系统设计中最基础也是最为关键的理论。CAP 定理指出,分布式数据存储不可能同时满足以下三个条件:一致性(Consistency)、可用性(Availability)和 分区容忍(Partition tolerance)。 “在网络发生阻断(partition)时,你只能选择数据的一致性(consistency)或可用性(availability),无法两者兼得”。论点比较直观:如果网络因阻断而分隔为二,在其中一边我送出一笔交易:“将我的十元给 A”;在另一半我送出另一笔交易:“将我的十元给 B”。此时系统要不是,a)无可用性,即这两笔交易至少会有一笔交易不会被接受;要不就是,b)无一致性,一半看到的是 A 多了十元而另一半则看到 B 多了十元。要注意的是,CAP 理论和扩展性(scalability)是无关的,在分片(sharded)或非分片的系统皆适用。 71 | 72 | * [FLP impossibility](https://www.the-paper-trail.org/post/2008-08-13-a-brief-tour-of-flp-impossibility/),在异步环境中,如果节点间的网络延迟没有上限,只要有一个恶意的节点存在,就没有算法能在有限的时间内达成共识。但值得注意的是, “[Las Vegas” algorithms](https://en.wikipedia.org/wiki/Las_Vegas_algorithm)(这个算法又叫撞大运算法,其保证结果正确,只是在运算时所用资源上进行赌博,一个简单的例子是随机快速排序,它的 pivot 是随机选的,但排序结果永远一致)在每一轮皆有一定机率达成共识,随着时间增加,机率会越趋近于 1。而这也是许多成功的共识算法会采用的解决问题的办法。 73 | 74 | * 容错的上限,从[DLS 论文](http://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf) 中我们可以得到以下结论: 75 | * 在部分同步(partially synchronous)的网络环境中(即网络延迟有一定的上限,但我们无法事先知道上限是多少),协议可以容忍最多 1/3 的拜占庭故障(Byzantine fault)。 76 | * 在异步(asynchronous)的网络环境中,具有确定性质的协议无法容忍任何错误,但这篇论文并没有提及 [randomized algorithms](https://link.springer.com/chapter/10.1007%2F978-3-540-77444-0_7),在这种情况下可以容忍最多 1/3 的拜占庭故障。 77 | * 在同步(synchronous)网络环境中(即网络延迟有上限且上限是已知的),协议可以容忍 100% 的拜占庭故障,但当超过 1/2 的节点为恶意节点时,会有一些限制条件。要注意的是,我们考虑的是"具有认证特性的拜占庭模型(authenticated Byzantine)",而不是"一般的拜占庭模型";具有认证特性指的是将如今已经过大量研究且成本低廉的公私钥加密机制应用在我们的算法中。 78 | 79 | 当然,还有一个著名的“8 条荒谬的分布式假设([Fallacies of Distributed Computing](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing))”。 80 | 81 | 1. 网络是稳定的。 82 | 83 | 2. 网络传输的延迟是零。 84 | 85 | 3. 网络的带宽是无穷大。 86 | 87 | 4. 网络是安全的。 88 | 89 | 5. 网络的拓扑不会改变。 90 | 91 | 6. 只有一个系统管理员。 92 | 93 | 7. 传输数据的成本为零。 94 | 95 | 8. 整个网络是同构的。 96 | 97 | 阿尔农·罗特姆 - 盖尔 - 奥兹(Arnon Rotem-Gal-Oz)写了一篇长文 [Fallacies of Distributed Computing Explained](http://www.rgoarchitects.com/Files/fallacies.pdf) 来解释为什么这些观点是错误的。另外,[加勒思·威尔逊(Gareth Wilson)的文章](https://shimo.im/docs/gYpKDyQv6CXGgHTr/read) 则用日常生活中的例子,对这些点做了通俗的解释。为什么我们深刻地认识到这 8 个错误?是因为,这要我们清楚地认识到——在分布式系统中错误是不可能避免的,我们在分布式系统中,能做的不是避免错误,而是要把错误的处理当成功能写在代码中。 98 | 99 | 下面分享几篇一致性方面的论文。 100 | 101 | * 当然,关于经典的 CAP 理论,也存在一些误导的地方,这个问题在 2012 年有一篇论文 [CAP Twelve Years Later: How the Rules Have Changed](https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/) ([中译版](https://www.infoq.cn/article/cap-twelve-years-later-how-the-rules-have-changed/))中做了一些讨论,主要是说,在 CAP 中最大的问题就是分区,也就是 P,在 P 发生的情况下,非常难以保证 C 和 A。然而,这是强一致性的情况。其实,在很多时候,我们并不需要强一致性的系统,所以后来,人们争论关于数据一致性和可用性时,主要是集中在强一致性的 ACID 或最终一致性的 BASE。当时,BASE 还不怎么为世人所接受,主要是大家都觉得 ACID 是最完美的模型,大家很难接受不完美的 BASE。在 CAP 理论中,大家总是觉得需要“三选二”,也就是说,P 是必选项,那“三选二”的选择题不就变成数据一致性 (consistency)、服务可用性 (availability) 间的“二选一”?然而,现实却是,P 很少遇到,而 C 和 A 这两个事,工程实践中一致性有不同程度,可用性也有不同等级,在保证分区容错性的前提下,放宽约束后可以兼顾一致性和可用性,两者不是非此即彼。其实,在一个时间可能允许的范围内是可以取舍并交替选择的。 102 | 103 | * [Harvest, Yield, and Scalable Tolerant Systems](https://www.semanticscholar.org/paper/Harvest%2C-yield%2C-and-scalable-tolerant-systems-Fox-Brewer/9c9ceb29a358149e9617d103f5624f325bf08b1e?p2df) ,这篇论文是基于上面那篇“CAP 12 年后”的论文写的,它主要提出了 Harvest 和 Yield 概念,并把上面那篇论文中所讨论的东西讲得更为仔细了一些。 104 | 105 | * [Base: An Acid Alternative](https://queue.acm.org/detail.cfm?id=1394128) ([中译版](https://www.cnblogs.com/savorboard/p/base-an-acid-alternative.html)),本文是 eBay 的架构师在 2008 年发表给 ACM 的文章,是一篇解释 BASE 原则,或者说最终一致性的经典文章。文中讨论了 BASE 与 ACID 原则的基本差异, 以及如何设计大型网站以满足不断增长的可伸缩性需求,其中有如何对业务做调整和折中,以及一些具体的折中技术的介绍。一个比较经典的话是——“在对数据库进行分区后, 为了可用性(Availability)牺牲部分一致性(Consistency)可以显著地提升系统的可伸缩性 (Scalability)”。 106 | 107 | * [Eventually Consistent](https://www.allthingsdistributed.com/2008/12/eventually_consistent.html) ,这篇文章是 AWS 的 CTO 维尔纳·沃格尔(Werner Vogels)在 2008 年发布在 ACM Queue 上的一篇数据库方面的重要文章,阐述了 NoSQL 数据库的理论基石——最终一致性,对传统的关系型数据库(ACID,Transaction)做了较好的补充。 108 | 109 | ## 小结 110 | 111 | 好了,总结一下今天分享的内容。文章的开头,我给出了学习分布式架构需要注意的几个关键点,然后列出了入门学习的资源,基本涵盖了所有与系统架构相关的技术。随后讲述了拜占庭容错系统研究中有三个重要理论:CAP、FLP 和 DLS,以及 8 条荒谬的分布式假设,从理论和认知等角度让你更为清楚地理解分布式系统。最后分享了几篇一致性相关的论文,很实用很经典,推荐阅读。 112 | 113 | 下篇文章中,我将推荐一些分布式架构的经典图书和论文,并给出了导读文字,几乎涵盖了分布式系统架构方面的所有关键的理论知识。敬请期待。 -------------------------------------------------------------------------------- /14_分布式架构经典图书和论文/README.md: -------------------------------------------------------------------------------- 1 | # 分布式架构经典图书和论文 2 | 3 | ## 经典图书 4 | 5 | 首先,我推荐几本分布式架构方面的经典图书。 6 | 7 | * [Distributed Systems for fun and profit](http://book.mixu.net/distsys/single-page.html),这是一本免费的电子书。作者撰写此书的目的是希望以一种更易于理解的方式,讲述以亚马逊的 Dynamo、谷歌的 Bigtable 和 MapReduce 等为代表的分布式系统背后的核心思想。 8 | * [Designing Data Intensive Applications](https://book.douban.com/subject/27154352/),这本书是一本非常好的书,我们知道,在分布式的世界里,数据结点的扩展是一件非常麻烦的事。这本书深入浅出地用很多的工程案例讲解了如何让数据结点做扩展。作者马丁·科勒普曼(Martin Kleppmann)在分布式数据系统领域有着很深的功底,并在这本书中完整地梳理各类纷繁复杂设计背后的技术逻辑,不同架构之间的妥协与超越,很值得开发人员与架构设计者阅读。这本书深入到 B-Tree、SSTables、LSM 这类数据存储结构中,并且从外部的视角来审视这些数据结构对 NoSQL 和关系型数据库的影响。这本书可以让你很清楚地了解到真正世界的大数据架构中的数据分区、数据复制的一些坑,并提供了很好的解决方案。最赞的是,作者将各种各样技术的本质非常好地关联在一起,令你触类旁通。而且,这本书完全就是抽丝剥茧,循循善诱,从“提出问题”到“解决问题”、“解决方案”、“优化方案”和“对比不同的方案”,一点一点地把非常晦涩的技术和知识展开。本书的引用相当多,每章后面都有几百个 Reference,通过这些 Reference 你可以看到更为广阔、更为精彩的世界。 9 | * [Distributed Systems: Principles and Paradigms](http://barbie.uta.edu/~jli/Resources/MapReduce&Hadoop/Distributed%20Systems%20Principles%20and%20Paradigms.pdf) ,本书是由计算机科学家安德鲁·斯图尔特·塔能鲍姆(Andrew S. Tanenbaum)和其同事马丁·范·斯蒂恩(Martin van Steen)合力撰写的,是分布式系统方面的经典教材。语言简洁,内容通俗易懂,介绍了分布式系统的七大核心原理,并给出了大量的例子;系统讲述了分布式系统的概念和技术,包括通信、进程、命名、同步化、一致性和复制、容错以及安全等;讨论了分布式应用的开发方法(即范型)。但本书不是一本指导“如何做”的手册,仅适合系统性地学习基础知识,了解编写分布式系统的基本原则和逻辑。中文翻译版为《[分布式系统原理与范型](https://item.jd.com/10079452.html)》(第二版)。 10 | * [Scalable Web Architecture and Distributed Systems](http://www.aosabook.org/en/distsys.html),这是一本免费的在线小册子,其中文翻译版 [可扩展的 Web 架构和分布式系统](http://nettee.github.io/posts/2016/Scalable-Web-Architecture-and-Distributed-Systems/)。本书主要针对面向互联网(公网)的分布式系统,但其中的原理或许也可以应用于其他分布式系统的设计中。作者的观点是,通过了解大型网站的分布式架构原理,小型网站的构建也能从中受益。本书从大型互联网系统的常见特性,如高可用、高性能、高可靠、易管理等出发,引出了一个类似于 Flickr 的典型的大型图片网站的例子。 11 | * [Principles of Distributed Systems](https://disco.ethz.ch/courses/podc_allstars/lecture/podc.pdf) ,本书是苏黎世联邦理工学院的教材。它讲述了多种分布式系统中会用到的算法。虽然分布式系统的不同场景会用到不同算法,但并不表示这些算法都会被用到。不过,作为学生来说,掌握了算法设计的精髓也就能举一反三地设计出解决其他问题的算法,从而得到分布式系统架构设计中所需的算法。 12 | 13 | ## 经典论文 14 | 15 | ### 分布式事务 16 | 17 | 想了解分布式模型中最难的“分布式事务”,你需要看看 Google App Engine 联合创始人瑞恩·巴雷特(Ryan Barrett)在 2009 年的 Google I/O 大会上的演讲《[Transaction Across DataCenter](https://snarfed.org/transactions_across_datacenters_io.html)》([YouTube 视频](https://www.youtube.com/watch?v=srOgpXECblk))。 18 | 19 | 在这个演讲中,巴雷特讲述了各种经典的解决方案如何在一致性、事务、性能和错误上做平衡。而最后得到为什么分布式系统的事务只有 Paxos 算法是最好的。 20 | 21 | 下面这个图是这个算法中的结论。 22 | 23 | ![](/Users/likejun/ProgrammersLevelUp/14_分布式架构经典图书和论文/Transaction-Across-DataCenter.jpeg) 24 | 25 | 你也可以移步看一下我在 Coolshell 上写的这篇文章《[分布式系统的事务处理](https://coolshell.cn/articles/10910.html)》。 26 | 27 | ### Paxos 一致性算法 28 | 29 | Paxos 算法,是莱斯利·兰伯特(Lesile Lamport)于 1990 年提出来的一种基于消息传递且具有高度容错特性的一致性算法。但是这个算法太过于晦涩,所以一直以来都属于理论上的论文性质的东西。其真正进入工程圈,主要是来源于 Google 的 Chubby lock——一个分布式的锁服务,用在了 Bigtable 中。直到 Google 发布了下面这两篇论文,Paxos 才进入到工程界的视野中来。 30 | 31 | * [Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf) 32 | * [The Chubby lock service for loosely-coupled distributed systems](https://static.googleusercontent.com/media/research.google.com/en//archive/chubby-osdi06.pdf) 33 | 34 | Google 与 Bigtable 相齐名的还有另外两篇论文。 35 | 36 | * [The Google File System](https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf) 37 | * [MapReduce: Simplified Data Processing on Large Clusters](https://static.googleusercontent.com/media/research.google.com/en//archive/mapreduce-osdi04.pdf) 38 | 39 | 不过,这几篇文章中并没有讲太多的 Paxos 算法上的细节,反而是在[Paxos Made Live - An Engineering Perspective](https://static.googleusercontent.com/media/research.google.com/en//archive/paxos_made_live.pdf) 这篇论文中提到了很多工程实现的细节。这篇论文详细解释了 Google 实现 Paxos 时遇到的各种问题和解决方案,讲述了从理论到实际应用二者之间巨大的鸿沟。 40 | 41 | Paxos 算法的原版论文比较晦涩,也不易懂。这里推荐一篇比较容易读的—— [Neat Algorithms - Paxos](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/) 。这篇文章中还有一些小动画帮助你读懂。还有一篇可以帮你理解的文章是 [Paxos by Examples](https://angus.nyc/2012/paxos-by-example/)。 42 | 43 | ### Raft 一致性算法 44 | 45 | 因为 Paxos 算法太过于晦涩,而且在实际的实现上有太多的坑,并不太容易写对。所以,有人搞出了另外一个一致性的算法,叫 Raft。其原始论文是 [In search of an Understandable Consensus Algorithm (Extended Version)](https://raft.github.io/raft.pdf) ,寻找一种易于理解的 Raft 算法。这篇论文的译文在 InfoQ 上,题为《[Raft 一致性算法论文译文](https://www.infoq.cn/article/raft-paper)》,推荐你读一读。 46 | 47 | 这里推荐几个不错的 Raft 算法的动画演示。 48 | 49 | * [Raft - The Secret Lives of Data](http://thesecretlivesofdata.com/raft/) 50 | * [Raft Consensus Algorithm](https://raft.github.io/) 51 | * [Raft Distributed Consensus Algorithm Visualization](http://kanaka.github.io/raft.js/) 52 | 53 | ### Gossip 一致性算法 54 | 55 | 后面,业内又搞出来一些工程上的东西,比如 Amazon 的 DynamoDB,其论文[Dynamo: Amazon’s Highly Available Key Value Store](http://bnrg.eecs.berkeley.edu/~randy/Courses/CS294.F07/Dynamo.pdf) 的影响力非常大。这篇论文中讲述了 Amazon 的 DynamoDB 是如何满足系统的高可用、高扩展和高可靠的。其中展示了系统架构是如何做到数据分布以及数据一致性的。GFS 采用的是查表式的数据分布,而 DynamoDB 采用的是计算式的,也是一个改进版的通过虚拟结点减少增加结点带来数据迁移的一致性哈希。 56 | 57 | 这篇文章中有几个关键的概念,一个是 Vector Clock,另一个是 Gossip 协议。 58 | 59 | * [Time, Clocks and the Ordering of Events in a Distributed System](https://www.microsoft.com/en-us/research/publication/time-clocks-ordering-events-distributed-system/) ,这篇文章是莱斯利·兰伯特(Leslie Lamport)于 1978 年发表的,并在 2007 年被选入 SOSP 的名人堂,被誉为第一篇真正的“分布式系统”论文,该论文曾一度成为计算机科学史上被引用最多的文章。分布式系统中的时钟同步是一个非常难的问题,因为分布式系统中是使用消息进行通信的,若使用物理时钟来进行同步,一方面是不同的 process 的时钟有差异,另一方面是时间的计算也有一定的误差,这样若有两个时间相同的事件,则无法区分它们谁前谁后了。这篇文章主要解决分布式系统中的时钟同步问题。 60 | * [马萨诸塞大学课程 Distributed Operating System](http://lass.cs.umass.edu/~shenoy/courses/spring05/lectures.html) 中第 10 节 [Clock Synchronization](http://lass.cs.umass.edu/~shenoy/courses/spring05/lectures/Lec10.pdf),这篇讲义讲述了时钟同步的问题。 61 | * 关于 Vector Clock,你可以看一下 [Why Vector Clocks are Easy](https://riak.com/posts/technical/why-vector-clocks-are-easy/) 和 [Why Vector Clocks are Hard](https://riak.com/posts/technical/why-vector-clocks-are-hard/) 这两篇文章。 62 | 63 | 用来做数据同步的 Gossip 协议的原始论文是 [Efficient Reconciliation and Flow Control for Anti-Entropy Protocols](https://www.cs.cornell.edu/home/rvr/papers/flowgossip.pdf)。Gossip 算法也是 Cassandra 使用的数据复制协议。这个协议就像八卦和谣言传播一样,可以“一传十、十传百”传播开来。但是这个协议看似简单,细节上却非常麻烦。 64 | 65 | Gossip 协议也是 NoSQL 数据库 Cassandra 中使用到的数据协议,你可以上 YouTube 上看一下这个视频介绍: [Understanding Gossip (Cassandra Internals)](https://www.youtube.com/watch?v=FuP1Fvrv6ZQ)。 66 | 67 | 关于 Gossip 的一些图示化的东西,你可以看一下动画 [Gossip Visualization](https://rrmoelker.github.io/gossip-visualization/)。 68 | 69 | ### 分布式存储和数据库 70 | 71 | 除了前面的 Google 的 BigTable 和 Google File System 那两篇论文,还有 Amazon 的 DynamoDB 的论文,下面也有几篇也是要读一下的。 72 | 73 | * 一篇是 AWS Aurora 的论文 [Amazon Aurora: Design Considerations for High Throughput Cloud -Native Relation Databases](https://www.allthingsdistributed.com/files/p1041-verbitski.pdf)。 74 | * 另一篇是比较有代表的论文是 Google 的 [Spanner: Google’s Globally-Distributed Database](http://static.googleusercontent.com/media/research.google.com/zh-CN//archive/spanner-osdi2012.pdf)。 其 2017 年的新版论文:[Spanner, TrueTime & The CAP Theorem](https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/45855.pdf)。 75 | * [F1 - The Fault-Tolerant Distributed RDBMS Supporting Google’s Ad Business](http://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/38125.pdf) 。 76 | * [Cassandra: A Decentralized Structured Storage System](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.161.6751&rep=rep1&type=pdf) 。 77 | * [CRUSH: Controlled, Scalable, Decentralized Placement of Replicated Data](https://www.ssrc.ucsc.edu/Papers/weil-sc06.pdf), 这里提到的算法被应用在了 Ceph 分布式文件系统中,其架构可以读一下 [RADOS - A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters](https://ceph.com/wp-content/uploads/2016/08/weil-rados-pdsw07.pdf) 以及 [Ceph 的架构文档](https://docs.ceph.com/en/latest/docs/jewel/architecture/)。 78 | 79 | ### 分布式消息系统 80 | 81 | * 分布式消息系统,你一定要读一下 Kafka 的这篇论文 [Kafka: a Distributed Messaging System for Log Processing](http://notes.stephenholiday.com/Kafka.pdf)。 82 | 83 | * [Wormhole: Reliable Pub-Sub to Support Geo-replicated Internet Services](https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-sharma.pdf) ,Wormhole 是 Facebook 内部使用的一个 Pub-Sub 系统,目前还没有开源。它和 Kafka 之类的消息中间件很类似。但是它又不像其它的 Pub-Sub 系统,Wormhole 没有自己的存储来保存消息,它也不需要数据源在原有的更新路径上去插入一个操作来发送消息,是非侵入式的。其直接部署在数据源的机器上并直接扫描数据源的 transaction logs,这样还带来一个好处,Wormhole 本身不需要做任何地域复制(geo-replication)策略,只需要依赖于数据源的 geo-replication 策略即可。 84 | * [All Aboard the Databus! LinkedIn’s Scalable Consistent Change Data Capture Platform](https://engineering.linkedin.com/research/2012/all-aboard-the-databus-linkedlns-scalable-consistent-change-data-capture-platform) , 在 LinkedIn 投稿 SOCC 2012 的这篇论文中,指出支持对不同数据源的抽取,允许不同数据源抽取器的开发和接入,只需该抽取器遵循设计规范即可。该规范的一个重要方面就是每个数据变化都必须被一个单调递增的数字标注(SCN),用于同步。这其中的一些方法完全可以用做异地双活的系统架构中。(和这篇论文相关的几个链接如下:[PDF 论文](https://sites.google.com/site/sites/system/errors/WebspaceNotFound?path=/acm2012socc/s18-das.pdf) 、 [PPT 分享](https://www.slideshare.net/amywtang/databus-socc-v3)。) 85 | 86 | ### 日志和数据 87 | 88 | * [The Log: What every software engineer should know about real-time data’s unifying abstraction](https://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying) ,这篇文章好长,不过这是一篇非常好非常好的文章,这是每个工程师都应用知道的事,必看啊。你可以看中译版《[日志:每个软件工程师都应该知道的有关实时数据的统一概念](https://github.com/oldratlee/translations/blob/master/log-what-every-software-engineer-should-know-about-real-time-datas-unifying/README.md)》。 89 | 90 | * [The Log-Structured Merge-Tree (LSM-Tree)](https://www.cs.umb.edu/~poneil/lsmtree.pdf) ,N 多年前,谷歌发表了 Bigtable 的论文,论文中很多很酷的方面,其一就是它所使用的文件组织方式,这个方法更一般的名字叫 Log Structured-Merge Tree。LSM 是当前被用在许多产品的文件结构策略:HBase、Cassandra、LevelDB、SQLite,甚至在 MongoDB 3.0 中也带了一个可选的 LSM 引擎(Wired Tiger 实现的)。LSM 有趣的地方是它抛弃了大多数数据库所使用的传统文件组织方法。实际上,当你第一次看它时是违反直觉的。这篇论文可以让你明白这个技术。(如果读起来有些费解的话,你可以看看中文社区里的这几篇文章:[文章一](https://www.cnblogs.com/siegfang/archive/2013/01/12/lsm-tree.html)、[文章二](https://kernelmaker.github.io/lsm-tree)。) 91 | 92 | * [Immutability Changes Everything](http://cidrdb.org/cidr2015/Papers/CIDR15_Paper16.pdf) ,这篇论文是现任 Salesforce 软件架构师帕特·赫兰德(Pat Helland)在 CIDR 2015 大会上发表的([相关视频演讲](https://vimeo.com/52831373))。 93 | 94 | * [Tango: Distributed Data Structures over a Shared Log](https://www.microsoft.com/en-us/research/wp-content/uploads/2013/11/Tango.pdf)。这个论文非常经典,其中说明了不可变性(immutability)架构设计的优点。随着为海量数据集存储和计算而设计的以数据为中心的新型抽象技术的出现,分布式系统比以往任何时候都更容易构建。但是,对于元数据的存储和访问不存在类似的抽象。 95 | 96 | 为了填补这一空白,Tango 为开发人员提供了一个由共享日志支持的内存复制数据结构(例如地图或树)的抽象。Tango 对象易于构建和使用,通过共享日志上简单的追加和读取操作来复制状态,而不是复杂的分布式协议。在这个过程中,它们从共享日志中获得诸如线性化、持久性和高可用性等属性。Tango 还利用共享日志支持跨不同对象的快速事务处理,允许应用程序跨机器进行状态划分,并在不牺牲一致性的情况下扩展到底层日志的上限。 97 | 98 | ### 分布式监控和跟踪 99 | 100 | * Google 的分布式跟踪监控论文 - [Dapper, a Large-Scale Distributed Systems Tracing Infrastructure](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/36356.pdf), 其开源实现有三个 [Zipkin](https://zipkin.io/)、[Pinpoint](https://github.com/pinpoint-apm/pinpoint) 和 [HTrace](http://incubator.apache.org/projects/htrace.html)。我个人更喜欢 Zipkin。 101 | 102 | ### 数据分析 103 | 104 | * [The Unified Logging Infrastructure for Data Analytics at Twitter](http://vldb.org/pvldb/vol5/p1771_georgelee_vldb2012.pdf) ,Twitter 公司的一篇关于日志架构和数据分析的论文。 105 | * [Scaling Big Data Mining Infrastructure: The Twitter Experience](http://www.datascienceassn.org/sites/default/files/Scaling%20Big%20Data%20Mining%20Infrastructure%20-%20The%20Twitter%20Experience.pdf) ,讲 Twitter 公司的数据分析平台在数据量越来越大,架构越来越复杂,业务需求越来越多的情况下,数据分析从头到底是怎么做的。 106 | * [Dremel: Interactive Analysis of Web-Scale Datasets](http://static.googleusercontent.com/media/research.google.com/en/us/pubs/archive/36632.pdf),Google 公司的 Dremel,是一个针对临时查询提供服务的系统,它处理的是只读的多层数据。本篇文章介绍了它的架构与实现,以及它与 MapReduce 是如何互补的。 107 | * [Resident Distributed Datasets: a Fault-Tolerant Abstraction for In-Memory Cluster Computing](https://www.usenix.org/system/files/conference/nsdi12/nsdi12-final138.pdf),这篇论文提出了弹性分布式数据集(Resilient Distributed Dataset,RDD)的概念,它是一个分布式存储抽象,使得程序员可以在大型集群上以容错的方式执行内存计算;解释了其出现原因:解决之前计算框架在迭代算法和交互式数据挖掘工具两种应用场景下处理效率低下的问题,并指出将数据保存在内存中,可以将性能提高一个数量级;同时阐述了其实现原理及应用场景等多方面内容。很有趣儿,推荐阅读。 108 | 109 | ### 与编程相关的论文 110 | 111 | * [Distributed Programming Model](https://web.cs.ucdavis.edu/~pandey/Research/Papers/icdcs01.pdf) 112 | * [PSync: a partially synchronous language for fault-tolerant distributed algorithms](https://www.di.ens.fr/~cezarad/popl16.pdf) 113 | * [Programming Models for Distributed Computing](https://heather.miller.am/teaching/cs7680/) 114 | * [Logic and Lattices for Distributed Programming](https://dsf.berkeley.edu/papers/UCB-lattice-tr.pdf) 115 | 116 | ### 其它的分布式论文阅读列表 117 | 118 | 除了上面上的那些我觉得不错的论文,下面还有三个我觉得不错的分布式系统论文的阅读列表,你可以浏览一下。 119 | 120 | * [Services Engineering Reading List](https://github.com/mmcgrana/services-engineering) 121 | * [Readings in Distributed Systems](http://christophermeiklejohn.com/distributed/systems/2013/07/12/readings-in-distributed-systems.html) 122 | * [Google Research - Distributed Systems and Parallel Computing](https://research.google/pubs/) 123 | 124 | ### 小结 125 | 126 | 今天分享的内容是分布式架构方面的经典图书和论文,并给出了导读文字,几乎涵盖了分布式系统架构方面的所有关键的理论知识。这些内容非常重要,是学好分布式架构的基石,请一定要认真学习。 127 | 128 | 下篇文章中,我们将讲述分布式架构工程设计方面的内容,包括设计原则、设计模式以及工程实践等方面的内容。敬请期待。 -------------------------------------------------------------------------------- /14_分布式架构经典图书和论文/Transaction-Across-DataCenter.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/14_分布式架构经典图书和论文/Transaction-Across-DataCenter.jpeg -------------------------------------------------------------------------------- /16_微服务/README.md: -------------------------------------------------------------------------------- 1 | # 微服务 2 | 3 | 微服务是分布式系统中最近比较流行的架构模型,也是 SOA 架构的一个进化。微服务架构并不是银弹,所以,也不要寄希望于微服务架构能够解决所有的问题。微服务架构主要解决的是如何快速地开发和部署我们的服务,这对于一个能够适应快速开发和成长的公司是非常必要的。同时我也觉得,微服务中有很多很不错的想法和理念,所以学习微服务是每一个技术人员迈向卓越的架构师的必经之路。 4 | 5 | 首先,你需要看一下,Martin Fowler 的这篇关于微服务架构的文档 - [Microservice Architecture](https://martinfowler.com/articles/microservices.html) ([中译版](https://blog.csdn.net/wurenhai/article/details/37659335)),这篇文章说明了微服务的架构与传统架构的不同之处在于,微服务的每个服务与其数据库都是独立的,可以无依赖地进行部署。你也可以看看 Martin Fowler 老人家现身说法的[视频](https://www.youtube.com/watch?v=wgdBVIX9ifA)。 6 | 7 | 另外,你还可以简单地浏览一下,各家对微服务的理解。 8 | 9 | * [AWS 的理解 - What are Microservices?](https://aws.amazon.com/cn/microservices/) 10 | 11 | * [Microsoft 的理解 - Microservices architecture style](https://docs.microsoft.com/en-us/azure/architecture/guide/architecture-styles/microservices) 12 | 13 | * [Pivotal 的理解 - Microservices](https://tanzu.vmware.com/microservices) 14 | 15 | ## 微服务架构 16 | 17 | 接下来,你可以看一下 [IBM 红皮书:Microservices Best Practices for Java](https://www.redbooks.ibm.com/redbooks/pdfs/sg248357.pdf) ,这本书非常好,不但有通过把 Spring Boot 和 Dropwizard 来架建 Java 的微服务,而且还谈到了一些标准的架构模型,如服务注册、服务发现、API 网关、服务通讯、数据处理、应用安全、测试、部署、运维等,是相当不错的一本书。 18 | 19 | 当然,有一本书你也可以读一下—— [微服务设计](https://book.douban.com/subject/26772677/)。这本书全面介绍了微服务的建模、集成、测试、部署和监控,通过一个虚构的公司讲解了如何建立微服务架构。主要内容包括认识微服务在保证系统设计与组织目标统一上的重要性,学会把服务集成到已有系统中,采用递增手段拆分单块大型应用,通过持续集成部署微服务,等等。 20 | 21 | 与此相似的,也有其它的一系列文章,值得一读。 22 | 23 | 下面是 Nginx 上的一组微服务架构的系列文章。 24 | 25 | * [Introduction to Microservices](https://www.nginx.com/blog/introduction-to-microservices/) 26 | 27 | * [Building Microservices: Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) 28 | 29 | * [Building Microservices: Inter-Process Communication in a Microservices Architecture](https://www.nginx.com/blog/building-microservices-inter-process-communication/) 30 | 31 | * [Service Discovery in a Microservices Architecture](https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture/) 32 | 33 | * [Event-Driven Data Management for Microservices](https://www.nginx.com/blog/event-driven-data-management-microservices/) 34 | 35 | * [Choosing a Microservices Deployment Strategy](https://www.nginx.com/blog/deploying-microservices/) 36 | 37 | * [Refactoring a Monolith into Microservices](https://www.nginx.com/blog/refactoring-a-monolith-into-microservices/) 38 | 39 | 下面这是 [Auto0 Blog](https://auth0.com/blog/) 上一系列的微服务的介绍,有代码演示。 40 | 41 | * [An Introduction to Microservices, Part 1](https://auth0.com/blog/an-introduction-to-microservices-part-1/) 42 | 43 | * [API Gateway. An Introduction to Microservices, Part 2](https://auth0.com/blog/an-introduction-to-microservices-part-2-API-gateway/) 44 | 45 | * [An Introduction to Microservices, Part 3: The Service Registry](https://auth0.com/blog/an-introduction-to-microservices-part-3-the-service-registry/) 46 | 47 | * [Intro to Microservices, Part 4: Dependencies and Data Sharing](https://auth0.com/blog/introduction-to-microservices-part-4-dependencies/) 48 | 49 | * [API Gateway: the Microservices Superglue](https://auth0.com/blog/apigateway-microservices-superglue/) 50 | 51 | 还有 Dzone 的这个 Spring Boot 的教程。 52 | 53 | * [Microservices With Spring Boot - Part 1 - Getting Started](https://dzone.com/articles/microservices-with-spring-boot-part-1-getting-star) 54 | 55 | * [Microservices With Spring Boot - Part 2 - Creating a Forex Microservice](https://dzone.com/articles/microservices-with-spring-boot-part-2-creating-a-f) 56 | 57 | * [Microservices With Spring Boot - Part 3 - Creating Currency Conversion Microservice](https://dzone.com/articles/microservices-with-spring-boot-part-3-creating-cur) 58 | 59 | * [Microservices With Spring Boot - Part 4 - Using Ribbon for Load Balancing](https://dzone.com/articles/microservices-with-spring-boot-part-4-using-ribbon) 60 | 61 | * [Microservices With Spring Boot - Part 5 - Using Eureka Naming Server](https://dzone.com/articles/microservices-with-spring-boot-part-5-using-eureka) 62 | 63 | 当然,如果你要玩得时髦一些的话,我推荐你使用下面的这套架构。 64 | 65 | * 前端:[React.js](https://reactjs.org/) 或 [Vue.js](https://vuejs.org/)。 66 | 67 | * 后端:[Go 语言](https://golang.org/) + 微服务工具集 [Go kit](https://gokit.io/) ,因为是微服务了,所以,每个服务的代码就简单了。既然简单了,也就可以用任何语言了,所以,我推荐 Go 语言。 68 | 69 | * 通讯:[gRPC](https://grpc.io/),这是 Google 远程调用的一个框架,它比 Restful 的调用要快 20 倍到 50 倍的样子。 70 | 71 | * API:[Swagger](https://swagger.io/) ,Swagger 是一种 Restful API 的简单但强大的表示方式,标准的,语言无关,这种表示方式不但人可读,而且机器可读。可以作为 Restful API 的交互式文档,也可以作为 Restful API 形式化的接口描述,生成客户端和服务端的代码。今天,所有的 API 应该都通过 Swagger 来完成。 72 | 73 | * 网关:[Envoy](https://www.envoyproxy.io/) 其包含了服务发现、负载均衡和熔断等这些特性,也是一个很有潜力的网关。当然,Kubernetes 也是很好的,而且它也是高扩展的,所以,完全可以把 Envoy 通过 Ingress 集成进 Kubernetes。这里有一个开源项目就是干这个事的 - [contour](https://github.com/projectcontour/contour)。 74 | 75 | * 日志监控:[fluentd](https://www.fluentd.org/) + [ELK](https://www.elastic.co/cn/webinars/introduction-elk-stack) 。 76 | 77 | * 指标监控:[Prometheus](https://prometheus.io/) 。 78 | 79 | * 调用跟踪:[Jaeger](https://www.jaegertracing.io/docs/1.22/) 或是 [Zipkin](https://zipkin.io/),当然,后者比较传统一些,前者比较时髦,最重要的是,其可以和 Prometheus 和 Envory 集成。 80 | 81 | * 自动化运维:[Docker](https://www.docker.com/) + [Kubernetes](https://kubernetes.io/) 。 82 | 83 | ## 微服务和 SOA 84 | 85 | 在对微服务有了一定的认识以后,一定有很多同学分不清楚微服务和 SOA 架构,对此,你可以看一下这本电子书 - 《[Microservices vs. Service-Oriented Architecture](https://www.nginx.com/resources/library/microservices-vs-soa/)》。通过这本书,你可以学到,服务化架构的一些事实,还有基础的 SOA 和微服务的架构知识,以及两种架构的不同。这本书的作者马克·理查兹(Mark Richards)同学拥有十年以上的 SOA 和微服务架构的设计和实现的经验。 86 | 87 | 另外,还有几篇其它对比 SOA 和微服务的文章你也可以看看。 88 | 89 | * [DZone: Microservices vs. SOA](https://dzone.com/articles/microservices-vs-soa-2) 90 | 91 | * [DZone: Microservices vs. SOA - Is There Any Difference at All?](https://dzone.com/articles/microservices-vs-soa-is-there-any-difference-at-al) 92 | 93 | * [Microservices, SOA, and APIs: Friends or enemies?](https://www.ibm.com/cloud/learn/microservices) 94 | 95 | 除此之外,我们还需要知道微服务和其它架构的一些不同和比较,这样我们就可以了解微服务架构的优缺点。下面几篇文章将帮助获得这些知识。 96 | 97 | * [PaaS vs. IaaS for Microservices Architectures: Top 6 Differences](https://www.altoros.com/blog/paas-vs-iaas-for-microservices-architectures-top-6-differences/) 98 | 99 | * [Microservices vs. Monolithic Architectures: Pros, Cons, and How Cloud Foundry (PaaS) Can Help](https://www.slideshare.net/altoros/microservices-vs-monolithic-architectures-pros-and-cons) 100 | 101 | * [Microservices - Not A Free Lunch!](http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html) 102 | 103 | * [The Hidden Costs Of Microservices](https://www.stackbuilders.com/news/the-hidden-costs-of-microservices) 104 | 105 | ## 设计模式和最佳实践 106 | 107 | 然后,你可以看一下微服务的一些设计模式。 108 | 109 | * [Microservice Patterns](https://microservices.io/),微服务架构的设计模式和最佳实践。 110 | 111 | * [Microservice Antipatterns and Pitfalls](https://www.oreilly.com/content/microservices-antipatterns-and-pitfalls/),微服务架构的一些已知的反模式和陷阱。 112 | 113 | * [Microservice Architecture: All The Best Practices You Need To Know](https://codingsans.com/blog/microservice-architecture-best-practices),这是一篇长文,里面讲述了什么是微服务、微服务架构的优缺点、微服务最大的挑战和解决方案是什么、如何避免出错,以及构建微服务架构的最佳实践等多方面的内容。推荐阅读。 114 | 115 | * [Best Practices for Building a Microservice Architecture](https://www.vinaysahni.com/best-practices-for-building-a-microservice-architecture) ,这篇文章分享了构建微服务架构的最佳实践。 116 | 117 | * [Simplicity by Distributing Complexity](https://engineering.zalando.com/posts/2018/01/simplicity-by-distributing-complexity.html),这是一篇讲如何使用事件驱动构建微服务架构的文章,其中有很多不错的设计上的基本原则。 118 | 119 | ## 相关资源 120 | 121 | * [Microservices Resource Guide](https://martinfowler.com/microservices/) ,这个网页上是 Martin Fowler 为我们挑选的和微服务相关的文章、视频、书或是 podcast。 122 | 123 | * [Awesome Microservices](https://github.com/mfornos/awesome-microservices/) ,一个各种微服务资源和相关项目的集中地。 124 | 125 | ## 小结 126 | 127 | 好了,总结一下今天的内容。我认为,微服务中有很多很不错的想法和理念,所以学习微服务是每一个技术人员迈向卓越的架构师的必经之路。在这篇文章中,我先给出了 AWS、Microsoft 和 Pivotal 对微服务的理解;然后给出了好几个系列的教程,帮你全面学习和理解微服务架构;然后通过一系列文章帮你来区分何为微服务,何为 SOA;最后给出了微服务架构的设计模式和最佳实践,以及相关资源。相信通过这一系列内容的学习,你一定会对微服务有全面、透彻的理解。 128 | 129 | 下篇文章,我们将讲述的容器化和自动化运维方面的内容。敬请期待。 -------------------------------------------------------------------------------- /17_容器化和自动化运维/README.md: -------------------------------------------------------------------------------- 1 | # 容器化和自动化运维 2 | 3 | 这篇文章我们来重点学习 Docker 和 Kubernetes,它们已经是分布式架构和自动化运维的必备工具了。对于这两个东西,你千万不要害怕,因为技术方面都不算复杂,只是它们的玩法和传统运维不一样,所以你不用担心,只要你花上一点时间,一定可以学好的。 4 | 5 | ## Docker 6 | 7 | * 你可以先看一下 Docker 的官方介绍 [Docker Overview](https://docs.docker.com/get-started/overview/) 。 8 | 9 | * 然后再去一个 Web 在线的 Playground 上体验一下, [Katacoda Docker Playground](https://www.katacoda.com/courses/docker/playground) 或者是 [Play With Docker](https://training.play-with-docker.com/) 。 10 | 11 | * 接下来,跟着 [Learn Docker](https://github.com/dwyl/learn-docker) 这个文档中的教程自己安装一个 Docker 的环境,实操一把。 12 | 13 | * 然后跟着 [Docker Curriculum](https://docker-curriculum.com/) 这个超详细的教程玩一下 Docker。 14 | 15 | 有了上述的一些感性体会之后,你就可以阅读 Docker 官方文档 [Docker Documentation](https://docs.docker.com/) 了,这是学习 Docker 最好的方式。 16 | 17 | 如果你想了解一下 Docker 的底层技术细节,你可以参看我的文章。 18 | 19 | * [Docker 基础技术:Linux Namespace(上)](https://coolshell.cn/articles/17010.html) 20 | 21 | * [Docker 基础技术:Linux Namespace(下)](https://coolshell.cn/articles/17029.html) 22 | 23 | * [Docker 基础技术:Cgroup](https://coolshell.cn/articles/17049.html) 24 | 25 | * [Docker 基础技术:AUFS](https://coolshell.cn/articles/17061.html) 26 | 27 | * [Docker 基础技术:DeviceMapper](https://coolshell.cn/articles/17200.html) 28 | 29 | 还有一些不错的与 Docker 网络有关的文章你需要阅读及实践一下。 30 | 31 | * [A container networking overview](https://jvns.ca/blog/2016/12/22/container-networking/) 32 | 33 | * [Docker networking 101 - User defined networks](http://www.dasblinkenlichten.com/docker-networking-101-user-defined-networks/) 34 | 35 | * [Understanding CNI (Container Networking Interface)](http://www.dasblinkenlichten.com/understanding-cni-container-networking-interface/) 36 | 37 | * [Using CNI with Docker](http://www.dasblinkenlichten.com/using-cni-docker/) 38 | 39 | Docker 有下面几种网络解决方案:[Calico](https://www.projectcalico.org/docker-libnetwork-is-almost-here-and-calico-is-ready/) 、[Flannel](https://github.com/flannel-io/flannel) 和 [Weave](https://github.com/weaveworks/weave) ,你需要学习一下。另外,还需要学习一下 [netshoot](https://github.com/nicolaka/netshoot) ,这是一个很不错的用来诊断 Docker 网络问题的工具集。 40 | 41 | 关于这几个容器网络解决方案的性能对比,你可以看一下下面这几篇文章或报告。 42 | 43 | * [Battlefield: Calico, Flannel, Weave and Docker Overlay Network](http://chunqi.li/2015/11/15/Battlefield-Calico-Flannel-Weave-and-Docker-Overlay-Network/) 44 | * [Comparison of Networking Solutions for Kubernetes](http://machinezone.github.io/research/networking-solutions-for-kubernetes/) 45 | * [Docker Overlay Networks: Performance analysis in high-latency enviroments](https://www.delaat.net/rp/2015-2016/p50/report.pdf) 46 | 47 | 如果你对 Docker 的性能有什么问题的话,你可以看一下下面这些文章。 48 | 49 | * [IBM Research Report: An Updated Performance Comparison of Virtual Machines and Linux Containers](https://dominoweb.draco.res.ibm.com/reports/rc25482.pdf) 50 | * [An Introduction to Docker and Analysis of its Performance](http://paper.ijcsns.org/07_book/201703/20170327.pdf) 51 | 52 | 下面是一些和存储相关的文章。 53 | 54 | * [Storage Concepts in Docker: Network and Cloud Storage](http://cloud-mechanic.blogspot.com/2014/10/storage-concepts-in-docker-network-and.html) 55 | * [Storage Concepts in Docker: Persistent Storage](http://cloud-mechanic.blogspot.com/2014/10/storage-concepts-in-docker-persistent.html) 56 | * [Storage Concepts in Docker: Shared Storage and the VOLUME directive](http://cloud-mechanic.blogspot.com/2014/10/storage-concepts-in-docker.html) 57 | 58 | 然后是跟运维相关的文章。 59 | 60 | * [Docker Monitoring with the ELK Stack: A Step-by-Step Guide](https://logz.io/learn/docker-monitoring-elk-stack/) 61 | 62 | 最后,推荐看看 [Valuable Docker Links](http://www.nkode.io/2014/08/24/valuable-docker-links.html) ,其中收集并罗列了一系列非常不错的 Docker 文章。 63 | 64 | ### 最佳实践 65 | 66 | 下面分享一些与 Docker 相关的最佳实践。 67 | 68 | * [Best Practices for Dockerfile](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) ,Docker 官方文档里的 Dockerfile 的最佳实践。 69 | 70 | * [Docker Best Practices](https://github.com/FuriKuri/docker-best-practices) ,这里收集汇总了存在于各个地方的使用 Docker 的建议和实践。 71 | 72 | * [Container Best Practices](http://docs.projectatomic.io/container-best-practices/) ,来自 Atomic 项目,是一个介绍容器化应用程序的架构、创建和管理的协作型文档项目。 73 | 74 | * [Eight Docker Development Patterns](http://hokstad.com/docker/patterns) ,八个 Docker 的开发模式:共享基础容器、共享同一个卷的多个开发容器、开发工具专用容器、测试环境容器、编译构建容器、防手误的安装容器、默认服务容器、胶黏容器(如英文链接不能访问,可阅读[中文版本](https://www.infoq.cn/article/2014/10/seven-docker-develop-pattern))。 75 | 76 | ## Kubernetes 77 | 78 | Kubernetes 是 Google 开源的容器集群管理系统,是 Google 多年大规模容器管理技术 Borg 的开源版本,也是 CNCF 最重要的项目之一,主要功能包括: 79 | 80 | * 基于容器的应用部署、维护和滚动升级; 81 | 82 | * 负载均衡和服务发现; 83 | 84 | * 跨机器和跨地区的集群调度; 85 | 86 | * 自动伸缩; 87 | 88 | * 无状态服务和有状态服务; 89 | 90 | * 广泛的 Volume 支持; 91 | 92 | * 插件机制保证扩展性。 93 | 94 | Kubernetes 发展非常迅速,已经成为容器编排领域的领导者。 95 | 96 | 首先,我推荐你阅读 Kubernetes 前世今生的一篇论文。 97 | 98 | * [Borg, Omega, and Kubernetes](https://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/44843.pdf) ,看看 Google 这十几年来从这三个容器管理系统中得到的经验教训。 99 | 100 | 学习 Kubernetes,有两个免费的开源电子书。 101 | 102 | * 《[Kubernetes Handbook](https://jimmysong.io/kubernetes-handbook/)》,这本书记录了作者从零开始学习和使用 Kubernetes 的心路历程,着重于经验分享和总结,同时也会有相关的概念解析。希望能够帮助你少踩坑,少走弯路,还会指引你关注 kubernetes 生态周边,如微服务构建、DevOps、大数据应用、Service Mesh、Cloud Native 等领域。 103 | * 《[Kubernetes 指南](https://kubernetes.feisky.xyz/zh/)》,这本书旨在整理平时在开发和使用 Kubernetes 时的参考指南和实践总结,形成一个系统化的参考指南以方便查阅。 104 | 105 | 这两本电子书都不错,前者更像是一本学习教程,而且面明显广一些,还包括 Cloud Natvie、Service Mesh 以及微服务相关的东西。而后者聚焦于 Kubernetes 本身,更像一本参考书。 106 | 107 | **另外,我这两天也读完了《Kubernetes in Action》一书,感觉写的非常好,一本很完美的教科书,抽丝剥茧,图文并茂。如果你只想读一本有关 Kubernetes 的书来学习 Kubernetes,那么我推荐你就选这本。** 108 | 109 | 但是也别忘了 Kubernetes 的官方网站:[Kubernetes.io](https://kubernetes.io/),上面不但有[全面的文档](https://kubernetes.io/docs/home/) ,也包括一个很不错的 [官方教程](https://kubernetes.io/docs/tutorials/kubernetes-basics/) 。 110 | 111 | 此外,还有一些交互式教程,帮助你理解掌握,以及一些很不错的文章推荐你阅读。 112 | 113 | ### 一些交互式教程 114 | 115 | * [Katacoda](https://www.katacoda.com/courses/kubernetes) 116 | 117 | * [Kubernetes Bootcamp](https://kubernetesbootcamp.github.io/kubernetes-bootcamp/) 118 | 119 | ### 一些文章 120 | 121 | 这里还有一些不错的文档,你应该去读一下。 122 | 123 | * [Kubernetes tips & tricks](https://opsnotice.xyz/kubernetes-tips-tricks/) 124 | 125 | * [Achieving CI/CD with Kubernetes](http://theremotelab.com/blog/achieving-ci-cd-with-k8s/) 126 | 127 | * [How to Set Up Scalable Jenkins on Top of a Kubernetes Cluster](https://dzone.com/articles/how-to-setup-scalable-jenkins-on-top-of-a-kubernet) 128 | 129 | * 10 Most Common Reasons Kubernetes Deployments Fail [Part I](https://kukulinski.com/10-most-common-reasons-kubernetes-deployments-fail-part-1/) 和 [Part II](https://kukulinski.com/10-most-common-reasons-kubernetes-deployments-fail-part-2/) 130 | 131 | * [How to Monitor Kubernetes](https://sysdig.com/blog/monitoring-kubernetes/) ,一共有 4 个篇章 132 | 133 | * [Logging in Kubernetes with Fluentd and Elasticsearch](http://www.dasblinkenlichten.com/logging-in-kubernetes-with-fluentd-and-elasticsearch/) 134 | 135 | * [Kubernetes Monitoring: Best Practices, Methods, and Existing Solutions](https://dzone.com/articles/kubernetes-monitoring-best-practices-methods-and-e) 136 | 137 | ### 网络相关的文章 138 | 139 | 要学习 Kubernetes,你只需要读一下,下面这个 Kubernetes 101 系列的文章。 140 | 141 | * [Kubernetes 101 - Networking](http://www.dasblinkenlichten.com/kubernetes-101-networking/) 142 | 143 | * [Kubernetes networking 101 - Pods](http://www.dasblinkenlichten.com/kubernetes-networking-101-pods/) 144 | 145 | * [Kubernetes networking 101 - Services](http://www.dasblinkenlichten.com/kubernetes-networking-101-services/) 146 | 147 | * [Kubernetes networking 101 - (Basic) External access into the cluster](http://www.dasblinkenlichten.com/kubernetes-networking-101-basic-external-access-into-the-cluster/) 148 | 149 | * [Kubernetes Networking 101 - Ingress resources](http://www.dasblinkenlichten.com/kubernetes-networking-101-ingress-resources/) 150 | 151 | * [Getting started with Calico on Kubernetes](http://www.dasblinkenlichten.com/getting-started-with-calico-on-kubernetes/) 152 | 153 | ### CI/CD 相关的文章 154 | 155 | * [Automated Image Builds with Jenkins, Packer, and Kubernetes](https://cloud.google.com/architecture/automated-build-images-with-jenkins-kubernetes#kubernetes_architecture) 156 | 157 | * [Jenkins setups for Kubernetes and Docker Workflow](http://iocanel.com/2015/09/jenkins-setups-for-kubernetes-and-docker-workflow/) 158 | 159 | * [Lab: Build a Continuous Deployment Pipeline with Jenkins and Kubernetes](https://github.com/GoogleCloudPlatform/continuous-deployment-on-kubernetes) 160 | 161 | ### 最佳实践 162 | 163 | * [Kubernetes Best Practices](https://medium.com/@sachin34268/kubernetes-best-practices-9b1435a4cb53) by [Sachin Arote](https://medium.com/@sachin.arote1) ,AWS 工程师总结的最佳实践。 164 | 165 | * [Kubernetes Best Practices](https://speakerdeck.com/thesandlord/kubernetes-best-practices) by [Sandeep Dinesh](https://github.com/thesandlord) ,Google 云平台工程师总结的最佳实践。 166 | 167 | ### Docker 和 Kubernetes 资源汇总 168 | 169 | 下面是 GitHub 上和 Docker & Kubernetes 相关的 Awesome 系列。 170 | 171 | * [Awesome Docker](https://github.com/veggiemonk/awesome-docker)。 172 | 173 | * [Awesome Kubernetes](https://github.com/ramitsurana/awesome-kubernetes)。 174 | 175 | 虽然上面的这些系列非常全的罗列了很多资源,但是我觉得很不系统。对于系统的说明 Docker 和 Kubernetes 生态圈,我非常推荐大家看一下 [The New Stack](https://thenewstack.io/ebooks) 为 Kubernetes 出的一系列的电子书或报告。 176 | 177 | * The New Stack eBook Series ,非常完整和详实的 Docker 和 Kubernetes 生态圈的所有东西。 178 | * [Book 01: The Docker Container Ecosystem](https://thenewstack.io/ebooks/docker-and-containers/the-docker-container-ecosystem/) 179 | * [Book 02: Applications & Microservices with Docker & Containers](https://thenewstack.io/ebooks/docker-and-containers/applications-microservices-docker-containers/) 180 | * [Book 03: Automation & Orchestration with Docker & Containers](https://thenewstack.io/ebooks/docker-and-containers/automation-orchestration-docker-containers/) 181 | * [Book 04: Network, Security & Storage with Docker & Containers](https://thenewstack.io/ebooks/docker-and-containers/networking-security-storage-docker-containers/) 182 | * [Book 05: Monitoring & Management with Docker & Containers](https://thenewstack.io/ebooks/docker-and-containers/monitoring-management-docker-containers/) 183 | * [Book 06: Use Cases for Kubernetes](https://thenewstack.io/ebooks/use-cases/use-cases-for-kubernetes/) 184 | * [Book 07: State of the Kubernetes Ecosystem](https://thenewstack.io/ebooks/kubernetes/state-of-kubernetes-ecosystem-second-edition-2020) 185 | * [Book 08: Kubernetes Deployment & Security Patterns](https://thenewstack.io/ebooks/kubernetes/kubernetes-deployment-and-security-patterns/) 186 | * [Book 09: CI/CD with Kubernetes](https://thenewstack.io/ebooks/kubernetes/ci-cd-with-kubernetes/) 187 | * [Book 10: Kubernetes solutions Directory](https://thenewstack.io/ebooks/kubernetes/kubernetes-solutions-directory/) 188 | * [Book 11: Guid to Cloud-Native Microservices](https://thenewstack.io/ebooks/microservices/cloud-native-microservices-2018/) 189 | 190 | ## 小结 191 | 192 | 总结一下今天的内容。Docker 和 Kubernetes 已经成为分布式架构和自动化运维方面的不可或缺的两大基本构成,是你必需要学习的。虽然它们的玩法跟传统运维不一样,但技术方面并不算复杂,只要你花上一点时间,一定会学好的。 193 | 194 | 在这篇文章中,我推荐了 Docker 和 Kubernetes 基础技术方面的学习资料,并给出了存储、运维、网络、CI/CD 等多方面的资料,同时列出了与之相关的最佳实践。相信认真学习和消化这些知识,你一定可以掌握 Docker 和 Kubernetes 两大利器。 195 | 196 | 下篇文章,我们将学习机器学习和人工智能方面的内容。敬请期待。 -------------------------------------------------------------------------------- /20_前端性能优化和框架/README.md: -------------------------------------------------------------------------------- 1 | # 前端性能优化和框架 2 | 3 | ## 前端性能优化 4 | 5 | 首先是推荐几本前端性能优化方面的图书。 6 | 7 | * [Web Performance in Action](https://www.allitebooks.in/web-performance-action/) ,这本书目前国内没有卖的。你可以看电子版本,我觉得是一本很不错的书,其中有 CSS、图片、字体、JavaScript 性能调优等。 8 | 9 | * [Designing for Performance](https://designingforperformance.com/) ,这本在线的电子书很不错,其中讲了很多网页优化的技术和相关的工具,可以让你对整体网页性能优化有所了解。 10 | 11 | * [High Performance JavaScript](https://book.douban.com/subject/5362856/) ,这本书在国内可以买到,能让你了解如何提升各方面的性能,包括代码的加载、运行、DOM 交互、页面生存周期等。雅虎的前端工程师尼古拉斯·扎卡斯(Nicholas C. Zakas)和其他五位 JavaScript 专家介绍了页面代码加载的最佳方法和编程技巧,来帮助你编写更为高效和快速的代码。你还会了解到构建和部署文件到生产环境的最佳实践,以及有助于定位线上问题的工具。 12 | 13 | * [High Performance Web Sites: Essential Knowledge for Front-End Engineers](https://book.douban.com/subject/26411563/) ,这本书国内也有卖,翻译版为《高性能网站建设指南:前端工程师技能精髓》。作者给出了 14 条具体的优化原则,每一条原则都配以范例佐证,并提供了在线支持。全书内容丰富,主要包括减少 HTTP 请求、Edge Computing 技术、Expires Header 技术、gzip 组件、CSS 和 JavaScript 最佳实践、主页内联、Domain 最小化、JavaScript 优化、避免重定向的技巧、删除重复 JavaScript 的技巧、关闭 ETags 的技巧、Ajax 缓存技术和最小化技术等。 14 | 15 | * 除了上面这几本书之外,Google 的 [Web Fundamentals](https://developers.google.com/web/fundamentals/) 里的 [Performance](https://web.dev/why-speed-matters/) 这一章节也有很多非常不错的知识和经验。 16 | 17 | 接下来是一些最佳实践性的文档。 18 | 19 | * [Browser Diet](https://browserdiet.com/zh/) ,前端权威性能指南(中文版)。这是一群为大型站点工作的专家们建立的一份前端性能的工作指南。 20 | 21 | * [PageSpeed Insights Rules](https://developers.google.com/speed/docs/insights/rules) ,谷歌给的一份性能指南和最佳实践。 22 | 23 | * [Best Practices for Speeding Up Your Web Site](https://developer.yahoo.com/performance/rules.html?guccounter=1&guce_referrer=aHR0cHM6Ly90aW1lLmdlZWtiYW5nLm9yZy9jb2x1bW4vYXJ0aWNsZS8xMjM4OQ&guce_referrer_sig=AQAAANo5g9PNH-vFi3vdX-7eAo6w7gJIVOj82S6Id4GT-RwjEgTOPjW1oUdHrje6HiKPYy9LTpVSob-JpF0bIei83QC1GGTyJHzxB8a-r_z5lRow30NjHh4_iF3T-3mQxFjUqNYHuNO71k_qA5mXop4On6Cet8_zLpCkaS7qciVeHJJW) ,雅虎公司给的一份 7 个分类共 35 个最佳实践的文档。 24 | 25 | 接下来,重点推荐一个性能优化的案例学习网站 [WPO Stats](https://wpostats.com/) 。WPO 是 Web Performance Optimization 的缩写,这个网站上有很多很不错的性能优化的案例分享,一定可以帮助你很多。 26 | 27 | 然后是一些文章和案例。 28 | 29 | * [A Simple Performance Comparison of HTTPS, SPDY and HTTP/2](http://blog.httpwatch.com/2015/01/16/a-simple-performance-comparison-of-https-spdy-and-http2/) ,这是一篇比较浏览器的 HTTPS、SPDY 和 HTTP/2 性能的文章,除了比较之外,还可以让你了解一些技术细节。 30 | 31 | * [7 Tips for Faster HTTP/2 Performance](https://www.nginx.com/blog/7-tips-for-faster-http2-performance/) ,对于 HTTP/2 来说,Nginx 公司给出的 7 个增加其性能的小提示。 32 | 33 | * [Reducing Slack’s memory footprint](https://slack.engineering/reducing-slacks-memory-footprint/) ,Slack 团队减少内存使用量的实践。 34 | 35 | * [Pinterest: Driving user growth with performance improvements](https://medium.com/pinterest-engineering/driving-user-growth-with-performance-improvements-cfc50dafadd7) ,Pinterest 关于性能调优的一些分享,其中包括了前后端的一些性能调优实践。其实也是一些比较通用的玩法,这篇文章主要是想让前端的同学了解一下如何做整体的性能调优。 36 | 37 | * [10 JavaScript Performance Boosting Tips](http://jonraasch.com/blog/10-javascript-performance-boosting-tips-from-nicholas-zakas) ,10 个提高 JavaScript 运行效率的小提示,挺有用的。 38 | 39 | * [17 Statistics to Sell Web Performance Optimization](https://www.guypo.com/17-statistics-to-sell-web-performance-optimization/) ,这个网页上收集了好些公司的 Web 性能优化的工程分享,都是非常有价值的。 40 | 41 | * [Getting started with the Picture Element](http://deanhume.com/getting-started-with-the-picture-element/) ,这篇文章讲述了 Responsive 布局所带来的一些负面的问题。主要是图像适配的问题,其中引出了一篇文章"[Native Responsive Images](https://dev.opera.com/articles/native-responsive-images/)" ,值得一读。 42 | 43 | * [Improve Page Load Times With DNS Prefetching](http://www.deanhume.com/improve-page-load-times-with-dns-prefetching/) ,这篇文章教了你一个如何降低 DNS 解析时间的小技术——DNS prefetching。 44 | 45 | * [Jank Busting for Better Rendering Performance](https://www.html5rocks.com/en/tutorials/speed/rendering/) ,这是一篇 Google I/O 上的分享,关于前端动画渲染性能提升。 46 | 47 | * [JavaScript Memory Profiling](https://developer.chrome.com/docs/devtools/) ,这是一篇谷歌官方教你如何使用 Chrome 的开发工具来分析 JavaScript 内存问题的文章。 48 | 49 | 接下来是一些性能工具。在线性能测试分析工具太多,这里只推荐比较权威的。 50 | 51 | * [PageSpeed](https://developers.google.com/speed) ,谷歌有一组 PageSpeed 工具来帮助你分析和优化网站的性能。Google 出品的,质量相当有保证。 52 | 53 | * [YSlow](https://github.com/marcelduran/yslow) ,雅虎的一个网页分析工具。 54 | 55 | * [GTmetrix](https://gtmetrix.com/) ,是一个将 PageSpeed 和 YSlow 合并起来的一个网页分析工具,并且加上一些 Page load 或是其它的一些分析。也是一个很不错的分析工具。 56 | 57 | * [Awesome WPO](https://github.com/davidsonfellipe/awesome-wpo) ,在 GitHub 上的这个 Awesome 中,你可以找到更多的性能优化工具和资源。 58 | 59 | 另外,中国的网络有各种问题(你懂的),所以,你不能使用 Google 共享的 JavaScript 链接来提速,你得用中国自己的。你可以到这里看看中国的共享库资源,[Forget Google and Use These Hosted JavaScript Libraries in China](https://chineseseoshifu.com/blog/china-hosted-javascript-libraries-jquery-dojo-boostrap.html) 。 60 | 61 | ## 前端框架 62 | 63 | 接下来,要学习的是 Web 前端的几大框架。目前而言,前端社区有三大框架 Angular.js、React.js 和 Vue.js。我认为,React 和 Vue 更为强劲一些,所以,我这里只写和 React 和 Vue 相关的攻略。关于两者的比较,网上有好多文章。我这里推荐几篇我觉得还不错的,供你参考。 64 | 65 | * [Angular vs. React vs. Vue: A 2017 comparison](https://medium.com/pixelpassion/angular-vs-react-vs-vue-a-2017-comparison-c5c52d620176) 66 | 67 | * [React or Vue: Which JavaScript UI Library Should You Be Using?](https://medium.com/js-dojo/react-or-vue-which-javascript-ui-library-should-you-be-using-543a383608d) 68 | 69 | * [ReactJS vs Angular5 vs Vue.js - What to choose in 2018?](https://medium.com/techmagic/reactjs-vs-angular5-vs-vue-js-what-to-choose-in-2018-b91e028fa91d) 70 | 71 | 其实,比较这些框架的优缺点还有利弊并不是要比出个输赢,而是让你了解一下不同框架的优缺点。我觉得,这些框架都是可以学习的。而在我们生活工作中具体要用哪个框架,最好还是要有一些出发点,比如,你是为了找份好的工作,为了快速地搭一个网站,为了改造一个大规模的前端系统,还是纯粹地为了学习…… 72 | 73 | 不同的目的会导致不同的决定。我并不希望上述的这些比较会让你进入“二选一”或是“三选一”的境地。我只是想通过这些文章让你知道这些框架的设计思路和实现原理,这些才是让你受益一辈子的事。 74 | 75 | ### React.js 框架 76 | 77 | 下面先来学习一下 React.js 框架。 78 | 79 | #### 入门 80 | 81 | React 学起来并不复杂,就看 [React 官方教程](https://reactjs.org/tutorial/tutorial.html) 和其文档就好了( [React 的中文教程](https://doc.react-china.org/) )。 82 | 83 | 然后,下面的文章会带你了解一下 React.js 的基本原理。 84 | 85 | * [All the fundamental React.js concepts](https://www.freecodecamp.org/news/all-the-fundamental-react-js-concepts-jammed-into-this-single-medium-article-c83f9b53eac2/) ,这篇文章讲了所有的 React.js 的基本原理。 86 | 87 | * [Learn React Fundamentals and Advanced Patterns](https://kentcdodds.com/blog/learn-react-fundamentals-and-advanced-patterns) ,这篇文章中有几个短视频,每个视频不超过 5 分钟,是学习 React 的一个很不错的地方。 88 | 89 | * [Thinking in React](https://reactjs.org/docs/thinking-in-react.html),这篇文章将引导你完成使用 React 构建可搜索产品数据表的思考过程。 90 | 91 | #### 提高 92 | 93 | 学习一个技术最重要的是要学到其中的思想和方法。下面是一些我觉得学习 React 中最重要的东西。 94 | 95 | * 状态,对于富客户端来说是非常麻烦也是坑最多的地方,这里有几篇文章你可以一读。 96 | * [Common React.js mistakes: Unneeded state](https://reactkungfu.com/2015/09/common-react-dot-js-mistakes-unneeded-state/) ,React.js 编程的常见错误——不必要的状态。 97 | * [State is an Anti-Pattern](https://www.reddit.com/r/reactjs/comments/3bjdoe/state_is_an_antipattern/) ,关于如何做一个不错的组件的思考,很有帮助。 98 | * [Why Local Component State is a Trap](https://www.oreilly.com/radar/) ,一些关于 “Single state tree” 的想法。 99 | 100 | * [Thinking Statefully](https://daveceddia.com/thinking-statefully/) ,几个很不错的例子让你对声明式有状态的技术有更好的理解。 101 | * 传统上,解决 React 的状态问题一般用 Redux。在这里推荐 [Tips to learn React + Redux in 2018](https://www.robinwieruch.de/tips-to-learn-react-redux) 。Redux 是一个状态粘合组件,一般来说,我们会用 Redux 来做一些数据状态和其上层 Component 上的同步。这篇教程很不错。 102 | * 最后是 "State Architecture Patterns in React " 系列文章,非常值得一读。 103 | * [Part 1: A Review](https://medium.com/@skylernelson_64801/state-architecture-patterns-in-react-a-review-df02c1e193c6) 104 | * [Part 2: The Top-Heavy Architecture, Flux and Performance](https://medium.com/@skylernelson_64801/state-architecture-patterns-in-react-part-2-the-top-heavy-architecture-flux-and-performance-a388b928ce89) 105 | * [Part 3: Articulation Points, zine and An Overall Strategy](https://medium.com/@skylernelson_64801/state-architecture-patterns-in-react-part-3-articulation-points-zine-and-an-overall-strategy-cf076f906391) 106 | * [Part 4: Purity, Flux-duality and Dataflow](https://medium.com/@skylernelson_64801/state-architecture-patterns-in-react-part-4-purity-flux-duality-and-dataflow-d06016b3379a) 107 | 108 | * 函数式编程。从 jQuery 过来的同学一定非常不习惯 React,而从 Java 等后端过来的程序员就会很习惯了。所以,我觉得 React 就是后端人员开发的,或者说是做函数式编程的人开发的。对此,你需要学习一下 JavaScript 函数式编程的东西。这里推荐一本免费的电子书 《[Professor Frisby’s Mostly Adequate Guide to Functional Programming](https://github.com/MostlyAdequate/mostly-adequate-guide)》,其中译版为《[JS 函数式编程指南中文版](https://jigsawye.gitbooks.io/mostly-adequate-guide/content/)》。下面有几篇文章非常不错。前两篇和函数式编程有关的文章非常值得一读。后三篇是一些比较实用的函数式编程和 React 结合的文章。 109 | * [Master the JavaScript Interview: What is Functional Programming?](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-functional-programming-7f218c68b3a0) 110 | * [The Rise and Fall and Rise of Functional Programming (Composing Software)](https://medium.com/javascript-scene/the-rise-and-fall-and-rise-of-functional-programming-composable-software-c2d91b424c8c) 111 | * [Functional UI and Components as Higher Order Functions](https://blog.risingstack.com/functional-ui-and-components-as-higher-order-functions/) 112 | * [Functional JavaScript: Reverse-Engineering the Hype](http://banderson.github.io/functional-js-reverse-engineering-the-hype/#/) 113 | * [Some Thoughts on Function Components in React](https://medium.com/javascript-inside/some-thoughts-on-function-components-in-react-cb2938686bc7) 114 | 115 | * 设计相关。接下来是学习一些 React 的设计模式。[React Pattern](https://reactpatterns.com/) 是一个不错的学习 React 模式的地方。除此之外,还有如下的一些不错的文章也会对你很有帮助的。 116 | * [React Higher Order Components in depth](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e) 117 | * [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) 118 | * [Controlled and uncontrolled form inputs in React don’t have to be complicated](https://goshakkk.name/controlled-vs-uncontrolled-inputs-react/) 119 | * [Function as Child Components](https://medium.com/merrickchristensen/function-as-child-components-5f3920a9ace9) 120 | * [Writing Scalable React Apps with the Component Folder Pattern](https://medium.com/styled-components/component-folder-pattern-ee42df37ec68) 121 | * [Reusable Web Application Strategies](https://www.freecodecamp.org/news/reusable-web-application-strategies-d51517ea68c8/) 122 | * [Characteristics of an Ideal React Architecture](https://medium.com/@robftw/characteristics-of-an-ideal-react-architecture-883b9b92be0b) 123 | 124 | #### 实践和经验 125 | 126 | 还有一些不错的实践和经验。 127 | 128 | * [9 things every React.js beginner should know](https://camjackson.net/post/9-things-every-reactjs-beginner-should-know) 129 | 130 | * [Best practices for building large React applications](https://engineering.sift.com/best-practices-for-building-large-react-applications/) 131 | 132 | * [Clean Code vs. Dirty Code: React Best Practices](https://americanexpress.io/clean-code-dirty-code/) 133 | 134 | * [How to become a more productive React Developer](https://dev.to/jakoblind/how-to-become-a-more-productive-react-developer) 135 | 136 | * [8 Key React Component Decisions](https://www.freecodecamp.org/news/8-key-react-component-decisions-cc965db11594/) 137 | 138 | #### 资源列表 139 | 140 | 最后就是 React 的资源列表。 141 | 142 | * [Awesome React](https://github.com/enaqx/awesome-react) ,这是一些 React 相关资源的列表,很大很全。 143 | 144 | * [React/Redux Links](https://github.com/markerikson/react-redux-links) ,这也是 React 相关的资源列表,与上面不一样的是,这个列表主要收集了大量的文章,其中讲述了很多 React 知识和技术,比上面的列表好很多。 145 | 146 | * [React Rocks](https://react.rocks/) ,这个网站主要收集各种 React 的组件示例,可以让你大开眼界。 147 | 148 | ### Vue.js 框架 149 | 150 | Vue 可能是一个更符合前端工程师习惯的框架。不像 React.js 那样使用函数式编程方式,是后端程序员的思路。 151 | 152 | * 通过文章 “[Why 43% of Front-End Developers want to learn Vue.js](https://medium.com/vue-mastery/why-43-of-front-end-developers-want-to-learn-vue-js-7f23348bc5be)” ,你可以看出其编程方式和 React 是大相径庭的,符合传统的前端开发的思维方式。 153 | 154 | * 通过文章 [Replacing jQuery With Vue.js: No Build Step Necessary](https://www.smashingmagazine.com/2018/02/jquery-vue-javascript/) ,我们可以看到,从 jQuery 是可以平滑过渡到 Vue 的。 155 | 156 | * 另外,我们可以通过 “[10 things I love about Vue](https://medium.com/@dalaidunc/10-things-i-love-about-vue-505886ddaff2)” ,了解 Vue 的一些比较优秀的特性。 157 | 158 | 最令人高兴的是,Vue 的作者是我的好朋友尤雨溪(Evan You),最近一次对他的采访 “[Vue on 2018 - Interview with Evan You](https://blog.hackages.io/https-blog-hackages-io-evanyoubhack2017-cc5559806157)” 当中有很多故事以及对 Vue 的展望。(**注意:Vue 是完全由其支持者和用户资助的,这意味着它更接近社区而不受大公司的控制。**) 159 | 160 | 要学习 Vue 并不难,我认为上官网看文档( [Vue 官方文档](https://vuejs.org/v2/guide/)([中文版](https://cn.vuejs.org/v2/guide/))),照着搞一搞就可以很快上手了。[Vue.js screencasts](https://laracasts.com/series/learn-vue-2-step-by-step) 是一个很不错的英文视频教程。 161 | 162 | 另外,推荐 [新手向:Vue 2.0 的建议学习顺序](https://zhuanlan.zhihu.com/p/23134551) ,这是 Vue 作者写的,所以有特殊意义。 163 | 164 | Vue 的确比较简单,有 Web 开发经验的人上手也比较快,所以这里也不会像 React 那样给出很多的资料。下面是一些我觉得还不错的内容,推荐给你。 165 | 166 | * [How not to Vue](https://itnext.io/how-not-to-vue-18f16fe620b5) ,任何技术都有坑,了解 Vue 的短板,你就能扬长避短,就能用得更好。 167 | 168 | * [Vue.js Component Communication Patterns](https://www.digitalocean.com/community/tutorials/vuejs-component-communication) 169 | 170 | * [4 AJAX Patterns For Vue.js Apps](https://medium.com/js-dojo/4-ajax-patterns-for-vue-js-apps-add915fc9168) 171 | 172 | * [How To (Safely) Use A jQuery Plugin With Vue.js](https://vuejsdevelopers.com/2017/05/20/vue-js-safely-jquery-plugin/) 173 | 174 | * [7 Ways To Define A Component Template in Vue.js](https://vuejsdevelopers.com/2017/03/24/vue-js-component-templates/) 175 | 176 | * [Use Any Javascript Library With Vue.js](https://vuejsdevelopers.com/2017/04/22/vue-js-libraries-plugins/) 177 | 178 | * [Dynamic and async components made easy with Vue.js](https://lobotuerto.com/blog/dynamic-and-async-components-made-easy-with-vuejs/#/) 179 | 180 | 当然,最后一定还有 [Awesome Vue](https://github.com/vuejs/awesome-vue) ,Vue.js 里最为巨大最为优秀的资源列表。 181 | 182 | ## 小结 183 | 184 | 总结一下今天的内容。我先介绍的是前端性能优化方面的内容,推荐了图书、最佳实践性的文档、案例,以及一些在线性能测试分析工具。随后重点讲述了 React 和 Vue 两大前端框架,给出了大量的文章、教程和相关资源列表。我认为,React.js 使用函数式编程方式,更加符合后端程序员的思路,而 Vue 是更符合前端工程师习惯的框架。因此,两者比较起来,Vue 会更容易上手一些。 185 | 186 | 下篇文章,我们将讲述前端工程师的一个基本功——UI/UX 设计。敬请期待。 -------------------------------------------------------------------------------- /21_UI和UX设计/README.md: -------------------------------------------------------------------------------- 1 | # UI/UX设计 2 | 3 | 上面的技术都讲完了,前端还有一个很重要的事就是设计。作为前端人员,我们有必要了解现在的一些知名且流行的设计语言或是一些设计规范或是设计方法,学习它们的设计思想和方法,有助于我们拓宽眼界、与时俱进。我并不觉得这些内容是设计师要学习的,如果你要成为一个前端程序员,那么学习这些设计上的东西可以让你有更好的成长空间。 4 | 5 | 对于学习设计的新手来说,推荐看看 [7 steps to become a UI/UX designer](https://blog.nicolesaidy.com/7-steps-to-become-a-ui-ux-designer-8beed7639a95) ,这是一篇很不错的让新手入门的文章,非常具有指导性。首先,你得开始学习设计的一些原则和套路,如配色、平衡、排版、一致性等。还有用户体验的 4D 步骤——Discover、Define、Develop 和 Delivery。然后,开始到一些网站上找灵感。接下来,是到不同的网站上读各种文章和资源,开始学习使用设计工具,最后是找人拜师。此外,其中还链接了其它一些不错的文章、网站、博客和工具。我认为,这篇文章是一篇很不错的设计师从入门到精通的练级攻略。 6 | 7 | 虽然有这么一个速成的教程,但我觉得还是应该系统地学习一下,所以有了下面这些推荐。 8 | 9 | ## 图书和文章推荐 10 | 11 | 先推荐几本书。 12 | 13 | * [Don’t Make Me Think](https://book.douban.com/subject/1827702/) ,这是我看的第一本和设计相关的书。这本书对我的影响也比较深远。这本书践行了自己的理论,整本书短小精悍,语言轻松诙谐,书中穿插大量色彩丰富的屏幕截图、趣味丛生的卡通插图以及包含大量信息的图表,使枯燥的设计原理变得平易近人。 14 | 15 | * [Simple and Usable Web,Mobile,and Interaction Design](https://book.douban.com/subject/5394309/) ,中文版译名为《简约至上》。本书作者贾尔斯(Giles)有 20 多年交互式设计的探索与实践。提出了合理删除、分层组织、适时隐藏和巧妙转移这四个达成简约至上的终极策略,讲述了为什么应该站在主流用户一边,以及如何从他们的真实需求和期望出发,简化设计,提升易用性。 16 | 17 | * [Designing with the Mind in Mind: Simple Guide to Understanding User Interface Design Rules](https://book.douban.com/subject/6792322/) ,中文版译名为《认知与设计:理解 UI 设计准则》。这本书语言清晰明了,将设计准则与其核心的认知学和感知科学高度统一起来,使得设计准则更容易在具体环境中得到应用。涵盖了交互计算机系统设计的方方面面,为交互系统设计提供了支持工程方法。不仅如此,这也是一本人类行为原理的入门书。 18 | 19 | * [Designing Interfaces: Patterns for Effective Interaction Design](https://book.douban.com/subject/25716088/) ,中文版译名为《界面设计模式》。这本书开篇即总结了“与人有关”的各类问题,为读者提供了界面设计总体思路上的指引,帮助读者举一反三。然后,收集并分析了很多常用的界面设计模式,帮助读者理解在实现级别的各种常用解决方案,将它们灵活地运用到自己的设计中。 20 | 21 | 除了上面的这几本书,还有下面的这几篇文章也是很不错的,推荐一读。 22 | 23 | * [The Psychology Principles Every UI/UX Designer Needs to Know](https://uxplanet.org/the-psychology-principles-every-ui-ux-designer-needs-to-know-24116fd65778) ,这篇文章讲述了 6 大用户界面用户体验设计的心理学原则。 24 | 25 | * [18 designers predict UI/UX trends for 2018](https://www.figma.com/blog/eighteen-designers-predict-ui-ux-trends-for-2018/), 我倒不觉得这篇文章中所说的 UI/UX 是在 2018 年的趋势,我反而觉得,这 18 条原则是指导性的思想。 26 | 27 | * [The Evolution of UI/UX Designers Into Product Designers](https://medium.com/thinking-design/the-evolution-of-ui-ux-designers-into-product-designers-623e4e7eaab3) ,这篇文章是 Adobe 公司的一篇博客,其在回顾整个产品设计的演化过程中有一些不错的思考和想法,并提供了一些方法论。 28 | 29 | ## 原子设计(Atomic Design) 30 | 31 | 在 2013 年网页设计师布拉德·弗罗斯特(Brad Frost)从化学中受到启发:原子(Atoms)结合在一起,形成分子(Molecules),进一步结合形成生物体(Organisms)。布拉德将这个概念应用在界面设计中,我们的界面就是由一些基本的元素组成的。 32 | 33 | 乔希·杜克(Josh Duck)的“HTML 元素周期表”完美阐述了我们所有的网站、App、企业内部网、hoobadyboops 等是如何由相同的 HTML 元素组成的。通过在大层面(页)和小层面(原子)同时思考界面,布拉德认为,可以利用原子设计建立一个适应组件的动态系统。 34 | 35 | 为什么要玩原子设计,我认为,这对程序员来说是非常好理解的,因为这就是代码模块化重用化的体现。于是,你就是要像搭积木一样开发和设计网页,当你把其模块化组件化了,也更容易规范整体的风格,而且容易维护……这些都意味着你可以更容易地维护你的代码。所以,这个方法论导致了 Web 组件化的玩法。这是设计中非常重要的方法论。 36 | 37 | 关于这个设计方法论,你可以阅读一下下面这几篇文章。 38 | 39 | * [Atomic Design 原子设计┃构建科学规范的设计系统](https://www.jianshu.com/p/13e87bf4f857) 40 | 41 | * [网页设计:Atomic Design 简介及工作实例](https://medium.com/uxeastmeetswest/%E7%B6%B2%E9%A0%81%E8%A8%AD%E8%A8%88-atomic-design%E7%B0%A1%E4%BB%8B%E5%8F%8A%E5%B7%A5%E4%BD%9C%E5%AF%A6%E4%BE%8B-42e666358d52) 42 | 43 | 但是,真正权威的地方还是布拉德·弗罗斯特的电子书、博客和实验室,可以从中获取更多的信息。 44 | 45 | * [电子书:Atomic Design by Brad Frost](https://atomicdesign.bradfrost.com/) 是布拉德·弗罗斯特写的一本书。 46 | 47 | * [博 客:Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/) 是布拉德·弗罗斯特的博客。 48 | 49 | * [实验室:Pattern lab](https://patternlab.io/) 是布拉德·弗罗斯特依照这个设计系统所建立的一套工具,可以前往 Pattern Lab 的 [GitHub](https://github.com/bradfrost/patternlab) 来试试 Atomic design。 50 | 51 | 接下来是关于这个设计方法和 React.js 框架的几篇文章。 52 | 53 | * [Atomic Design with React](https://codeburst.io/atomic-design-with-react-e7aea8152957) 54 | 55 | * [Atomic Components: Managing Dynamic React Components using Atomic Design](https://medium.com/@yejodido/atomic-components-managing-dynamic-react-components-using-atomic-design-part-1-5f07451f261f) 56 | 57 | ## 设计语言和设计系统 58 | 59 | 下面来介绍一下设计语言和设计系统。 60 | 61 | ### Fluent Design System 62 | 63 | [Fluent Design System](https://fluent.microsoft.com/) 中文翻译为流畅设计体系,是微软于 2017 年开发的设计语言。流畅设计是 Microsoft Design Language 2 的改版,其中包含为所有面向 Windows 10 设备和平台设计的软件中的设计和交互的指导原则。 64 | 65 | 该体系基于五个关键元素:光感、深度、动效、材质和缩放。新的设计语言包括更多对动效、深度及半透明效果的使用。过渡到流畅设计体系是一个长期项目,没有具体的完成目标,但是从创作者更新以来,新设计语言的元素已被融入到个别应用程序中。它将在未来的 Windows 10 秋季创作者更新中更广泛地使用,但微软也表示,该设计体系不会在秋季创作者更新内完成。 66 | 67 | 微软于 2017 年 5 月 11 日的 Microsoft Build 2017 开发者大会上公开了该设计体系。 68 | 69 | * [What’s new and coming for Windows UI: XAML and composition](https://channel9.msdn.com/Events/Build/2017/B8100) ,从概念上讲了一下 Fluent Design System 的各个部分。 70 | 71 | * [Introducing Fluent Design](https://channel9.msdn.com/Events/Build/2017/B8066) ,介绍了 Fluent Design System 的各个部分。 72 | 73 | 还有 Build 2018 上的一些微软的 YouTube 分享。 74 | 75 | * [Fluent Design: Evolving our Design System : Build 2018](https://www.youtube.com/watch?v=AnqwdPgVXAI) 76 | 77 | * [Microsoft Build 2018 - Fluent Design System Demo](https://www.youtube.com/watch?v=dMq8CMIE1xU) 78 | 79 | * [Microsoft Build 2018 - Fluent Design System Evolution](https://www.youtube.com/watch?v=pUuHSuCnDGE) 80 | 81 | * [Fluent Design System inside of Microsoft: Office : Build 2018](https://www.youtube.com/watch?v=DKvkRfQD8Yg) 82 | 83 | ### Material Design 84 | 85 | [Material Design](https://material.io/) 中文翻译为质感设计,或是材质设计、材料设计。这是由 Google 开发的设计语言。扩展于 [Google Now](https://en.wikipedia.org/wiki/Google_Now) 的“卡片”设计,Material Design 基于网格的布局、响应动画与过渡、填充、深度效果(如光线和阴影)。设计师马蒂亚斯·杜阿尔特(Matías Duarte)解释说:“与真正的纸张不同,我们的数字材质可以智能地扩大和变形。材质具有实体的表面和边缘。接缝和阴影表明组件的含义。”Google 指出他们的新设计语言基于纸张和油墨。 86 | 87 | Material Design 于 2014 年的 Google I/O 大会上发布(参看 [Google I/O 2014 - Material witness: How Android material applications work](https://www.youtube.com/watch?v=97SWYiRtF0Y))。其可借助 v7 appcompat 库用于 Android 2.1 及以上版本,几乎支持所有 2009 年以后制造的 Android 设备。随后,Material Design 扩展到 Google 的网络和移动产品阵列,提供一致的跨平台和应用程序体验。Google 还为第三方开发人员发布了 API,开发人员可将质感设计应用到他们的应用程序中。 88 | 89 | 除了到 [官网](https://material.io/) 学习 Material Design,你还可以访问 [Material Design 中文版](http://design.1sters.com/) 来学习。 90 | 91 | 另外,Wikipedia 上有一张 [Material Design 实现的比较表](https://en.wikipedia.org/wiki/Comparison_of_Material_Design_implementations),供你参考。 92 | 93 | 下面是几个可供你使用的 Material UI 的工程实现。 94 | 95 | * [Material Design Lite](https://getmdl.io/) ,这是 Google 官方的框架,简单易用。 96 | 97 | * [Materialize](https://materializecss.com/) ,一组类似于 Bootstrap 的前端 UI 框架。 98 | 99 | * [Material-UI](https://material-ui.com/zh/) 是基于 Google Material Design 的 React 组件实现。 100 | 101 | * [MUI](https://www.muicss.com/) 是一个轻量级的 CSS 框架,遵循 Google 的 Material Design 设计方针。 102 | 103 | ### 其它公司 104 | 105 | 接下来再来推荐其它几家公司的设计语言。 106 | 107 | * [苹果公司的设计指南](https://developer.apple.com/design/),在这个网站有苹果的各种设备的设计规范和指导,一方面可以让你的 App 能和苹果的 UI 融合在一起,另一方面,你也可以从中看到苹果的审美和思维方式。 108 | 109 | * [IBM 公司的设计语言](https://www.ibm.com/design/language/) ,我们总觉得 IBM 公司是一家比较传统的没有新意的公司,但是并不是这样的。IBM 公司的这个设计语言的确比较出众。所以,在这里推荐一下。 110 | 111 | * [Salesforce 公司的 Lightning Design System](https://www.lightningdesignsystem.com/) ,是在 Salesforce 生态系统中用于创建统一 UI 的设计模式、组件和指南的集合,是一个企业级的产品。 112 | 113 | * [Facebook Design - What’s on our mind?](https://design.facebook.com/) ,Facebook 的设计师们收集的一系列的文章、视频和资源。很不错哦。 114 | 115 | ### 动画效果设计 116 | 117 | 我认为,要了解 Web 动画效果设计的第一步,最好的地方是 [CodePen](https://codepen.io/)。这个网站不只是让人分享 HTML、CSS 和 JavaScript 代码的网站。其中也有很多分享样例都和动画效果有关。这个网站可以让你对动画效果有一些感性认识,当然还有代码供你参考。 118 | 119 | 接下来,我们要了解动画效果设计的一些方法。基本上来说,动画设计都会受 “[动画的 12 项基本法则](https://en.wikipedia.org/wiki/Twelve_basic_principles_of_animation) ”的影响,这个方法论源自于迪士尼动画师奥利·约翰斯顿(Ollie Johnston)和弗兰克·托马斯(Frank Thomas)在 1981 年所出的《The Illusion of Life: Disney Animation》一书。这些法则已被普遍采用,至今仍与制作 3D 动画法则有关联。这里还有一篇文章 “[Understand the 12 principles of animation](https://www.creativebloq.com/advice/understand-the-12-principles-of-animation)” 是对这个法则的解读和理解。 120 | 121 | 除此之外,还有几个动画设计指南和相关文章供你参考和学习。 122 | 123 | * [6 Animation Guidelines for UX Design](https://blog.prototypr.io/6-animation-guidelines-for-ux-design-74c90eb5e47a)。这是 Prototypr 公司的一个指南,其中主要指出,动画效果不是为了炫配,而是能让你的 UI/UX 能活起来,自然,不消耗时间,并且是生动故事型的动画效果。其中还推荐了如下几篇很不错的文章。 124 | * [Transitional Interfaces](https://medium.com/@pasql/transitional-interfaces-926eb80d64e3) 125 | * [UI Animation and UX: A Not-So-Secret Friendship](https://alistapart.com/article/ui-animation-and-ux-a-not-so-secret-friendship/) 126 | * [Invisible animation](https://medium.com/@stevenfabre/invisible-animation-ffa27d0b77e5) 127 | * [Creating Usability with Motion: The UX in Motion Manifesto](https://medium.com/ux-in-motion/creating-usability-with-motion-the-ux-in-motion-manifesto-a87a4584ddc) 128 | 129 | * [Designing Interface Animation](https://alistapart.com/article/designing-interface-animation/) ,这篇文章同样说明,任何一个小动画都是要讲一个微故事的,而且这些微故事会和你的品牌和产品理念相融合。动画会给人更深的印象,让人们更容易记住你。这篇文章主要是讲品牌动画。 130 | 131 | * [Animation principles in motion design](https://www.freepik.com/blog/animation-principles-in-motion-design/) ,这篇文章有点像设计模式,给了一些动画效果的套路和演示。 132 | 133 | * [Creating Usability with Motion: The UX in Motion Manifesto](https://medium.com/ux-in-motion/creating-usability-with-motion-the-ux-in-motion-manifesto-a87a4584ddc) 134 | 135 | * [Integrating Animation into a Design System](https://alistapart.com/article/integrating-animation-into-a-design-system/) 136 | 137 | * Great UI/UX Animations 是设计师丹尼尔(Daniel)收集的一些很不错的动画,可以给你一些灵感。 138 | * [Great UI/UX Animations 第一组](https://fromupnorth.com/mixed-ui-ux-animations-4d7a22f9ab7) 139 | * [Great UI/UX Animations 第二组](https://fromupnorth.com/great-ui-ux-animations-3e9a0baa336f) 140 | 141 | ## 相关资源 142 | 143 | 下面分享一下 UI/UX 设计的相关资源。文章资源主要有以下这些。 144 | 145 | ### 文章资源 146 | 147 | * [Web Designer News](https://www.webdesignernews.com/) ,一个文章聚合的网站。除此之外,还有两个文章聚合网站,你也可以订阅。一个是[Designer News](https://www.designernews.co/) ,另一个是 [Reddit Web Design](https://www.reddit.com/r/web_design/)。 148 | 149 | * [Marvel Blog](https://marvelapp.com/blog/) ,Marvel 团队的博客。 150 | 151 | * [The Next Web](https://thenextweb.com/topic/creative) ,内容主要涵盖国际技术新闻、商业和文化等多个方面。 152 | 153 | * [Medium - Design](https://medium.com/design) ,Medium 现在已经成为一个好文章的集散地了,这个地方必去。 154 | 155 | * [Smashing Magazine](https://www.smashingmagazine.com/) ,这个地方是给专业的 Web 设计师和程序员的。不但有设计还有 HTML、CSS 和 JavaScript 等各种资源。 156 | 157 | * [Sitepoint](https://www.sitepoint.com/design-ux/) ,这个网站上也有很多不错的给 Web 前端程序员和设计师看的文章(当然,给程序员看的有点简单了,我觉得更像是让设计师来学写程序的网站)。 158 | 159 | ### 设计收集 160 | 161 | 接下来推荐一些优秀设计的聚集地。 162 | 163 | * [Awwwards](https://www.awwwards.com/) ,这个网站给一些设计得不错网站的评分,在这里你可以看到很多设计不错的网站。 164 | 165 | * [One Page Love](https://onepagelove.com/) ,就是一个单页的网页设计的收集。 166 | 167 | * [Inspired UI](https://inspired-ui.com/) ,移动 App 的设计模式。 168 | 169 | * [Behance](https://www.behance.net/),这个地言有很不错的很有创意的作品。 170 | 171 | * [Dribbble](https://dribbble.com/) ,这应该是设计师都知道也都爱去的网站。除了你可以看到一些很不错的作品外,你还可以在这里看到很多不错的设计师。 172 | 173 | * [UI Movement](https://screenlane.com/?ref=uimovement) ,也是个设计的收集网站,上面有很多很不错的 UI 设计,大量的动画。虽说会像抖音一样,让你不知不觉就看了好几小时,但是它比抖音让你的收获大多了。 174 | 175 | ## 小结 176 | 177 | 总结一下今天的内容。我并不认为 UI/UX 设计这些内容只是设计师要学习的,如果你要成为一个前端程序员,那么学习这些设计上的东西可以让你有更好的成长空间。首先,我推荐了一些图书和文章,让你更好地了解经典的设计原则和指导思想。 178 | 179 | 然后介绍了原子设计,以及深入学习和理解这一设计方法论的图书、文章和其他相关资源。最后分享了当下主流和知名公司中在用的设计语言和设计系统,并给出了大量的学习资源,推荐了一些优秀设计的聚集地。相信通过学习这些内容,你在 UI/UX 设计方面不仅能收获方法,还能获得非常多的灵感。 180 | 181 | 下篇文章是程序员练级攻略高手成长篇的最后一篇,我将推荐大量有价值的技术资源,这些内容将会为你后续的学习和成长提供很大的助力。敬请期待。 -------------------------------------------------------------------------------- /22_技术资源集散地/README.md: -------------------------------------------------------------------------------- 1 | # 技术资源集散地 2 | 3 | ## 个人技术博客 4 | 5 | 首先,我先推荐一些不错的个人技术博客。 6 | 7 | * [Coding Horror](https://blog.codinghorror.com/) ,这是杰夫·阿特伍德(Jeff Atwood)于 2004 年创办的博客,记录其在软件开发经历中的所思所想、点点滴滴。时至今日,该博客每天都有近 10 万人次的访问量,读者纷纷参与评论,各种观点与智慧在这里不断地激情碰撞。其博文选集在中国被翻译成《[高效能程序员的修练](https://book.douban.com/subject/24868904/)》,在豆瓣上有 8.3 的高分。2008 年,他和 Joel Spolsky 联合创办了 [StackOverflow](https://stackoverflow.com/) 问答网站,为程序员在开发软件时节省了非常多的时间,并开启了“StackOverflow Copy + Paste 式编程”。 8 | 9 | * [Joel on Software](https://www.joelonsoftware.com/) ,Joel Spolsky 的这个博客在全世界都有很多的读者和粉丝,其博文选集在中国被翻译成《[软件随想录](https://book.douban.com/subject/4163938/)》在豆瓣上有 8.7 的高分。这是一本关于软件技术、人才、创业和企业管理的随想文集,作者以诙谐幽默的笔触将自己在软件行业的亲身感悟娓娓道来,观点新颖独特,简洁实用。 10 | 11 | * [Clean Coder Blog](http://blog.cleancoder.com/) ,这是编程大师“Bob 大叔”的博客,其真名叫 Robert C. Martin,世界级软件开发大师,设计模式和敏捷开发先驱,敏捷联盟首任主席,C++ Report 前主编,被后辈程序员尊称为“Bob 大叔”。其博文选集在中国被翻译成《[程序员的职业素养](https://book.douban.com/subject/11614538/)》,在豆瓣上有 8.8 的高分。 12 | 13 | * [Martin Fowler](https://martinfowler.com/) ,这是另外一个程序员大师,Martin 主要专注于面向对象分析与设计、统一建模语言、领域建模,以及敏捷软件开发方法,包括极限编程。他的《[重构](https://book.douban.com/subject/1229923/)》、《[分析模式](https://book.douban.com/subject/4832380/)》、《[企业应用架构模式](https://book.douban.com/subject/1230559/)》、《[领域特定语言](https://book.douban.com/subject/21964984/)》和《[NoSQL 精粹](https://book.douban.com/subject/25662138/)》都是非常不错的书。在他的博客上有很多很多的编程和架构模式方法可以学习。 14 | 15 | * [Paul Graham Essays](http://www.paulgraham.com/articles.html) ,美国著名程序员、风险投资家、博客和技术作家。《[黑客与画家](https://book.douban.com/subject/6021440/)》是他的著作之一。2005 年他与人共同创建了科技创业孵化器 Y Combinator,孵化了 Airbnb、Dropbox、Stripe 等知名互联网公司。他有几篇创业方面的文章都很经典,如果你想创业,可以读一读这几篇:《[How to Get Startup Ideas](http://paulgraham.com/startupideas.html)》、《[Do Things that Don’t Scale](http://paulgraham.com/ds.html)》、《[Startup = Growth](http://www.paulgraham.com/growth.html)》。Paul Graham 的文章以清新自然,思想深刻见长。不仅可以跟 Paul Graham 学创业,学思考,学技术,更可以学习写作。 16 | 17 | * [Steve Yegge](https://medium.com/@steve.yegge) ,Steve Yegge 这个人算是一个知名的程序员了,在 Amazon 呆过,现在在 Google,他的文章都是长篇大论,最知名的文章就是[对 Amazon 和 Google 平台的吐槽](https://coolshell.cn/articles/5701.html),这篇文章引发了大家的讨论和议论。 18 | 19 | * [Bruce Eckel’s Programming Blog](http://bruceeckel.github.io/) ,《Thinking in Java》作者的博客,他之前的博客在 artima - [Computing Thoughts](https://www.artima.com/weblogs/index.jsp?blogger=beckel) 。 20 | 21 | * [Herb Sutter](https://herbsutter.com/) ,C++ 大拿,C++ 标准委员会专家,微软软件架构师。《Exceptional C++ 》、《More Exceptional C++》、《Exceptional C++ Style》作者。 22 | 23 | * [Eli Bendersky’s website](https://eli.thegreenplace.net/) ,这位老哥从 2003 年就一直写博客到今天,其中的文章都非常不错,原理型的,主要是 C、C++ 和 Python 相关的。里面有很多干货。 24 | 25 | * [Peter Krumins’ blog](https://catonmat.net/) ,这位老哥从 2007 年开始写博客,他博客里好玩的东西太多了。 26 | 27 | * [Brendan D. Gregg](http://www.brendangregg.com/index.html) ,Brendan 是 Netflix 的工程师,他的博客里有大量的非常不错的文章,基本上都是和 Linux 性能分析相关的,这是一个如果你要玩底层性能分析一定不能错过的博客。 28 | 29 | * [Evan Klitzke](https://eklitzke.org/) ,主要讨论 Linux 和 C++ 相关的内容。 30 | 31 | * [Julia Evans](https://jvns.ca/) ,主要讨论 Linux debug 工具和网络相关的内容。 32 | 33 | * [null program](https://nullprogram.com/) ,和 C/C++ 相关的一个博客。其中关于 Linux 系统调用、GPU、无锁编程、JIT 编译的一些文章非常不错。 34 | 35 | * [Fluent {C++}](http://www.fluentcpp.com/) ,博主是 Murex 的首席工程师,主要玩 C++,在这个博客里有很多很不错的 C++ 相关的文章。 36 | 37 | * [Preshing on Programming](https://preshing.com/) ,这也是一个和 C/C++ 相关的博客,其中有很多的干货。 38 | 39 | * [Programming is Terrible](https://programmingisterrible.com/) ,这个博客有很多强观点的文章,主要是软件开发中的一些教训。 40 | 41 | * [Accidentally Quadratic](https://accidentallyquadratic.tumblr.com/) ,姑且翻译成事故二次方,这里有好些非常有趣的文章。 42 | 43 | * [Hacker Noon](https://hackernoon.com/) ,这是一个一堆人在写的博客,里面有很多质量很高的文章。 44 | 45 | 其实还有很多不错的博客,不过,现在国外不错的博客都在一个叫 [Medium](https://medium.com/) 的网站,我也发现我 Google 很多东西时都会到这个网站上。这个网站上的内容不只有技术的,还有很多很多其他方面的内容,比如文化、艺术、科学等等。这个网站就是一个博客发布系统,其是由 Twitter 联合创始人埃文·克拉克·威廉姆斯(Evan Clark Williams)和克里斯多福·艾萨克·比兹·斯通(Christopher Isaac Biz Stone)创办的,这两个人觉得 Twitter 上全是垃圾没有营养的信息。所以,创办了 Medium,这个平台上有专业和非专业的贡献者,亦有受雇的编者。 46 | 47 | 我已经感觉到,未来高质量的文章都会在 Medium 这个平台上出现,因为有一些公司的技术博客也在这个平台上发布了,比如 Netflix 的。所以,你有必要上到这个平台上 follow 一些作者、专栏和主题。 48 | 49 | ## YouTube 技术频道 50 | 51 | 下面是我订阅的一些我认为还不错的和编程相关的频道,推荐给你。 52 | 53 | * [Devoxx](https://www.youtube.com/channel/UCCBVCTuk6uJrN3iFV_3vurg) ,Devoxx 的频道,其中有各种很不错的技术分享。 54 | 55 | * [Coding Tech](https://www.youtube.com/channel/UCtxCXg-UvSnTKPOzLH4wJaQ) ,也是个非常不错的编程频道,涵盖各种技术。 56 | 57 | * [Amazon Web Services](https://www.youtube.com/channel/UCd6MoB9NC6uYN2grvUNT-Zg) 58 | * [Facebook Developers](https://www.youtube.com/user/FacebookDevelopers) 59 | * [Google Developer](https://www.youtube.com/user/GoogleDevelopers) ,Google 公司的官方频道,其中包括 Google I/O 大会、教程、新闻、最佳实践、技巧分享…… 60 | 61 | * [Spring Developer](https://www.youtube.com/user/SpringSourceDev) ,Spring 的官方频道。 62 | 63 | * [Microsoft Research](https://www.youtube.com/user/MicrosoftResearch) 64 | 65 | * [MIT 公开课](https://www.youtube.com/user/MIT) 66 | 67 | * [Stanford Online](https://www.youtube.com/user/stanfordonline) 68 | 69 | * [Prof. Dr. Jens Dittrich](https://www.youtube.com/user/jensdit) ,一个德国教授开的一个关于数据库相关的频道,里面有很不错的数据库内在原理的内容。 70 | 71 | * [Red Hat Summit](https://www.youtube.com/user/redhatsummit) ,RedHat 峰会频道,其中有很多和 Linux 相关的技术新闻和分享。 72 | 73 | * [Open Networking Summit](https://www.youtube.com/user/OpenNetSummit) ,这是一个网络相关的频道。 74 | 75 | * [Dan Van Boxel](https://www.youtube.com/user/dvbuntu) ,这是一个机器学习工程师折腾各种事的视频,挺有意思的。 76 | 77 | * [The New Boston](https://www.youtube.com/user/thenewboston) ,这个频道应该是前端开发工程师必去的地方,可能也是我所知道的最好的关于前端技术的 YouTube 频道。 78 | 79 | * [Derek Banas](https://www.youtube.com/user/derekbanas) 是一个教程型的频道,其中包括编程语言、游戏开发、Web 开发……我个人觉得是一个可以用来练英文听力的频道。 80 | 81 | * [Java](https://www.youtube.com/user/java) ,Java 相关的各种分享。 82 | 83 | * [CppCon](https://www.youtube.com/user/CppCon) ,C++ 大会的一些视频,可以让你了解很多 C++ 最新功能和相关的动态。 84 | 85 | * [Computerphile](https://www.youtube.com/user/Computerphile) ,这个频道是布雷迪·哈伦(Brady Haran)运作的几个频道中的一个,在这个频道里你可以看到很多很有趣的技术方面的科普教程、资讯、见闻等,说得都非常地简单易懂,所以有大量的订阅用户。布雷迪是个对任何技术都很有热情的人,这个频道是关于计算机技术的。除此之外,他还运作 [Numberphile](https://www.youtube.com/user/Numberphile)(数学)、[Periodic Videos](https://www.youtube.com/user/periodicvideos)(化学)、[Sixty Symbols](https://www.youtube.com/user/sixtysymbols)(物理)、[Deep Sky Videos](https://www.youtube.com/user/DeepSkyVideos)(天文)等有众多阅人数的频道。如果你喜欢,你都可以一一订阅,感觉就是一个个人版的 Discovery。 86 | 87 | * 关于安全,有如下四个频道你可以订阅一下: 88 | * [DEFCONConference](https://www.youtube.com/user/DEFCONConference) ,defcon.org 的官方频道。 89 | * [CCCen](https://www.youtube.com/user/mediacccde) ,Chaos Computer Club。 90 | * [RSA Conference](https://www.youtube.com/user/RSAConference) ,RSA Conference。 91 | * [Black Hat](https://www.youtube.com/user/BlackHatOfficialYT) - Black Hat Conference。 92 | 93 | ## 各大公司技术博客 94 | 95 | 细心的你一定会发现这份攻略中的很多推荐文章都来自于各个公司的技术团队的博客。是的,跟随这些公司的博客,你不但可以看到这些公司的工程技术,还能掌握到一些技术方向和趋势。 96 | 97 | 下面是 Airbnb、AWS、Cloudera、Dropbox、Facebook、Google 等各个公司的技术博客列表。 98 | 99 | * [Airbnb Engineering](https://medium.com/airbnb-engineering) 100 | 101 | * AWS 相关 102 | * [All Things Distributed](https://www.allthingsdistributed.com/) 103 | * [AWS Architecture Blog](https://aws.amazon.com/cn/blogs/architecture/) 104 | * [On Efficiency, Reliability, Scaling - James Hamilton, VP at AWS](https://mvdirona.com/jrh/work/) 105 | 106 | * [Bandcamp Tech](https://bandcamptech.wordpress.com/) 107 | 108 | * [BankSimple Simple Blog](https://www.bbvausa.com/go/simple.html) 109 | 110 | * [Bitly Engineering Blog](https://word.bitly.com/) 111 | 112 | * [Cloudera Developer Blog](https://blog.cloudera.com/) 113 | 114 | * [Dropbox Tech Blog](https://dropbox.tech/) 115 | 116 | * [Etsy Code as Craft](https://codeascraft.com/) 117 | 118 | * [Facebook Engineering](https://www.facebook.com/Engineering) 119 | 120 | * [Flickr Code](https://code.flickr.net/) 121 | 122 | * [Foursquare Engineering Blog](https://developer.foursquare.com/blog/) 123 | 124 | * [Google Research Blog](https://ai.googleblog.com/) 125 | 126 | * [Groupn Engineering Blog](https://medium.com/groupon-eng) 127 | 128 | * [High Scalability](http://highscalability.com/) 129 | 130 | * [Instagram Engineering](https://instagram-engineering.com/) 131 | 132 | * [LinkedIn Engineering](https://engineering.linkedin.com/blog) 133 | 134 | * [Oyster Tech Blog](http://tech.oyster.com/) 135 | 136 | * [Pinterest Engineering Blog](https://www.pinterestcareers.com/) 137 | 138 | * [Quora Engineering](https://quoraengineering.quora.com/) 139 | 140 | * [Songkick Technology Blog](https://devblog.songkick.com/) 141 | 142 | * [SoundCloud Backstage Blog](https://developers.soundcloud.com/blog/) 143 | 144 | * [Square The Corner](https://developer.squareup.com/blog/) 145 | 146 | * [The Reddit Blog](https://redditblog.com/) 147 | 148 | * [The GitHub Blog](https://github.blog/category/engineering/) 149 | 150 | * [The Netflix Tech Blog](https://netflixtechblog.com/) 151 | 152 | * [Twilio Engineering Blog](https://www.twilio.com/blog) 153 | 154 | * [Twitter Engineering](https://blog.twitter.com/engineering/en_us.html) 155 | 156 | * [WebEngage Engineering Blog](https://webengage.com/blog) 157 | 158 | * [Yammer Engineering](http://eng.yammer.com/blog/) 159 | 160 | * [Yelp Engineering Blog](https://engineeringblog.yelp.com/) 161 | 162 | * [Smarkets Blog](https://smarketshq.com/) 163 | 164 | ## 论文 165 | 166 | 要想将技术研究得精深,论文是必不可少的。那要如何读论文呢? 167 | 168 | ### 如何读论文 169 | 170 | 下面有几篇文章,教你一些读论文的方法,非常不错。 171 | 172 | * [How to read an academic article](https://organizationsandmarkets.com/2010/08/31/how-to-read-an-academic-article/) 173 | 174 | * [Advice on reading academic papers](https://www.cc.gatech.edu/~akmassey/posts/2012-02-15-advice-on-reading-academic-papers.html) 175 | 176 | * [How to read and understand a scientific paper](https://violentmetaphors.com/2013/08/25/how-to-read-and-understand-a-scientific-paper-2/) 177 | 178 | * [Should I Read Papers?](https://michaelrbernste.in/2014/10/21/should-i-read-papers.html) 179 | 180 | * [The Refreshingly Rewarding Realm of Research Papers](https://www.youtube.com/watch?v=8eRx5Wo3xYA) 181 | 182 | ### 论文集散地 183 | 184 | 要成长为一个高手,论文是你一定要读的。下面是一些非常不错的计算机方面的论文集散地。 185 | 186 | * [2 Minute Papers](https://www.youtube.com/user/keeroyz) ,这是一个 YouTube 的频道,其会给出一些非常不错的和计算机相关的论文介绍,让你了解目前最有意思的一些科学突破,每次两分钟左右。 187 | 188 | * [Best Paper Awards in Computer Science](https://jeffhuang.com/best_paper_awards/) ,从 1996 年以来,获奖的计算机科学方面的论文收集。 189 | 190 | * [Google Scholar](https://scholar.google.com/citations?view_op=top_venues&hl=en&vq=eng) ,Google 学术搜索(英语:Google Scholar)是一个可以免费搜索学术文章的网络搜索引擎,由计算机专家阿努拉格·阿查里雅(Anurag Acharya)开发。2004 年 11 月,Google 第一次发布了 Google 学术搜索的试用版。该项索引包括了世界上绝大部分出版的学术期刊。 191 | 192 | * [Facebook](https://research.fb.com/publications/) ,Facebook 公司的论文。 193 | 194 | * [Research at Google](https://research.google/pubs/) ,Google 发布一些论文。 195 | 196 | * [Microsoft Research](https://www.microsoft.com/en-us/research/search/?from=http%3A%2F%2Fresearch.microsoft.com%2Fapps%2Fcatalog%2Fdefault.aspx%3Ft%3Dpublications) ,微软发布的论文。 197 | 198 | * [MIT’s Artificial Intelligence Lab Publications](http://dspace.mit.edu/handle/1721.1/39813) ,MIT 和人工智能相关的论文。 199 | 200 | * [MIT’s Distributed System’s Reading Group](http://dsrg.pdos.csail.mit.edu/) ,MIT 和分布式系统相关的论文。 201 | 202 | * [arXiv Paper Repository](https://arxiv.org/) ,arXiv 是一个收集物理学、数学、计算机科学与生物学的论文预印本的网站,始于 1991 年 8 月 14 日。截至 2008 年 10 月,arXiv.org 已收集超过 50 万篇预印本。至 2014 年底,藏量达到 1 百万篇。在 2014 年时,约以每月 8000 篇的速度增加。arXiv 的存在是造就科学出版业中所谓开放获取运动的因素之一。现今的一些数学家及科学家习惯先将其论文上传至 arXiv.org,再提交予专业的学术期刊。这个趋势对传统学术期刊的经营模式造成了可观的冲击。 203 | 204 | * [SciRate](https://scirate.com/) ,arXiv 上的论文太多,所以,SciRate 索引了 arXiv 上的一些好评的论文,并供大家评论和打分。([开源代码](https://github.com/scirate/scirate)。) 205 | 206 | * [cat-v.org](http://doc.cat-v.org/) ,这个网站,不只有论文,还有技术手册或是一些有意思的文章,包括一些历史资料什么的。 207 | 208 | * [Usenix: Best Papers](https://www.usenix.org/conferences/best-papers) ,Usenix 上推荐的最佳论文。 209 | 210 | * [The Morning Paper](https://blog.acolyer.org/) ,该博客会每天推送一篇论文,特别棒。 211 | 212 | * [Lobste.rs tagged as PDF](https://lobste.rs/t/pdf) ,Lobsters 是一个聚焦于技术的社区,主要是链接聚合和对话题进行讨论。其中的 PDF 分类可以认为也是一个论文的集散地。 213 | 214 | * [Papers We Love](https://github.com/papers-we-love/papers-we-love) ,GitHub 上的一个近 3 万颗星的计算机科学方面的论文社区。 215 | 216 | ## 小结 217 | 218 | 总结一下今天的内容。这篇文章我主要跟你分享了一些好的学习资源,帮你开拓眼界,为后续学习夯实基础。 219 | 220 | 首先,我推荐了 Coding Horror、Joel on Software、Clean Coder Blog、Martin Fowler、Paul Graham Essays 等多个知名的个人技术博客。然后分享了一些我认为还不错的和编程相关的 YouTube 频道,比如 Coding Tech、Amazon Web Services、Facebook Developers、Google Developer 等。 221 | 222 | 随后是 Airbnb、AWS、Cloudera、Dropbox、Facebook、Google 等各个公司的技术博客,跟随这些公司的博客,你不但可以看到这些公司的工程技术,还能掌握到一些技术方向和趋势。最后,想成长为一个高手,论文是一定要读的。所以,我给出了一个非常不错的计算机方面的论文集散地,并推荐了一些学习资源来教你如何读这些论文。 223 | 224 | 我一直认为,学习需要自我驱动,要学会自己“找食物”,而不是“等着喂”。程序员练级攻略 2018 版到今天就全部更新完成了,但我认为,这其实只是技术练级的起点,还有很多知识和技术,需要我们不断地去探索和发现。加油,我能做到的,你一定也可以做到。 225 | 226 | -------------------------------------------------------------------------------- /23_编程范式游记-起源/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(1)- 起源 2 | 3 | ## 序 4 | 5 | 现在很多的文章和演讲都在谈架构,很少有人再会谈及编程范式。然而, 这些基础性和本质性的话题,却是非常非常重要的。 6 | 7 | 一方面,我发现在一些语言争论上,有很多人对编程语言的认识其实并不深;另一方面,通过编程语言的范式,我们不但可以知道整个编程语言的发展史,而且还能提高自己的编程技能,写出更好的代码。 8 | 9 | **我希望通过一系列的文章带大家漫游一下各式各样的编程范式**。 10 | 11 | 这一经历可能有些漫长,途中也会有各式各样的语言的代码。但是我保证这一历程对于一个程序员来说是非常有价值的,因为你不但可以对主流编程语言的一些特性有所了解,而且当我们到达终点的时候,你还能了解到编程的本质是什么。 12 | 13 | 这一系列文章中有各种语言的代码,其中有 C、C++、Python、Java、Scheme、Go、JavaScript、Prolog 等。所以,如果要能跟上本文的前因后果,你要对这几门比较主流的语言多少有些了解。 14 | 15 | 而且,你需要在一线编写一段时间(大概 5 年以上吧)的代码,可能才能体会到这一系列文章的内涵。 16 | 17 | 我根据每篇文章中所讲述的内容,将这一系列文章分为四个部分。 18 | 19 | * **第一部分:泛型编程**,第 1~3 章,讨论了从 C 到 C++ 的泛型编程方法,并系统地总结了编程语言中的类型系统和泛型编程的本质。 20 | 21 | * **第二部分:函数式编程**,第 4 章和第 5 章,讲述了函数式编程用到的技术,及其思维方式,并通过 Python 和 Go 修饰器的例子,展示了函数式编程下的代码扩展能力,以及函数的相互和随意拼装带来的好处。 22 | 23 | * **第三部分:面向对象编程**,第 6~8 章,讲述与传统的编程思想的相反之处,面向对象设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,列举了面向对象编程的优缺点,基于原型的编程范式,以及 Go 语言的委托模式。 24 | 25 | * **第四部分:编程本质和逻辑编程**,第 9~11 章,先探讨了编程的本质:逻辑部分才是真正有意义的,控制部分只能影响逻辑部分的效率,然后结合 Prolog 语言介绍了逻辑编程范式,最后对程序世界里的编程范式进行了总结,对比了它们之间的不同。 26 | 27 | 我会以每部分为一个发布单元,将这些文章陆续发表在专栏中。如果在编程范式方面,你有其他感兴趣的主题,欢迎留言给我。 28 | 29 | 下面我们来说说什么是编程范式。编程范式的英语是 Programming Paradigm,范即模范之意,范式即模式、方法,是一类典型的编程风格,是指从事软件工程的一类典型的风格(可以对照“方法学”一词)。 30 | 31 | 编程语言发展到今天,出现了好多不同的代码编写方式,但不同的方式解决的都是同一个问题,那就是如何写出更为通用、更具可重用性的代码或模块。 32 | 33 | 如果你准备好了,就和我一起来吧。 34 | 35 | ## 先从 C 语言开始 36 | 37 | 为了讲清楚这个问题,我需要从 C 语言开始讲起。因为 C 语言历史悠久,而几乎现在看到的所有编程语言都是以 C 语言为基础来拓展的,不管是 C++、Java、C#、Go、Python、PHP、Perl、JavaScript、Lua,还是 Shell。 38 | 39 | 自 C 语言问世 40 多年以来,其影响了太多太多的编程语言,到现在还一直被广泛使用,不得不佩服它的生命力。但是,我们也要清楚地知道,大多数 C Like 编程语言其实都是在改善 C 语言带来的问题。 40 | 41 | 那 C 语言有哪些特性呢?我简单来总结下: 42 | 43 | 1. C 语言是一个静态弱类型语言,在使用变量时需要声明变量类型,但是类型间可以有隐式转换; 44 | 2. 不同的变量类型可以用结构体(struct)组合在一起,以此来声明新的数据类型; 45 | 3. C 语言可以用 typedef 关键字来定义类型的别名,以此来达到变量类型的抽象; 46 | 4. C 语言是一个有结构化程序设计、具有变量作用域以及递归功能的过程式语言; 47 | 5. C 语言传递参数一般是以值传递,也可以传递指针; 48 | 6. 通过指针,C 语言可以容易地对内存进行低级控制,然而这加大了编程复杂度; 49 | 7. 编译预处理让 C 语言的编译更具有弹性,比如跨平台。 50 | 51 | C 语言的这些特性,可以让程序员在微观层面写出非常精细和精确的编程操作,让程序员可以在底层和系统细节上非常自由、灵活和精准地控制代码。 52 | 53 | 然而,在代码组织和功能编程上,C 语言的上述特性,却不那么美妙了。 54 | 55 | ## 从 C 语言的一个简单例子说起 56 | 57 | 我们从 C 语言最简单的交换两个变量的 swap 函数说起,参看下面的代码: 58 | 59 | ```c 60 | void swap(int* x, int* y) 61 | { 62 | int tmp = *x; 63 | *x = *y; 64 | *y = tmp; 65 | } 66 | ``` 67 | 68 | 你可以想一想,这里为什么要传指针?这里是 C 语言指针,因为如果你不用指针的话,那么参数变成传值,即函数的形参是调用实参的一个拷贝,函数里面对形参的修改无法影响实参的结果。为了要达到调用完函数后,实参内容的交换,必须要把实参的地址传递进来,也就是传指针。这样在函数里面做交换,实际变量的值也被交换了。 69 | 70 | 然而,这个函数最大的问题就是它只能给 int 值用,这个世界上还有很多类型包括 double、float,这就是静态语言最糟糕的一个问题。 71 | 72 | ## 数据类型与现实世界的类比 73 | 74 | 与现实世界类比一下,数据类型就好像螺帽一样,有多种接口方式:平口的、十字的、六角的等,而螺丝刀就像是函数,或是用来操作这些螺丝的算法或代码。我们发现,这些不同类型的螺帽(数据类型),需要我们为之适配一堆不同的螺丝刀。 75 | 76 | 而且它们还有不同的尺寸(尺寸就代表它是单字节的,还是多字节的,比如整型的 int、long,浮点数的 float 和 double),这样复杂度一下就提高了,最终导致电工(程序员)工作的时候需要带下图这样的一堆工具。 77 | 78 | ![](/Users/likejun/ProgrammersLevelUp/23_编程范式游记-起源/pic_1.png) 79 | 80 | 这就是类型为编程带来的问题。要解决这个问题,我们还是来看一下现实世界。 81 | 82 | 你应该见过下面图片中的这种经过优化的螺丝刀,上面手柄是一样的,拧螺丝的动作也是一样的,只是接口不一样。每次我看到这张图片的时候就在想,这密密麻麻的看着有 40 多种接口,不知道为什么人类世界要干出这么多的花样,你们这群人类究竟是要干什么啊。 83 | 84 | ![](/Users/likejun/ProgrammersLevelUp/23_编程范式游记-起源/pic_2.png) 85 | 86 | 我们可以看到,无论是传统世界,还是编程世界,我们都在干一件事情,什么事呢?**那就是通过使用一种更为通用的方式,用另外的话说就是抽象和隔离,让复杂的“世界”变得简单一些**。 87 | 88 | 然而,要做到抽象,对于 C 语言这样的类型语言来说,首先要拿出来讲的就是抽象类型,这就是所谓的泛型编程。 89 | 90 | 另外,我们还要注意到,在编程世界里,对于 C 语言来说,类型还可以转换。编译器会使用一切方式来做类型转换,因为类型转换有时候可以让我们编程更方便一些,也让相近的类型可以做到一点点的泛型。 91 | 92 | 然而,对于 C 语言的类型转换,是会出很多问题的。比如说,传给我一个数组,这个数组本来是 double 型的,或者是 long 型 64 位的,但是如果把数组类型强转成 int,那么就会出现很多问题,因为这会导致程序遍历数组的步长不一样了。 93 | 94 | 比如:一个 double a[10] 的数组,a[2] 意味着 a + sizeof(double) * 2。如果你把 a 强转成 int,那么 a[2] 就意味着 a + sizeof(int) * 2。我们知道 sizeof(double) 是 8,而 sizeof(int) 是 4。于是访问到了不同的地址和内存空间,这就导致程序出现严重的问题。 95 | 96 | ## C 语言的泛型 97 | 98 | ### 一个泛型的示例 - swap 函数 99 | 100 | 好了,我们再看下,C 语言是如何实现泛型的。C 语言的类型泛型基本上来说就是使用void * 关键字或是使用宏定义。 101 | 102 | 下面是一个使用了void*泛型版本的 swap 函数。 103 | 104 | ```c 105 | void swap(void* x, void* y, size_t size) 106 | { 107 | char tmp[size]; 108 | memcpy(tmp, y, size); 109 | memcpy(y, x, size); 110 | memcpy(x, tmp, size); 111 | } 112 | ``` 113 | 114 | 上面这个函数几乎完全改变了 int 版的函数的实现方式,这个实现方式有三个重点: 115 | 116 | * **函数接口中增加了一个size参数**。为什么要这么干呢?因为,用了 void* 后,类型被“抽象”掉了,编译器不能通过类型得到类型的尺寸了,所以,需要我们手动地加上一个类型长度的标识。 117 | * **函数的实现中使用了memcpy()函数**。为什么要这样干呢?还是因为类型被“抽象”掉了,所以不能用赋值表达式了,很有可能传进来的参数类型还是一个结构体,因此,为了要交换这些复杂类型的值,我们只能使用内存复制的方法了。 118 | * **函数的实现中使用了一个temp[size]数组**。这就是交换数据时需要用的 buffer,用 buffer 来做临时的空间存储。 119 | 120 | 于是,新增的size参数,使用的memcpy内存拷贝以及一个 buffer,这增加了编程的复杂度。这就是 C 语言的类型抽象所带来的复杂度的提升。 121 | 122 | 在提升复杂度的同时,我们发现还有问题,比如,我们想交换两个字符串数组,类型是char* ,那么,我的swap()函数的x和y参数是不是要用void * * 了?这样一来,接口就没法定义了。 123 | 124 | 除了使用 void* 来做泛型,在 C 语言中,还可以用宏定义来做泛型,如下所示: 125 | 126 | ```c 127 | #define swap(x, y, size) {\ 128 | char temp[size]; \ 129 | memcpy(temp, &y, size); \ 130 | memcpy(&y, &x, size); \ 131 | memcpy(&x, temp, size); \ 132 | } 133 | ``` 134 | 135 | 但用宏带来的问题就是编译器做字符串替换,因为宏是做字符串替换,所以会导致代码膨胀,导致编译出的执行文件比较大。不过对于 swap 这个简单的函数来说,用void*和宏替换来说都可以达到泛型。 136 | 137 | 但是,如果我们不是 swap,而是 min() 或 max() 函数,那么宏替换的问题就会暴露得更多一些。比如,对于下面的这个宏: 138 | 139 | ```c 140 | #define min(x, y) ((x)>(y) ? (y) : (x)) 141 | ``` 142 | 143 | 其中一个最大的问题,就是有可能会有**重复执行**的问题。如: 144 | 145 | * min(i++, j++) 对于这个案例来说,我们本意是比较完后,对变量做累加,但是,因为宏替换的缘故,这会导致变量i或j被累加两次。 146 | * min(foo(), bar()) 对于这个示例来说,我们本意是比较 foo() 和 bar() 函数的返回值,然而,经过宏替换后,foo() 或 bar() 会被调用两次,这会带来很多问题。 147 | 148 | 另外,你会不会觉得无论是用哪种方式,这种“泛型”是不是太宽松了一些,完全不做类型检查,就是在内存上对拷,直接操作内存的这种方式,感觉是不是比较危险,而且就像一个定时炸弹一样,不知道什么时候,在什么条件下就爆炸了。 149 | 150 | 从上面的两个例子,我们可以发现,无论哪种方式,接口都变得复杂了——加入了size,因为如果不加入size的话,那么我们的函数内部就需要自己检查size。然而,void* 这种地址的方式是没法得到size的。 151 | 152 | 而宏定义的那种方式,虽然不会把类型给隐藏掉,可以使用像 sizeof(x) 这样的方式得到 size。但是如果类型是 char*,那么,使用sizeof方式只能提到指针类型的size,而不是值的size。另外,对于不同的类型,比如说double和int,那应该用谁的size呢?是不是先转一下型呢?这些都是问题。 153 | 154 | 于是,这种泛型,让我们根本没有办法检查传入参数的size,导致我们只能增加接口复杂度,加入一个size参数,然后把这个问题抛给调用者了。 155 | 156 | ### 一个更为复杂的泛型示例 - Search 函数 157 | 158 | 如果我们把这个事情变得更复杂,写个search函数,再传一个int数组,然后想搜索target,搜到返回数组下标,搜不到返回-1。 159 | 160 | ```c 161 | int search(int* a, size_t size, int target) { 162 | for(int i=0; iname, y->name); 220 | if (n != 0) return n; 221 | return strcmp(x->id, y->id); 222 | } 223 | ``` 224 | 225 | 我们的 C 语言干成这个样子,看上去还行,但是,上面的这个search函数只能用于数组这样的顺序型的数据容器(数据结构)。如果这个search函数能支持一些非顺序型的数据容器(数据结构),比如:堆、栈、哈希表、树、图。那么,用 C 语言来干基本上干不下去了,对于像search()这样的算法来说,数据类型的自适应问题就已经把事情搞得很复杂了。然而,数据结构的自适应就会把这个事的复杂度搞上几个数量级。 226 | 227 | ## 小结 228 | 229 | 这里,如果说,程序 = 算法 + 数据,我觉得 C 语言会有这几个问题: 230 | 231 | 1. 一个通用的算法,需要对所处理的数据的数据类型进行适配。但在适配数据类型的过程中,C 语言只能使用 void* 或 宏替换的方式,这两种方式导致了类型过于宽松,并带来很多其它问题。 232 | 233 | 2. 适配数据类型,需要 C 语言在泛型中加入一个类型的 size,这是因为我们识别不了被泛型后的数据类型,而 C 语言没有运行时的类型识别,所以,只能将这个工作抛给调用泛型算法的程序员来做了。 234 | 235 | 3. 算法其实是在操作数据结构,而数据则是放到数据结构中的,所以,真正的泛型除了适配数据类型外,还要适配数据结构,最后这个事情导致泛型算法的复杂急剧上升。比如容器内存的分配和释放,不同的数据体可能有非常不一样的内存分配和释放模型;再比如对象之间的复制,要把它存进来我需要有一个复制,这其中又涉及到是深拷贝,还是浅拷贝。 236 | 237 | 4. 最后,在实现泛型算法的时候,你会发现自己在纠结哪些东西应该抛给调用者处理,哪些又是可以封装起来。如何平衡和选择,并没有定论,也不好解决。 238 | 239 | 总体来说,C 语言设计目标是提供一种能以简易的方式编译、处理低层内存、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C 语言也很适合搭配汇编语言来使用。C 语言把非常底层的控制权交给了程序员,它设计的理念是: 240 | 241 | * 相信程序员; 242 | 243 | * 不会阻止程序员做任何底层的事; 244 | 245 | * 保持语言的最小和最简的特性; 246 | 247 | * 保证 C 语言的最快的运行速度,那怕牺牲移值性。 248 | 249 | 从某种角度上来说,C 语言的伟大之处在于——**使用 C 语言的程序员在高级语言的特性之上还能简单地做任何底层上的微观控制**。这是 C 语言的强大和优雅之处。也有人说,C 语言是高级语言中的汇编语言。 250 | 251 | 不过,这只是在针对底层指令控制和过程式的编程方式。而对于更高阶、更为抽象的编程模型来说,C 语言这种基于过程和底层的初衷设计方式就会成为它的短板。因为,在编程这个世界中,更多的编程工作是解决业务上的问题,而不是计算机的问题,所以,我们需要更为贴近业务、更为抽象的语言。 252 | 253 | 说到这里,我想你会问,那 C 语言本会怎么去解决这些问题呢?简单点说,C 语言并没有解决这些问题,所以才有了后面的 C++ 等其他语言,下一篇文章中,我也会和你聊聊 C++ 是如何解决这些问题的。 254 | 255 | C 语言诞生于 1972 年,到现在已经有 45 年的历史,在它之后,C++、Java、C# 等语言前仆后继,一浪高过一浪,都在试图解决那个时代的那个特定问题。我们不能去否定某个语言,但可以确定的是,随着历史的发展,每一门语言都还在默默迭代,不断优化和更新。同时,也会有很多新的编程语言带着新的闪光耀眼的特性出现在我们面前。 256 | 257 | 再回过头来说,编程范式其实就是程序的指导思想,它也代表了这门语言的设计方向,我们并不能说哪种范式更为超前,只能说各有千秋。 258 | 259 | 比如 C 语言就是过程式的编程语言,像 C 语言这样的过程式编程语言优点是底层灵活而且高效,特别适合开发运行较快且对系统资源利用率要求较高的程序,但我上面抛出的问题它在后来也没有试图去解决,因为编程范式的选择基本已经决定了它的“命运”。 260 | 261 | 我们怎么解决上述 C 语言没有解决好的问题呢?请期待接下来的文章。 262 | 263 | -------------------------------------------------------------------------------- /23_编程范式游记-起源/pic_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/23_编程范式游记-起源/pic_1.png -------------------------------------------------------------------------------- /23_编程范式游记-起源/pic_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/23_编程范式游记-起源/pic_2.png -------------------------------------------------------------------------------- /24_编程范式游记-泛型编程/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(2)- 泛型编程 2 | 3 | 上一篇文章中,我从 C 语言开始说起,聊了聊面向过程式的编程范式,相信从代码的角度你对这类型的语言已经有了一些理解。作为一门高级语言,C 语言绝对是编程语言历史发展中的一个重要里程碑,但随着认知的升级,面向过程的 C 语言已经无法满足更高层次的编程的需要。于是,C++ 出现了。 4 | 5 | ## C++ 语言 6 | 7 | 1980 年,AT&T 贝尔实验室的 **Bjarne Stroustrup** 创建的 C++ 语言横空出世,它既可以全面兼容 C 语言,又巧妙揉和了一些面向对象的编程理念。现在来看,不得不佩服 Stroustrup 的魄力。在这里,我也向你推荐一本书,书名是《C++ 语言的设计和演化》。 8 | 9 | 这本书系统介绍了 C++ 诞生的背景以及初衷,书的作者就是[Stroustrup](https://book.douban.com/author/362072/)本人,所以你可以非常详细地从语言创建者的角度了解他的设计思路和创新之旅。当然,就是在今天,C++ 这门语言也还有很多争议,这里我不细说。如果你感兴趣的话,可以看看我几年前在酷壳上发表的文章《[C++ 的坑真的多吗?](https://coolshell.cn/articles/7992.html)》。 10 | 11 | 从语言角度来说,实际上早期 C++ 的许多工作是对 C 的强化和净化,并把完全兼容 C 作为强制性要求(这也是 C++ 复杂晦涩的原因,这点 Java 就干得比 C++ 彻底得多)。在 C89、C99 这两个 C 语言的标准中,有许多改进都是从 C++ 中引进的。 12 | 13 | 可见,C++ 对 C 语言的贡献非常之大。是的,因为 C++ 很大程度就是用来解决 C 语言中的各种问题和各种不方便的。比如: 14 | 15 | * 用引用来解决指针的问题。 16 | 17 | * 用 namespace 来解决名字空间冲突的问题。 18 | 19 | * 通过 try-catch 来解决检查返回值编程的问题。 20 | 21 | * 用 class 来解决对象的创建、复制、销毁的问题,从而可以达到在结构体嵌套时可以深度复制的内存安全问题。 22 | 23 | * 通过重载操作符来达到操作上的泛型。(比如,消除《01 | 编程范式游记:起源》中提到的比较函数cmpFn,再比如用>>操作符消除printf()的数据类型不够泛型的问题。) 24 | 25 | * 通过模板 template 和虚函数的多态以及运行时识别来达到更高层次的泛型和多态。 26 | 27 | * 用 RAII、智能指针的方式,解决了 C 语言中因为需要释放资源而出现的那些非常 ugly 也很容易出错的代码的问题。 28 | 29 | * 用 STL 解决了 C 语言中算法和数据结构的 N 多种坑。 30 | 31 | ## C++ 泛型编程 32 | 33 | C++ 是支持编程范式最多的一门语言,它虽然解决了很多 C 语言的问题,但我个人觉得它最大的意义是解决了 C 语言泛型编程的问题。因为,我们可以看到一些 C++ 的标准规格说明书里,有一半以上都在说明 STL 的标准规格应该是什么样的,这说明泛型编程是 C++ 重点中的重点。 34 | 35 | 理想情况下,算法应是和数据结构以及类型无关的,各种特殊的数据类型理应做好自己分内的工作,算法只关心一个标准的实现。**而对于泛型的抽象,我们需要回答的问题是,如果我们的数据类型符合通用算法,那么对数据类型的最小需求又是什么呢**? 36 | 37 | 我们来看看 C++ 是如何有效解决程序泛型问题的,我认为有三点。 38 | 39 | **第一,它通过类的方式来解决**。 40 | 41 | * 类里面会有构造函数、析构函数表示这个类的分配和释放。 42 | 43 | * 还有它的拷贝构造函数,表示了对内存的复制。 44 | 45 | * 还有重载操作符,像我们要去比较大于、等于、不等于。 46 | 47 | 这样可以让一个用户自定义的数据类型和内建的那些数据类型就很一致了。 48 | 49 | **第二,通过模板达到类型和算法的妥协**。 50 | 51 | * 模板有点像 DSL,模板的特化会根据使用者的类型在编译时期生成那个模板的代码。 52 | 53 | * 模板可以通过一个虚拟类型来做类型绑定,这样不会导致类型转换时的问题。 54 | 55 | 模板很好地取代了 C 时代宏定义带来的问题。 56 | 57 | **第三,通过虚函数和运行时类型识别**。 58 | 59 | * 虚函数带来的多态在语义上可以支持“同一类”的类型泛型。 60 | 61 | * 运行时类型识别技术可以做到在泛型时对具体类型的特殊处理。 62 | 63 | 这样一来,就可以写出基于抽象接口的泛型。 64 | 65 | 拥有了这些 C++ 引入的技术,我们就可以做到 C 语言很难做到的泛型编程了。 66 | 67 | 正如前面说过的,一个良好的泛型编程需要解决如下几个泛型编程的问题: 68 | 69 | 1. 算法的泛型; 70 | 71 | 2. 类型的泛型; 72 | 73 | 3. 数据结构(数据容器)的泛型。 74 | 75 | ### C++ 泛型编程的示例 - Search 函数 76 | 77 | 就像前面的search()函数,里面的 for(int i=0; i 114 | Iter search(Iter pStart, Iter pEnd, T target) 115 | { 116 | for(Iter p = pStart; p != pEnd; p++) { 117 | if ( *p == target ) 118 | return p; 119 | } 120 | return NULL; 121 | } 122 | ``` 123 | 124 | 在 C++ 的泛型版本中,我们可以看到: 125 | 126 | * 使用typename T抽象了数据结构中存储数据的类型。 127 | * 使用typename Iter,这是不同的数据结构需要自己实现的“迭代器”,这样也就抽象掉了不同类型的数据结构。 128 | * 然后,我们对数据容器的遍历使用了Iter中的++方法,这是数据容器需要重载的操作符,这样通过操作符重载也就泛型掉了遍历。 129 | * 在函数的入参上使用了pStart和pEnd来表示遍历的起止。 130 | * 使用*Iter来取得这个“指针”的内容。这也是通过重载 * 取值操作符来达到的泛型。 131 | 132 | 当然,你可能会问,为什么我们不用标准接口Iter.Next()取代++, 用Iter.GetValue()来取代*,而是通过重载操作符?其实这样做是为了兼容原有 C 语言的编程习惯。 133 | 134 | 说明一下,所谓的Iter,在实际代码中,就是像vector::iterator或map::iterator这样的东西。这是由相应的数据容器来实现和提供的。 135 | 136 | 注:下面是 C++ STL 中的find()函数的代码。 137 | 138 | ```c++ 139 | template 140 | InputIterator find (InputIterator first, InputIterator last, const T& val) 141 | { 142 | while (first!=last) { 143 | if (*first==val) return first; 144 | ++first; 145 | } 146 | return last; 147 | } 148 | ``` 149 | 150 | ### C++ 泛型编程示例 - Sum 函数 151 | 152 | 也许你觉得到这一步,我们的泛型设计就完成了。其实,还远远不够。search函数只是一个开始,我们还有很多别的算法会让问题变得更为复杂。 153 | 154 | 我们再来看一个sum()函数。 155 | 156 | 先看 C 语言版: 157 | 158 | ```c 159 | long sum(int *a, size_t size) { 160 | long result = 0; 161 | for(int i=0; i 172 | T sum(Iter pStart, Iter pEnd) { 173 | T result = 0; 174 | for(Iter p=pStart; p!=pEnd; p++) { 175 | result += *p; 176 | } 177 | return result; 178 | } 179 | ``` 180 | 181 | 你看到了什么样的问题?这个代码中最大的问题就是 T result = 0; 这条语句: 182 | 183 | * 那个0假设了类型是int; 184 | 185 | * 那个T假设了 Iter 中出来的类型是T。 186 | 187 | 这样的假设是有问题的,如果类型不一样,就会导致转型的问题,这会带来非常 buggy 的代码。那么,我们怎么解决呢? 188 | 189 | ### C++ 泛型编程的重要技术 - 迭代器 190 | 191 | 我们知道Iter在实际调用者那会是一个具体的像vector::iterator这样的东西。在这个声明中,int已经被传入Iter中了。所以,定义result的T应该可以从Iter中来。这样就可以保证类型是一样的,而且不会有被转型的问题。 192 | 193 | 所以,我们需要精心地实现一个“迭代器”。下面是一个“精简版”的迭代器(我没有把 C++ STL 代码里的迭代器列出来,是因为代码太多太复杂,我这里只是为了说明问题)。 194 | 195 | ```c++ 196 | template 197 | class container { 198 | public: 199 | class iterator { 200 | public: 201 | typedef iterator self_type; 202 | typedef T value_type; 203 | typedef T* pointer; 204 | typedef T& reference; 205 | 206 | reference operator*(); 207 | pointer operator->(); 208 | bool operator==(const self_type& rhs); 209 | bool operator!=(const self_type& rhs); 210 | self_type operator++() { self_type i = *this; ptr_++; return i; } 211 | self_type operator++(int junk) { ptr_++; return *this; } 212 | ... 213 | ... 214 | private: 215 | pointer _ptr; 216 | }; 217 | 218 | iterator begin(); 219 | iterator end(); 220 | ... 221 | ... 222 | }; 223 | ``` 224 | 225 | 上面的代码是我写的一个迭代器(这个迭代器在语义上是没有问题的),我没有把所有的代码列出来,而把它的一些基本思路列了出来。这里我说明一下几个关键点。 226 | 227 | * 首先,一个迭代器需要和一个容器在一起,因为里面是对这个容器的具体的代码实现。 228 | 229 | * 它需要重载一些操作符,比如:取值操作* 、成员操作->、比较操作==和!=,还有遍历操作++,等等。 230 | 231 | * 然后,还要typedef一些类型,比如value_type,告诉我们容器内的数据的实际类型是什么样子。 232 | 233 | * 还有一些,如begin()和end()的基本操作。 234 | 235 | * 我们还可以看到其中有一个pointer _ptr的内部指针来指向当前的数据(注意,pointer就是 T*)。 236 | 237 | 好了,有了这个迭代器后,我们还要解决T result = 0后面的这个0的问题。这个事,算法没有办法搞定,最好由用户传入。于是出现了下面最终泛型的sum()版函数。 238 | 239 | ```c++ 240 | template 241 | typename Iter::value_type 242 | sum(Iter start, Iter end, T init) { 243 | typename Iter::value_type result = init; 244 | while (start != end) { 245 | result = result + *start; 246 | start++; 247 | } 248 | return result; 249 | } 250 | ``` 251 | 252 | 我们可以看到typename Iter::value_type result = init这条语句是关键。我们解决了所有的问题。 253 | 254 | 我们如下使用: 255 | 256 | ```c++ 257 | container c; 258 | container::iterator it = c.begin(); 259 | sum(c.begin(), c.end(), 0); 260 | ``` 261 | 262 | 这就是整个 STL 的泛型方法,其中包括: 263 | 264 | * 泛型的数据容器; 265 | 266 | * 泛型数据容器的迭代器; 267 | 268 | * 然后泛型的算法就很容易写了。 269 | 270 | ### 需要更多的抽象 271 | 272 | #### 更为复杂的需求 273 | 274 | 但是,还能不能做到更为泛型呢?比如:如果我们有这样的一个数据结构 Employee,里面有 vacation 就是休假多少天,以及工资。 275 | 276 | ```c++ 277 | struct Employee { 278 | string name; 279 | string id; 280 | int vacation; 281 | double salary; 282 | }; 283 | ``` 284 | 285 | 现在我想计算员工的总薪水,或是总休假天数。 286 | 287 | ```c++ 288 | vector staff; 289 | //total salary or total vacation days? 290 | sum(staff.begin(), staff.end(), 0); 291 | ``` 292 | 293 | 我们的sum完全不知道怎么搞了,因为要累加的是Employee类中的不同字段,即便我们的 Employee 中重载了+操作,也不知道要加哪个字段。 294 | 295 | 另外,我们可能还会有:求平均值 average,求最小值 min,求最大值 max,求中位数 mean 等等。你会发现,算法写出来基本上都是一样的,只是其中的“累加”操作变成了另外一个操作。就这个例子而言,我想计算员工薪水里面最高的,和休假最少的,或者我想计算全部员工的总共休假多少天。那么面对这么多的需求,我们是否可以泛型一些呢?怎样解决这些问题呢? 296 | 297 | #### 更高维度的抽象 298 | 299 | 要解决这个问题,我希望我的这个算法只管遍历,具体要干什么,那是业务逻辑,由外面的调用方来定义我就好了,和我无关。这样一来,代码的重用度就更高了。 300 | 301 | 下面是一个抽象度更高的版本,这个版本再叫sum就不太合适了。这个版本应该是reduce——用于把一个数组 reduce 成一个值。 302 | 303 | ```c++ 304 | template 305 | T reduce (Iter start, Iter end, T init, Op op) { 306 | T result = init; 307 | while ( start != end ) { 308 | result = op( result, *start ); 309 | start++; 310 | } 311 | return result; 312 | } 313 | 314 | ``` 315 | 316 | 上面的代码中,我们需要传一个函数进来。在 STL 中,它是个函数对象,我们还是这套算法,但是 result 不是像前面那样去加,是把整个迭代器值给你一个 operation,然后由它来做。我把这个方法又拿出去了,所以就会变成这个样子。 317 | 318 | 在 C++ STL 中,与我的这个 reduce 函数对应的函数名叫 accumulate(),其实际代码有两个版本。 319 | 320 | 第一个版本就是上面的版本,只不过是用for语句而不是while。 321 | 322 | ```c++ 323 | template 324 | T accumulate(InputIt first, InputIt last, T init) 325 | { 326 | for (; first != last; ++first) { 327 | init = init + *first; 328 | } 329 | return init; 330 | } 331 | ``` 332 | 333 | 第二个版本,更为抽象,因为需要传入一个“二元操作函数”——BinaryOperation op来做 accumulate。accumulate 的语义比 sum 更抽象了。 334 | 335 | ```c++ 336 | template 337 | T accumulate(InputIt first, InputIt last, T init, 338 | BinaryOperation op) 339 | { 340 | for (; first != last; ++first) { 341 | init = op(init, *first); 342 | } 343 | return init; 344 | } 345 | ``` 346 | 347 | 来看看我们在使用中是什么样子的: 348 | 349 | ```c++ 350 | double sum_salaries = 351 | reduce( staff.begin(), staff.end(), 0.0, 352 | [](double s, Employee e) 353 | {return s + e.salary;} ); 354 | 355 | double max_salary = 356 | reduce( staff.begin(), staff.end(), 0.0, 357 | [](double s, Employee e) 358 | {return s > e.salary? s: e.salary; } ); 359 | ``` 360 | 361 | 注意:我这里用了 C++ 的 lambda 表达式。 362 | 363 | 你可以很清楚地看到,reduce 这个函数就更通用了,具体要干什么样的事情呢?放在匿名函数里面,它会定义我,我只做一个 reduce。更抽象地来说,我就把一个数组,一个集合,变成一个值。怎么变成一个值呢?由这个函数来决定。 364 | 365 | ### Reduce 函数 366 | 367 | 我们来看看如何使用 reduce 和其它函数完成一个更为复杂的功能。 368 | 369 | 下面这个示例中,我先定义了一个函数对象counter。这个函数对象需要一个Cond的函数对象,它是个条件判断函数,如果满足条件,则加 1,否则加 0。 370 | 371 | ```c++ 372 | template 373 | struct counter { 374 | size_t operator()(size_t c, T t) const { 375 | return c + (Cond(t) ? 1 : 0); 376 | } 377 | }; 378 | ``` 379 | 380 | 然后,我用上面的counter函数对象和reduce函数共同来打造一个counter_if算法(当条件满足的时候我就记个数,也就是统计满足某个条件的个数),我们可以看到,就是一行代码的事。 381 | 382 | ```c++ 383 | template 384 | size_t count_if(Iter begin, Iter end, Cond c){ 385 | return reduce(begin, end, 0, 386 | counter(c)); 387 | } 388 | ``` 389 | 390 | 至于是什么样的条件,这个属于业务逻辑,不是我的流程控制,所以,这应该交给使用方。于是,当我需要统计薪资超过 1 万元的员工的数量时,一行代码就完成了。 391 | 392 | ```c++ 393 | size_t cnt = count_if(staff.begin(), staff.end(), 394 | [](Employee e){ return e.salary > 10000; }); 395 | ``` 396 | 397 | Reduce 时可以只对结构体中的某些值做 Reduce,比如说只对 salary>10000 的人做,只选出这个里面的值,它用 Reduce 就可以达到这步,只要传不同的方式给它,你就可以又造出一个新的东西出来。 398 | 399 | 说着说着,就到了函数式编程。函数式编程里面,我们可以用很多的像 reduce 这样的函数来完成更多的像 STL 里面的count_if()这样有具体意义的函数。关于函数式编程,我们会在后面继续具体聊。 400 | 401 | ## 小结 402 | 403 | 在这篇文章中,我们聊到 C++ 语言是如何通过泛型来解决 C 语言遇到的问题,其实这里面主要就是泛型编程和函数式编程的基本方法相关的细节,虽然解决编程语言中类型带来的问题可能有多种方式,不一定就是 C++ 这种方式。 404 | 405 | 而我之所以从 C/C++ 开始,目的只是因为 C/C++ 都是比较偏底层的编程语言。从底层的原理上,我们可以更透彻地了解,从 C 到 C++ 的演进这一过程中带来的编程方式的变化。这可以让你看到,在静态类型语言方面解决泛型编程的一些技术和方法,从而感受到其中的奥妙和原理。 406 | 407 | **因为形式是多样的,但是原理是相通的,所以,这个过程会非常有助于你更深刻地了解后面会谈到的更多的编程范式**。 408 | 409 | -------------------------------------------------------------------------------- /25_编程范式游记-类型系统/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(3) - 类型系统和泛型的本质 2 | 3 | 前面,我们讨论了从 C 到 C++ 的泛型编程方法,并且初探了更为抽象的函数式编程。正如在上一篇文章中所说的,泛型编程的方式并不只有 C++ 这一种类型,我们只是通过这个过程了解一下,底层静态类型语言的泛型编程原理。这样能够方便我们继续后面的历程。 4 | 5 | 是的,除了 C++ 那样的泛型,如果你了解其它编程语言一定会发现,在动态类型语言或是某些有语法糖支持的语言中,那个swap() 或 search() 函数的泛型其实可以很简单地就实现了。 6 | 7 | 比如,你甚至可以把swap()函数简单地写成下面这个样子(包括 Go 语言也有这样的语法): 8 | 9 | ```go 10 | b, a = a, b; 11 | ``` 12 | 13 | 在上一篇文章后面的 Reduce 函数中,可以看到,在编程世界中,我们需要处理好两件事: 14 | 15 | * 第一件事是编程语言中的类型问题。 16 | 17 | * 第二件事是对真实世界中业务代码的抽象、重用和拼装。 18 | 19 | 所以,在这篇文章中,我们还是继续深入地讨论上面这两个问题,着重讨论一下编程语言中的类型系统和泛型编程的本质。 20 | 21 | ## 类型系统 22 | 23 | 在计算机科学中,类型系统用于定义如何将编程语言中的数值和表达式归类为许多不同的类型,以及如何操作这些类型,还有这些类型如何互相作用。类型可以确认一个值或者一组值,具有特定的意义和目的。 24 | 25 | 一般来说,编程语言会有两种类型,一种是内建类型,如 int、float 和 char 等,一种是抽象类型,如 struct、class 和 function 等。抽象类型在程序运行中,可能不表示为值。类型系统在各种语言之间有非常大的不同,也许,最主要的差异存在于编译时期的语法,以及运行时期的操作实现方式。 26 | 27 | 编译器可能使用值的静态类型以最优化所需的存储区,并选取对数值运算时的最佳算法。例如,在许多 C 编译器中,“浮点数”数据类型是以 32 比特表示,与 IEEE 754 规格一致的单精度浮点数。因此,在数值运算上,C 应用了浮点数规范(浮点数加法、乘法等)。 28 | 29 | 类型的约束程度以及评估方法,影响了语言的类型。更进一步讲,编程语言可能就类型多态性部分,对每一个类型都对应了一个针对于这个类型的算法运算。类型理论研究类型系统,尽管实际的编程语言类型系统,起源于计算机架构的实际问题、编译器实现,以及语言设计。 30 | 31 | 程序语言的类型系统主要提供如下的功能。 32 | 33 | * **程序语言的安全性**。使用类型可以让编译器侦测一些代码的错误,例如:可以识别出一个错误无效的表达式,如“Hello, World” + 3这样的不同数据类型间操作的问题。强类型语言提供更多的安全性,但是并不能保证绝对的安全。 34 | 35 | * **利于编译器的优化**。 静态类型语言的类型声明,可以让编译器明确地知道程序员的意图。因此,编译器就可以利用这一信息做很多代码优化工作。例如:如果我们指定一个类型是 int ,那么编译就知道,这个类型会以 4 个字节的倍数进行对齐,编译器就可以非常有效地利用更有效率的机器指令。 36 | 37 | * **代码的可读性**。有类型的编程语言,可以让代码更易读和更易维护,代码的语义也更清楚,代码模块的接口(如函数)也更丰富和清楚。 38 | 39 | * **抽象化**。类型允许程序设计者对程序以较高层次的方式思考,而不是烦人的低层次实现。例如,我们使用整型或是浮点型来取代底层的字节实现,我们可以将字符串设计成一个值,而不是底层字节的数组。从高层上来说,类型可以用来定义不同模块间的交互协议,比如函数的入参类型和返回类型,从而可以让接口更有语义,而且不同的模块数据交换更为直观和易懂。 40 | 41 | 但是,正如前面说的,**类型带来的问题就是我们作用于不同类型的代码,虽然长得非常相似,但是由于类型的问题需要根据不同版本写出不同的算法,如果要做到泛型,就需要涉及比较底层的玩法**。 42 | 43 | 对此,这个世界出现了两类语言,一类是静态类型语言,如 C、C++、Java,一种是动态类型语言,如 Python、PHP、JavaScript 等。 44 | 45 | 我们来看一下,一段动态类型语言的代码: 46 | 47 | ```javascript 48 | x = 5; 49 | x = "hello"; 50 | ``` 51 | 52 | 在这个示例中,我们可以看到变量 x 一开始好像是整型,然后又成了字符串型。如果在静态类型的语言中写出这样的代码,那么就会在编译期出错。而在动态类型的语言中,会以类型标记维持程序所有数值的“标记”,并在运算任何数值之前检查标记。所以,一个变量的类型是由运行时的解释器来动态标记的,这样就可以动态地和底层的计算机指令或内存布局对应起来。 53 | 54 | 我们再来看一个示例,对于 JavaScript 这样的动态语言来说可以定义出下面这样的数据结构(一个数组的元素可以是各式各样的类型),这在静态类型的语言中是很难做到的。 55 | 56 | ```javascript 57 | var a = new Array() 58 | a[0] = 2017; 59 | a[1] = "Hello"; 60 | a[2] = {name: "Hao Chen"}; 61 | ``` 62 | 63 | >注:其实,这并不是一个数组,而是一个 key:value。因为动态语言的类型是动态的,所以,key 和 value 的类型都可以随意。比如,对于 a 这个数据结构,还可以写成:a["key"] = "value" 这样的方式。 64 | 65 | 在弱类型或是动态类型的语言中,下面代码的执行会有不确定的结果。 66 | 67 | ```javascript 68 | x = 5; 69 | y = "37"; 70 | z = x + y; 71 | ``` 72 | 73 | * 有的像 Visual Basic 语言,给出的结果是 42:系统将字符串 "37" 转换成数字 37,以匹配运算上的直觉。 74 | 75 | * 而有的像 JavaScript 语言,给出的结果是 "537":系统将数字 5 转换成字符串 "5" 并把两者串接起来。 76 | 77 | * 像 Python 这样的语言,则会产生一个运行时错误。 78 | 79 | 但是,**我们需要清楚地知道,无论哪种程序语言,都避免不了一个特定的类型系统**。哪怕是可随意改变变量类型的动态类型的语言,我们在读代码的过程中也需要脑补某个变量在运行时的类型。 80 | 81 | 所以,每个语言都需要一个类型检查系统。 82 | 83 | * 静态类型检查是在编译器进行语义分析时进行的。如果一个语言强制实行类型规则(即通常只允许以不丢失信息为前提的自动类型转换),那么称此处理为强类型,反之称为弱类型。 84 | 85 | * 动态类型检查系统更多的是在运行时期做动态类型标记和相关检查。所以,动态类型的语言必然要给出一堆诸如:is_array(), is_int(), is_string() 或是 typeof() 这样的运行时类型检查函数。 86 | 87 | 总之,“类型”有时候是一个有用的事,有时候又是一件很讨厌的事情。因为类型是对底层内存布局的一个抽象,会让我们的代码要关注于这些非业务逻辑上的东西。而且,我们的代码需要在不同类型的数据间做处理。但是如果程序语言类型检查得过于严格,那么,我们写出来的代码就不能那么随意。 88 | 89 | 所以,对于静态类型的语言也开了些“小后门”:比如,类型转换,还有 C++、Java 运行时期的类型测试。 90 | 91 | 这些小后门也会带来相当讨厌的问题,比如下面这个 C 语言的示例。 92 | 93 | ```c 94 | int x = 5; 95 | char y[] = "37"; 96 | char* z = x + y; 97 | ``` 98 | 99 | 在上面这个例子中,结果可能和你想的完全不一样。由于 C 语言的底层特性,这个例子中的 z 会指向一个超过 y 地址 5 个字节的内存地址,相当于指向 y 字符串的指针之后的两个空字符处。 100 | 101 | 静态类型语言的支持者和动态类型自由形式的支持者,经常发生争执。前者主张,在编译的时候就可以较早发现错误,而且还可增进运行时期的性能。 102 | 103 | 后者主张,使用更加动态的类型系统,分析代码更为简单,减少出错机会,才能更加轻松快速地编写程序。与此相关的是,后者还主张,考虑到在类型推断的编程语言中,通常不需要手动宣告类型,这部分的额外开销也就自动降低了。 104 | 105 | 在本系列内容的前两篇文章中,我们用 C/C++ 语言来做泛型编程的示例,似乎动态类型语言能够比较好地规避类型导致需要出现多个版本代码的问题,这样可以让我们更好地关注于业务。 106 | 107 | 但是,我们需要清楚地明白,**任何语言都有类型系统**,只是动态类型语言在运行时做类型检查。动态语言的代码复杂度比较低,并可以更容易地关注业务,在某些场景下是对的,但有些情况下却并不见得。 108 | 109 | 比如:在 JavaScript 中,我们需要做一个变量转型的函数,可能会是下面这个样子: 110 | 111 | ```javascript 112 | function ToNumber(x) { 113 | switch(typeof x) { 114 | case "number": return x; 115 | case "undefined": return NaN; 116 | case "boolean": return x ? 1 : 0; 117 | case "string": return Number(x); 118 | case "object": return NaN; 119 | case "function": return NaN; 120 | } 121 | } 122 | ``` 123 | 124 | 我相信,你在动态类型语言的代码中可以看到大量类似 typeof 这样的类型检查代码。是的,这是动态类型带来的另一个问题,就是运行时识别(这个是比较耗性能的)。 125 | 126 | 如果你用过一段时间的动态类型语言,一旦代码量比较大了,我们就会发现,代码中出现“类型问题”而引发整个程序出错的情况实在是太多太多了。而且,这样的出错会让整个程序崩溃掉,太恐怖了。这个时候,我们就很希望提前发现这些类型的问题。 127 | 128 | 静态语言的支持者会说编译器能帮我们找到这些问题,而动态语言的支持者则认为,静态语言的编译器也无法找到所有的问题,想真正提前找到问题只能通过测试来解决。其实他们都对。 129 | 130 | ## 泛型的本质 131 | 132 | 要了解泛型的本质,就需要了解类型的本质。 133 | 134 | * 类型是对内存的一种抽象。不同的类型,会有不同的内存布局和内存分配的策略。 135 | 136 | * 不同的类型,有不同的操作。所以,对于特定的类型,也有特定的一组操作。 137 | 138 | 所以,要做到泛型,我们需要做下面的事情: 139 | 140 | * 标准化掉类型的内存分配、释放和访问。 141 | 142 | * 标准化掉类型的操作。比如:比较操作,I/O 操作,复制操作…… 143 | 144 | * 标准化掉数据容器的操作。比如:查找算法、过滤算法、聚合算法…… 145 | 146 | * 标准化掉类型上特有的操作。需要有标准化的接口来回调不同类型的具体操作…… 147 | 148 | 所以,C++ 动用了非常繁多和复杂的技术来达到泛型编程的目标。 149 | 150 | * 通过类中的构造、析构、拷贝构造,重载赋值操作符,标准化(隐藏)了类型的内存分配、释放和复制的操作。 151 | 152 | * 通过重载操作符,可以标准化类型的比较等操作。 153 | 154 | * 通过 iostream,标准化了类型的输入、输出控制。 155 | 156 | * 通过模板技术(包括模板的特化),来为不同的类型生成类型专属的代码。 157 | 158 | * 通过迭代器来标准化数据容器的遍历操作。 159 | 160 | * 通过面向对象的接口依赖(虚函数技术),来标准化了特定类型在特定算法上的操作。 161 | 162 | * 通过函数式(函数对象),来标准化对于不同类型的特定操作。 163 | 164 | 通过学习 C++,我们可以看到一个比较完整的泛型编程里所涉及的编程范式,这些编程泛式在其它语言中都会或多或少地体现着。比如,JDK 5 引入的泛型类型,就源自 C++ 的模板。 165 | 166 | 泛型编程于 1985 年在论文 Generic Programming 中被这样定义: 167 | 168 | > Generic programming centers around the idea of abstracting from concrete, efficient algorithms to obtain generic algorithms that can be combined with different data representations to produce a wide variety of useful software. 169 | > 170 | > — Musser, David R.; Stepanov, Alexander A., Generic Programming 171 | 172 | 我理解其本质就是 —— **屏蔽掉数据和操作数据的细节,让算法更为通用,让编程者更多地关注算法的结构,而不是在算法中处理不同的数据类型**。 173 | 174 | ## 小结 175 | 176 | 在编程语言中,类型系统的出现主要是对容许混乱的操作加上了严格的限制,以避免代码以无效的数据使用方式编译或运行。例如,整数运算不可用于字符串;指针的操作不可用于整数上,等等。但是,类型的产生和限制,虽然对底层代码来说是安全的,但是对于更高层次的抽象产生了些负面因素。比如在 C++ 语言里,为了同时满足静态类型和抽象,就导致了模板技术的出现,带来了语言的复杂性。 177 | 178 | 我们需要清楚地明白,编程语言本质上帮助程序员屏蔽底层机器代码的实现,而让我们可以更为关注于业务逻辑代码。但是因为,编程语言作为机器代码和业务逻辑的粘合层,是在让程序员可以控制更多底层的灵活性,还是屏蔽底层细节,让程序员可以更多地关注于业务逻辑,这是很难两全需要 trade-off 的事。 179 | 180 | 所以,不同的语言在设计上都会做相应的取舍,比如:C 语言偏向于让程序员可以控制更多的底层细节,而 Java 和 Python 则让程序员更多地关注业务功能的实现。而 C++ 则是两者都想要,导致语言在设计上非常复杂。 -------------------------------------------------------------------------------- /26_编程范式游记-函数式编程/pic_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/26_编程范式游记-函数式编程/pic_1.png -------------------------------------------------------------------------------- /26_编程范式游记-函数式编程/pic_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/26_编程范式游记-函数式编程/pic_2.png -------------------------------------------------------------------------------- /26_编程范式游记-函数式编程/pic_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/26_编程范式游记-函数式编程/pic_3.png -------------------------------------------------------------------------------- /27_编程范式游记-修饰器模式/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(5)- 修饰器模式 2 | 3 | 在上一篇文章中,我们领略了函数式编程的趣味和魅力,主要讲了函数式编程的主要技术。还记得有哪些吗?递归、Map、Reduce、Filter 等,并利用 Python 的 Decorator 和 Generator 功能,将多个函数组合成了管道。 4 | 5 | 此时,你心中可能会有个疑问,这个 decorator 又是怎样工作的呢?这就是本文中要讲述的内容,“Decorator 模式”,又叫“修饰器模式”,或是“装饰器模式”。 6 | 7 | ## Python 的 Decorator 8 | 9 | Python 的 Decorator 在使用上和 Java 的 Annotation(以及 C# 的 Attribute)很相似,就是在方法名前面加一个 @XXX 注解来为这个方法装饰一些东西。但是,Java/C# 的 Annotation 也很让人望而却步,太过于复杂了。你要玩它,需要先了解一堆 Annotation 的类库文档,感觉几乎就是在学另外一门语言。 10 | 11 | 而 Python 使用了一种相对于 Decorator Pattern 和 Annotation 来说非常优雅的方法,这种方法不需要你去掌握什么复杂的 OO 模型或是 Annotation 的各种类库规定,完全就是语言层面的玩法:一种函数式编程的技巧。 12 | 13 | 这是我最喜欢的一个模式了,也是一个挺好玩儿的东西,这个模式动用了函数式编程的一个技术——用一个函数来构造另一个函数。 14 | 15 | 好了,我们先来点感性认识,看一个 Python 修饰器的 Hello World 代码。 16 | 17 | ```python 18 | def hello(fn): 19 | def wrapper(): 20 | print "hello, %s" % fn.__name__ 21 | fn() 22 | print "goodbye, %s" % fn.__name__ 23 | return wrapper 24 | 25 | @hello 26 | def Hao(): 27 | print "i am Hao Chen" 28 | 29 | Hao() 30 | ``` 31 | 32 | 代码的执行结果如下: 33 | 34 | ```shell 35 | $ python hello.py 36 | hello, Hao 37 | i am Hao Chen 38 | goodbye, Hao 39 | ``` 40 | 41 | 你可以看到如下的东西: 42 | 43 | 1. 函数 Hao 前面有个 @hello 的“注解”,hello 就是我们前面定义的函数 hello; 44 | 45 | 2. 在 hello 函数中,其需要一个 fn 的参数(这就是用来做回调的函数); 46 | 47 | 3. hello 函数中返回了一个 inner 函数 wrapper,这个 wrapper函数回调了传进来的 fn,并在回调前后加了两条语句。 48 | 49 | 对于 Python 的这个 @注解语法糖(Syntactic sugar)来说,当你在用某个 @decorator 来修饰某个函数 func 时,如下所示: 50 | 51 | ```python 52 | @decorator 53 | def func(): 54 | pass 55 | ``` 56 | 57 | 其解释器会解释成下面这样的语句: 58 | 59 | ```python 60 | func = decorator(func) 61 | ``` 62 | 63 | 嘿!这不就是把一个函数当参数传到另一个函数中,然后再回调吗?是的。但是,我们需要注意,那里还有一个赋值语句,把 decorator 这个函数的返回值赋值回了原来的 func。 64 | 65 | 我们再来看一个带参数的玩法: 66 | 67 | ```python 68 | def makeHtmlTag(tag, *args, **kwds): 69 | def real_decorator(fn): 70 | css_class = " class='{0}'".format(kwds["css_class"]) \ 71 | if "css_class" in kwds else "" 72 | def wrapped(*args, **kwds): 73 | return "<"+tag+css_class+">" + fn(*args, **kwds) + "" 74 | return wrapped 75 | return real_decorator 76 | 77 | @makeHtmlTag(tag="b", css_class="bold_css") 78 | @makeHtmlTag(tag="i", css_class="italic_css") 79 | def hello(): 80 | return "hello world" 81 | 82 | print hello() 83 | 84 | # 输出: 85 | # hello world 86 | ``` 87 | 88 | 在上面这个例子中,我们可以看到:makeHtmlTag有两个参数。所以,为了让 hello = makeHtmlTag(arg1, arg2)(hello) 成功, makeHtmlTag 必需返回一个 decorator(这就是为什么我们在 makeHtmlTag 中加入了 real_decorator())。 89 | 90 | 这样一来,我们就可以进入到 decorator 的逻辑中去了——decorator 得返回一个 wrapper,wrapper 里回调 hello。看似那个 makeHtmlTag() 写得层层叠叠,但是,已经了解了本质的我们觉得写得很自然。 91 | 92 | 我们再来看一个为其它函数加缓存的示例: 93 | 94 | ```python 95 | from functools import wraps 96 | def memoization(fn): 97 | cache = {} 98 | miss = object() 99 | 100 | @wraps(fn) 101 | def wrapper(*args): 102 | result = cache.get(args, miss) 103 | if result is miss: 104 | result = fn(*args) 105 | cache[args] = result 106 | return result 107 | 108 | return wrapper 109 | 110 | @memoization 111 | def fib(n): 112 | if n < 2: 113 | return n 114 | return fib(n - 1) + fib(n - 2) 115 | ``` 116 | 117 | 上面这个例子中,是一个斐波那契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重复调用。比如:我们要计算 fib(5),于是其分解成 fib(4) + fib(3),而 fib(4) 分解成 fib(3) + fib(2),fib(3) 又分解成fib(2) + fib(1)……你可以看到,基本上来说,fib(3)、fib(2)、fib(1)在整个递归过程中被调用了至少两次。 118 | 119 | 而我们用 decorator,在调用函数前查询一下缓存,如果没有才调用,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。wraps 的作用是保证 fib 的函数名不被 wrapper 所取代。 120 | 121 | 除此之外,Python 还支持类方式的 decorator。 122 | 123 | ```python 124 | class myDecorator(object): 125 | def __init__(self, fn): 126 | print "inside myDecorator.__init__()" 127 | self.fn = fn 128 | 129 | def __call__(self): 130 | self.fn() 131 | print "inside myDecorator.__call__()" 132 | 133 | @myDecorator 134 | def aFunction(): 135 | print "inside aFunction()" 136 | 137 | print "Finished decorating aFunction()" 138 | 139 | aFunction() 140 | 141 | # 输出: 142 | # inside myDecorator.__init__() 143 | # Finished decorating aFunction() 144 | # inside aFunction() 145 | # inside myDecorator.__call__() 146 | ``` 147 | 148 | 上面这个示例展示了,用类的方式声明一个 decorator。我们可以看到这个类中有两个成员: 149 | 150 | 1. 一个是__ init __ (),这个方法是在我们给某个函数 decorate 时被调用,所以,需要有一个 fn 的参数,也就是被 decorate 的函数。 151 | 152 | 2. 一个是__ call __ (),这个方法是在我们调用被 decorate 的函数时被调用的。 153 | 154 | 从上面的输出中,可以看到整个程序的执行顺序,这看上去要比“函数式”的方式更易读一些。 155 | 156 | 我们来看一个实际点的例子,下面这个示例展示了通过 URL 的路由来调用相关注册的函数示例: 157 | 158 | ```python 159 | class MyApp(): 160 | def __init__(self): 161 | self.func_map = {} 162 | 163 | def register(self, name): 164 | def func_wrapper(func): 165 | self.func_map[name] = func 166 | return func 167 | return func_wrapper 168 | 169 | def call_method(self, name=None): 170 | func = self.func_map.get(name, None) 171 | if func is None: 172 | raise Exception("No function registered against - " + str(name)) 173 | return func() 174 | 175 | app = MyApp() 176 | 177 | @app.register('/') 178 | def main_page_func(): 179 | return "This is the main page." 180 | 181 | @app.register('/next_page') 182 | def next_page_func(): 183 | return "This is the next page." 184 | 185 | print app.call_method('/') 186 | print app.call_method('/next_page') 187 | ``` 188 | 189 | 注意:上面这个示例中 decorator 类不是真正的 decorator,其中也没有__call__(),并且,wrapper 返回了原函数。所以,原函数没有发生任何变化。 190 | 191 | ## Go 语言的 Decorator 192 | 193 | Python 有语法糖,所以写出来的代码比较酷。但是对于没有修饰器语法糖这类语言,写出来的代码会是怎么样的?我们来看一下 Go 语言的代码。 194 | 195 | 还是从一个 Hello World 开始。 196 | 197 | ```go 198 | package main 199 | 200 | import "fmt" 201 | 202 | func decorator(f func(s string)) func(s string) { 203 | return func(s string) { 204 | fmt.Println("Started") 205 | f(s) 206 | fmt.Println("Done") 207 | } 208 | } 209 | 210 | func Hello(s string) { 211 | fmt.Println(s) 212 | } 213 | 214 | func main() { 215 | decorator(Hello)("Hello, World!") 216 | } 217 | ``` 218 | 219 | 可以看到,我们动用了一个高阶函数 decorator(),在调用的时候,先把 Hello() 函数传进去,然后其返回一个匿名函数。这个匿名函数中除了运行了自己的代码,也调用了被传入的 Hello() 函数。 220 | 221 | 这个玩法和 Python 的异曲同工,只不过,Go 并不支持像 Python 那样的 @decorator 语法糖。所以,在调用上有些难看。当然,如果要想让代码容易读一些,你可以这样: 222 | 223 | ```go 224 | hello := decorator(Hello) 225 | hello("Hello") 226 | ``` 227 | 228 | 我们再来看一个为函数 log 消耗时间的例子: 229 | 230 | ```go 231 | type SumFunc func(int64, int64) int64 232 | 233 | func getFunctionName(i interface{}) string { 234 | return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() 235 | } 236 | 237 | func timedSumFunc(f SumFunc) SumFunc { 238 | return func(start, end int64) int64 { 239 | defer func(t time.Time) { 240 | fmt.Printf("--- Time Elapsed (%s): %v ---\n", 241 | getFunctionName(f), time.Since(t)) 242 | }(time.Now()) 243 | return f(start, end) 244 | } 245 | } 246 | 247 | func Sum1(start, end int64) int64 { 248 | var sum int64 249 | sum = 0 250 | if start > end { 251 | start, end = end, start 252 | } 253 | for i := start; i <= end; i++ { 254 | sum += i 255 | } 256 | return sum 257 | } 258 | 259 | func Sum2(start, end int64) int64 { 260 | if start > end { 261 | start, end = end, start 262 | } 263 | return (end - start + 1) * (end + start) / 2 264 | } 265 | 266 | func main() { 267 | 268 | sum1 := timedSumFunc(Sum1) 269 | sum2 := timedSumFunc(Sum2) 270 | 271 | fmt.Printf("%d, %d\n", sum1(-10000, 10000000), sum2(-10000, 10000000)) 272 | } 273 | ``` 274 | 275 | 关于上面的代码: 276 | 277 | * 有两个 Sum 函数,Sum1() 函数就是简单地做个循环,Sum2() 函数动用了数据公式。(注意:start 和 end 有可能有负数的情况。) 278 | 279 | * 代码中使用了 Go 语言的反射机制来获取函数名。 280 | 281 | * 修饰器函数是 timedSumFunc()。 282 | 283 | 再来看一个 HTTP 路由的例子: 284 | 285 | ```go 286 | func WithServerHeader(h http.HandlerFunc) http.HandlerFunc { 287 | return func(w http.ResponseWriter, r *http.Request) { 288 | log.Println("--->WithServerHeader()") 289 | w.Header().Set("Server", "HelloServer v0.0.1") 290 | h(w, r) 291 | } 292 | } 293 | 294 | func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc { 295 | return func(w http.ResponseWriter, r *http.Request) { 296 | log.Println("--->WithAuthCookie()") 297 | cookie := &http.Cookie{Name: "Auth", Value: "Pass", Path: "/"} 298 | http.SetCookie(w, cookie) 299 | h(w, r) 300 | } 301 | } 302 | 303 | func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc { 304 | return func(w http.ResponseWriter, r *http.Request) { 305 | log.Println("--->WithBasicAuth()") 306 | cookie, err := r.Cookie("Auth") 307 | if err != nil || cookie.Value != "Pass" { 308 | w.WriteHeader(http.StatusForbidden) 309 | return 310 | } 311 | h(w, r) 312 | } 313 | } 314 | 315 | func WithDebugLog(h http.HandlerFunc) http.HandlerFunc { 316 | return func(w http.ResponseWriter, r *http.Request) { 317 | log.Println("--->WithDebugLog") 318 | r.ParseForm() 319 | log.Println(r.Form) 320 | log.Println("path", r.URL.Path) 321 | log.Println("scheme", r.URL.Scheme) 322 | log.Println(r.Form["url_long"]) 323 | for k, v := range r.Form { 324 | log.Println("key:", k) 325 | log.Println("val:", strings.Join(v, "")) 326 | } 327 | h(w, r) 328 | } 329 | } 330 | func hello(w http.ResponseWriter, r *http.Request) { 331 | log.Printf("Received Request %s from %s\n", r.URL.Path, r.RemoteAddr) 332 | fmt.Fprintf(w, "Hello, World! "+r.URL.Path) 333 | } 334 | ``` 335 | 336 | 上面的代码中,我们写了多个函数。有写 HTTP 响应头的,有写认证 Cookie 的,有检查认证 Cookie 的,有打日志的……在使用过程中,我们可以把其嵌套起来使用,在修饰过的函数上继续修饰,这样就可以拼装出更复杂的功能。 337 | 338 | ```go 339 | func main() { 340 | http.HandleFunc("/v1/hello", WithServerHeader(WithAuthCookie(hello))) 341 | http.HandleFunc("/v2/hello", WithServerHeader(WithBasicAuth(hello))) 342 | http.HandleFunc("/v3/hello", WithServerHeader(WithBasicAuth(WithDebugLog(hello)))) 343 | err := http.ListenAndServe(":8080", nil) 344 | if err != nil { 345 | log.Fatal("ListenAndServe: ", err) 346 | } 347 | } 348 | ``` 349 | 350 | 当然,如果一层套一层不好看的话,我们可以使用 pipeline 的玩法,需要先写一个工具函数——用来遍历并调用各个 decorator: 351 | 352 | ```go 353 | type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc 354 | 355 | func Handler(h http.HandlerFunc, decors ...HttpHandlerDecorator) http.HandlerFunc { 356 | for i := range decors { 357 | d := decors[len(decors)-1-i] // iterate in reverse 358 | h = d(h) 359 | } 360 | return h 361 | } 362 | ``` 363 | 364 | 然后,我们就可以像下面这样使用了。 365 | 366 | ```go 367 | http.HandleFunc("/v4/hello", Handler(hello, 368 | WithServerHeader, WithBasicAuth, WithDebugLog)) 369 | ``` 370 | 371 | 这样的代码是不是更易读了一些?pipeline 的功能也就出来了。 372 | 373 | 不过,对于 Go 的修饰器模式,还有一个小问题——好像无法做到泛型,就像上面那个计算时间的函数一样,它的代码耦合了需要被修饰的函数的接口类型,无法做到非常通用。如果这个事解决不了,那么,这个修饰器模式还是有点不好用的。 374 | 375 | 因为 Go 语言不像 Python 和 Java,Python 是动态语言,而 Java 有语言虚拟机,所以它们可以干许多比较变态的事儿,然而 Go 语言是一个静态的语言,这意味着其类型需要在编译时就要搞定,否则无法编译。不过,Go 语言支持的最大的泛型是 interface{},还有比较简单的 Reflection 机制,在上面做做文章,应该还是可以搞定的。 376 | 377 | 废话不说,下面是我用 Reflection 机制写的一个比较通用的修饰器(为了便于阅读,我删除了出错判断代码)。 378 | 379 | ```go 380 | func Decorator(decoPtr, fn interface{}) (err error) { 381 | var decoratedFunc, targetFunc reflect.Value 382 | 383 | decoratedFunc = reflect.ValueOf(decoPtr).Elem() 384 | targetFunc = reflect.ValueOf(fn) 385 | 386 | v := reflect.MakeFunc(targetFunc.Type(), 387 | func(in []reflect.Value) (out []reflect.Value) { 388 | fmt.Println("before") 389 | out = targetFunc.Call(in) 390 | fmt.Println("after") 391 | return 392 | }) 393 | 394 | decoratedFunc.Set(v) 395 | return 396 | } 397 | ``` 398 | 399 | 上面的代码动用了 reflect.MakeFunc() 函数制作出了一个新的函数,其中的 targetFunc.Call(in) 调用了被修饰的函数。关于 Go 语言的反射机制,推荐官方文章——《[The Laws of Reflection](https://blog.golang.org/laws-of-reflection)》,在这里我不多说了。 400 | 401 | 上面这个 Decorator() 需要两个参数: 402 | 403 | * 第一个是出参 decoPtr ,就是完成修饰后的函数。 404 | 405 | * 第二个是入参 fn ,就是需要修饰的函数。 406 | 407 | 这样写是不是有些二?的确是的。不过,这是我个人在 Go 语言里所能写出来的最好的代码了。如果你知道更优雅的写法,请你一定告诉我! 408 | 409 | 好的,让我们来看一下使用效果。首先,假设我们有两个需要修饰的函数: 410 | 411 | ```go 412 | func foo(a, b, c int) int { 413 | fmt.Printf("%d, %d, %d \n", a, b, c) 414 | return a + b + c 415 | } 416 | 417 | func bar(a, b string) string { 418 | fmt.Printf("%s, %s \n", a, b) 419 | return a + b 420 | } 421 | ``` 422 | 423 | 然后,我们可以这样做: 424 | 425 | ```go 426 | type MyFoo func(int, int, int) int 427 | var myfoo MyFoo 428 | Decorator(&myfoo, foo) 429 | myfoo(1, 2, 3) 430 | ``` 431 | 432 | 你会发现,使用 Decorator() 时,还需要先声明一个函数签名,感觉好傻啊。一点都不泛型,不是吗?谁叫这是有类型的静态编译的语言呢? 433 | 434 | 嗯。如果你不想声明函数签名,那么也可以这样: 435 | 436 | ```go 437 | mybar := bar 438 | Decorator(&mybar, bar) 439 | mybar("hello,", "world!") 440 | ``` 441 | 442 | 好吧,看上去不是那么得漂亮,但是 it does work。看样子 Go 语言目前本身的特性无法做成像 Java 或 Python 那样,对此,我们只能多求 Go 语言多放糖了! 443 | 444 | ## 小结 445 | 446 | 好了,讲了那么多的例子,看了那么多的代码,我估计你可能有点晕,让我们来做个小结吧。 447 | 448 | 通过上面 Python 和 Go 修饰器的例子,我们可以看到,所谓的修饰器模式其实是在做下面的几件事。 449 | 450 | * 表面上看,修饰器模式就是扩展现有的一个函数的功能,让它可以干一些其他的事,或是在现有的函数功能上再附加上一些别的功能。 451 | 452 | * 除了我们可以感受到函数式编程下的代码扩展能力,我们还能感受到函数的互相和随意拼装带来的好处。 453 | 454 | * 但是深入看一下,我们不难发现,Decorator 这个函数其实是可以修饰几乎所有的函数的。于是,这种可以通用于其它函数的编程方式,可以很容易地将一些非业务功能的、属于控制类型的代码给抽象出来(所谓的控制类型的代码就是像 for-loop,或是打日志,或是函数路由,或是求函数运行时间之类的非业务功能性的代码)。 -------------------------------------------------------------------------------- /28_编程范式游记-面向对象编程/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(6)- 面向对象编程 2 | 3 | 前面我们谈了函数式编程,函数式编程总结起来就是把一些功能或逻辑代码通过函数拼装方式来组织的玩法。这其中涉及最多的是函数,也就是编程中的代码逻辑。但我们知道,代码中还是需要处理数据的,这些就是所谓的“状态”,函数式编程需要我们写出无状态的代码。 4 | 5 | 而这天下并不存在没有状态没有数据的代码,如果函数式编程不处理状态这些东西,那么,状态会放在什么地方呢?总是需要一个地方放这些数据的。 6 | 7 | 对于状态和数据的处理,我们有必要提一下“面向对象编程”(Object-oriented programming,OOP)这个编程范式了。我们知道,**面向对象的编程有三大特性:封装、继承和多态**。 8 | 9 | 面向对象编程是一种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针,它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的可重用性、灵活性和可扩展性,对象里的程序可以访问及修改对象相关联的数据。在面向对象编程里,计算机程序会被设计成彼此相关的对象。 10 | 11 | 面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对计算机下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。 12 | 13 | 目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。此外,支持者声称面向对象程序设计要比以往的做法更加便于学习,因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。 14 | 15 | 现在,几乎所有的主流语言都支持面向对象,比如:Common Lisp、Python、C++、Objective-C、Smalltalk、Delphi、Java、Swift、C#、Perl、Ruby 与 PHP 等。 16 | 17 | 说起面向对象,就不得不提由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合作出版的《[设计模式:可复用面向对象软件的基础](https://book.douban.com/subject/1052241/)》(Design Patterns - Elements of Reusable Object-Oriented Software)一书,在此书中共收录了 23 种设计模式。 18 | 19 | 这本书的 23 个经典的设计模式,基本上就是说了两个面向对象的核心理念: 20 | 21 | * "Program to an ‘interface’, not an ‘implementation’." 22 | * 使用者不需要知道数据类型、结构、算法的细节。 23 | * 使用者不需要知道实现细节,只需要知道提供的接口。 24 | 25 | * 利于抽象、封装、动态绑定、多态。 26 | * 符合面向对象的特质和理念。 27 | 28 | * "Favor ‘object composition’ over ‘class inheritance’." 29 | * 继承需要给子类暴露一些父类的设计和实现细节。 30 | * 父类实现的改变会造成子类也需要改变。 31 | * 我们以为继承主要是为了代码重用,但实际上在子类中需要重新实现很多父类的方法。 32 | * 继承更多的应该是为了多态。 33 | 34 | ### 示例一:拼装对象 35 | 36 | 好,我们先来看一个示例,假设我们有如下的描述: 37 | 38 | * 四个物体:木头桌子、木头椅子、塑料桌子、塑料椅子 39 | 40 | * 四个属性:燃点、密度、价格、重量 41 | 42 | 那么,我们怎么用面向对象的方式来设计我们的类呢? 43 | 44 | 参看下图: 45 | 46 | ![](/Users/likejun/ProgrammersLevelUp/28_编程范式游记-面向对象编程/pic_1.png) 47 | 48 | * 图的左边是“材质类” Material。其属性有燃点和密度。 49 | 50 | * 图的右边是“家具类” Furniture。其属性有价格和体积。 51 | 52 | * 在 Furniture 中耦合了 Material。而具体的 Material 是 Wood 还是 Plastic,这在构造对象的时候注入到 Furniture 里就好了。 53 | 54 | * 这样,在家具类中,通过材料的密度属性和家具的体积属性就可以计算出重量属性。 55 | 56 | 这样设计的优点显而易见,它能和现实世界相对应起来,而且,材料类是可以重用的。这个模式也表现了面向对象的拼装数据的另一个精髓——喜欢组合,而不是继承。这个模式在设计模式里叫“桥接(Bridge)模式”。 57 | 58 | 和函数式编程来比较,函数式强调动词,而面向对象强调名词,面向对象更多的关注接口间的关系,而通过多态来适配不同的具体实现。 59 | 60 | ### 示例二:拼装功能 61 | 62 | 再来看一个示例。我们的需求是:处理电商系统中的订单,处理订单有一个关键的动作就是计算订单的价格。有的订单需要打折,有的则不打折。 63 | 64 | 在进行面向对象编程时,假设我们用 Java 语言,我们需要先写一个接口—— BillingStrategy,其中一个方法就是GetActPrice(double rawPrice),输入一个原始的价格,输出一个根据相应的策略计算出来的价格。 65 | 66 | ```java 67 | interface BillingStrategy { 68 | public double GetActPrice(double rawPrice); 69 | } 70 | ``` 71 | 72 | 这个接口很简单,只是对接口的抽象,而与实现无关。现在我们需要对这个接口进行实现。 73 | 74 | ```java 75 | // Normal billing strategy (unchanged price) 76 | class NormalStrategy implements BillingStrategy { 77 | @Override 78 | public double GetActPrice(double rawPrice) { 79 | return rawPrice; 80 | } 81 | } 82 | 83 | // Strategy for Happy hour (50% discount) 84 | class HappyHourStrategy implements BillingStrategy { 85 | @Override 86 | public double GetActPrice(double rawPrice) { 87 | return rawPrice * 0.5; 88 | } 89 | } 90 | ``` 91 | 92 | 上面的代码实现了两个策略,一个是不打折的:NormalStrategy,一个是打了 5 折的:HappyHourStrategy。 93 | 94 | 于是,我们先封装订单项 OrderItem,其包含了每个商品的原始价格和数量,以及计算价格的策略。 95 | 96 | ```java 97 | class OrderItem { 98 | public String Name; 99 | public double Price; 100 | public int Quantity; 101 | public BillingStrategy Strategy; 102 | public OrderItem(String name, double price, int quantity, BillingStrategy strategy) { 103 | this.Name = name; 104 | this.Price = price; 105 | this.Quantity = quantity; 106 | this.Strategy = strategy; 107 | } 108 | } 109 | ``` 110 | 111 | 然后,在我们的订单类—— Order 中封装了 OrderItem 的列表,即商品列表。并在操作订单添加购买商品时,加入一个计算价格的 BillingStrategy。 112 | 113 | ```java 114 | class Order { 115 | private List orderItems = new ArrayList(); 116 | private BillingStrategy strategy = new NormalStrategy(); 117 | 118 | public void Add(String name, double price, int quantity, BillingStrategy strategy) { 119 | orderItems.add(new OrderItem(name, price, quantity, strategy)); 120 | } 121 | 122 | // Payment of bill 123 | public void PayBill() { 124 | double sum = 0; 125 | for (OrderItem item : orderItems) { 126 | 127 | actPrice = item.Strategy.GetActPrice(item.price * item.quantity); 128 | sum += actPrice; 129 | 130 | System.out.println("%s -- %f(%d) - %f", 131 | item.name, item.price, item.quantity, actPrice); 132 | } 133 | System.out.println("Total due: " + sum); 134 | } 135 | } 136 | 137 | ``` 138 | 139 | 最终,我们在 PayBill() 函数中,把整个订单的价格明细和总价打印出来。 140 | 141 | 在上面这个示例中,可以看到,我把定价策略和订单处理的流程分开了。这么做的好处是,我们可以随时给不同的商品注入不同的价格计算策略,这样一来就有很高的灵活度了。剩下的事就交给我们的运营人员来配置不同的商品使用什么样的价格计算策略了。 142 | 143 | 注意:现实社会中,订单价格计算会比这个事复杂得多,比如:有会员价,有打折卡,还有商品的打包价等,而且还可以叠加不同的策略(叠加策略用前面说的函数式的 pipeline 或 decorator 就可以实现)。我们这里只是为了说明面向对象编程范式,所以故意简单化了。 144 | 145 | 其实,这个设计模式叫——策略模式。我认为,这是设计模式中最为经典的模式了,其充分体现了面向对象编程的方式。 146 | 147 | ### 示例三:资源管理 148 | 149 | 先看一段代码: 150 | 151 | ```java 152 | mutex m; 153 | 154 | void foo() { 155 | m.lock(); 156 | Func(); 157 | if ( ! everythingOk() ) return; 158 | ... 159 | ... 160 | m.unlock(); 161 | } 162 | ``` 163 | 164 | 可以看到,上面这段代码是有问题的,原因是:那个 if 语句返回时没有把锁给 unlock 掉,这会导致锁没有被释放。如果我们要把代码写对,需要在 return 前 unlock 一下。 165 | 166 | ```java 167 | mutex m; 168 | 169 | void foo() { 170 | m.lock(); 171 | Func(); 172 | if ( ! everythingOk() ) { 173 | m.unlock(); 174 | return; 175 | } 176 | ... 177 | ... 178 | m.unlock(); 179 | } 180 | ``` 181 | 182 | 但是,在所有的函数退出的地方都要加上 m.unlock(); 语句,这会让我们很难维护代码。于是可以使用面向对象的编程模式,我们先设计一个代理类。 183 | 184 | ```c++ 185 | class lock_guard { 186 | private: 187 | mutex &_m; 188 | public: 189 | lock_guard(mutex &m):_m(m) { _m.lock(); } 190 | ~lock_guard() { _m.unlock(); } 191 | }; 192 | ``` 193 | 194 | 然后,我们的代码就可以这样写了: 195 | 196 | ```c++ 197 | mutex m; 198 | 199 | void foo() { 200 | lock_guard guard(m); 201 | Func(); 202 | if ( ! everythingOk() ) { 203 | return; 204 | } 205 | ... 206 | ... 207 | } 208 | ``` 209 | 210 | 这个技术叫 RAII(Resource Acquisition Is Initialization,资源获取就是初始化), 是 C++ 中的一个利用了面向对象的技术。这个设计模式叫“代理模式”。我们可以把一些控制资源分配和释放的逻辑交给这些代理类,然后,只需要关注业务逻辑代码了。而且,在我们的业务逻辑代码中,减少了这些和业务逻辑不相关的程序控制的代码。 211 | 212 | 从上面的代码中,我们可以看到下面几个面向对象的事情。 213 | 214 | * 我们使用接口抽象了具体的实现类。 215 | 216 | * 然后其它类耦合的是接口而不是实现类。这就是多态,其增加了程序的可扩展性。 217 | 218 | * 因为这就是接口编程,所谓接口也就是一种“协议”,就像 HTTP 协议一样。浏览器和后端的程序都依赖于这一种协议,而不是具体实现(如果是依赖具体实现,那么浏览器就要依赖后端的编程语言或中间件了,这就太恶心了)。于是,浏览器和后端的程序就完全解除依赖关系,而去依赖于一个标准的协议。 219 | 220 | * 这就是面向对象的编程范式的精髓!同样也是 IoC/DIP(控制反转 / 依赖倒置)的本质。 221 | 222 | ### IoC 控制反转 223 | 224 | 关于 IoC 的的概念提出来已经很多年了,其被用于一种面向对象的设计。我在这里再简单地回顾一下这个概念。我先谈技术,再说管理。 225 | 226 | 话说,我们有一个开关要控制一个灯的开和关这两个动作,最常见也是最没有技术含量的实现会是这个样子: 227 | 228 | ![](/Users/likejun/ProgrammersLevelUp/28_编程范式游记-面向对象编程/pic_2.jpeg) 229 | 230 | 然后,有一天,我们发现需要对灯泡扩展一下,于是做了个抽象类: 231 | 232 | ![](/Users/likejun/ProgrammersLevelUp/28_编程范式游记-面向对象编程/pic_3.jpeg) 233 | 234 | 但是,如果有一天,我们发现这个开关可能还要控制别的不单单是灯泡的东西,就会发现这个开关耦合了灯泡这种类别,非常不利于扩展,于是反转控制出现了。 235 | 236 | 就像现实世界一样,造开关的工厂根本不关心要控制的东西是什么,它只做一个开关应该做好的事,就是把电接通,把电断开(不管是手动的,还是声控的,还是光控,还是遥控的)。而我们造的各种各样的灯泡(不管是日光灯、白炽灯)的工厂也不关心你用什么样的开关,反正我只管把灯的电源接口给做出来。然后,开关厂和电灯厂依赖于一个标准的通电和断电的接口。于是产生了 IoC 控制反转,如下图: 237 | 238 | ![](/Users/likejun/ProgrammersLevelUp/28_编程范式游记-面向对象编程/pic_4.jpeg) 239 | 240 | 所谓控制反转的意思是,开关从以前设备的专用开关,转变到了控制电源的开关,而以前的设备要反过来依赖于开关厂声明的电源连接接口。只要符合开关厂定义的电源连接的接口,这个开关可以控制所有符合这个电源连接接口的设备。也就是说,开关从依赖设备这种情况,变成了设备反过来依赖于开关所定义的接口。 241 | 242 | 这样的例子在生活中太多见了,比如说: 243 | 244 | * 钱就是一个很好的例子。以前大家都是“以物易物”,所以,在各种物品之前都需要相应的“交易策略”,比如:一头羊换 2 袋米,一袋米换一斤猪后腿肉……这种换算太复杂了。于是,“钱”就出来了,所谓“钱”,其实就是一种交易协议,所有的商品都依赖这个协议,而不用再互相依赖了。于是整个世界的运作就简单了很多。 245 | 246 | * 在交易的过程中,卖家向买家卖东西,一手交钱一手交货,所以,基本上来说卖家和买家必需强耦合(必需见面)。这个时候,银行出来做担保,买家把钱先垫到银行,银行让卖家发货,买家验货后,银行再把钱打给卖家。这就是反转控制。买卖双方把对对方的直接依赖和控制,反转到了让对方来依赖一个标准的交易模型的接口。股票交易也是一样的,证交所就是买卖双方的标准交易模型接口。 247 | 248 | * 上面这个例子,可能还不明显,再举一个例子。海尔公司作为一个电器制商需要把自己的商品分销到全国各地,但是发现,不同的分销渠道有不同的玩法,于是派出了各种销售代表玩不同的玩法。随着渠道越来越多,发现,每增加一个渠道就要新增一批人和一个新的流程,严重耦合并依赖各渠道商的玩法。 249 | 250 | 实在受不了了,于是制定业务标准,开发分销信息化系统,只有符合这个标准的渠道商才能成为海尔的分销商,让各个渠道商反过来依赖自己标准。反转了控制,倒置了依赖。 251 | 252 | 这个思维方式其实还深远地影响了很多东西,比如我们的系统架构。 253 | 254 | * 云计算平台中有很多的云产品线。一些底层服务的开发团队只管开发底层的技术,然后什么也不管了,就交给上层的开发人员。上层开发人员在底层团队开发出来的产品上面开发各种管理这个底层资源的东西,比如:生产底层资源的业务,底层资源的控制台,底层资源的监控系统。 255 | 256 | 然而,随着接入的资源越来越多,上层为各个云资源控制生产,开发控制台和监控的团队,完全干不过来了。这个时候依赖倒置和反转控制又可以解决问题了。为了有统一体验,各个云产品线需要遵从一定的协议或规范来开发。比如,每个云产品团队需要按照标准定义相关资源的生命周期管理,提供控制台,接入整体监控系统,通过标准的协议开发控制系统。 257 | 258 | * 集中式处理电子商务订单的流程。各个垂直业务线都需要通过这个平台来处理自己的交易业务,但是垂直业务线上的个性化需求太多。于是,这个技术平台开始发现,对来自各个业务方的需求应接不暇,各种变态需求严重干扰系统,各种技术决策越来越不好做,导致需求排期排不过来。 259 | 260 | 这个时候,也可以使用依赖倒置和反转控制的思想来解决问题:开发一个插件模型、工作流引擎和 Pub/Sub 系统,让业务方的个性化需求支持以插件的方式插入订单流程中。业务方自己的数据存在自己的库中,业务逻辑也不要侵入系统,并可以使用工作流引擎或 Pub/Sub 的协议标准来自己定义工作流的各个步骤(甚至把工作流引擎的各个步骤的 decider 交给各个业务方自行处理)。 261 | 262 | 让各个业务方来依赖于标准插件和工作流接口,反转控制,让它们来控制系统,依赖倒置,让它们来依赖标准。 263 | 264 | 上面这些我想说什么?我想说的是: 265 | 266 | * 我们每天都在标准化和定制化中纠结。我们痛苦于哪些应该是平台要做的,哪些应该要甩出去的。 267 | 268 | * 这里面会出现大量的与业务无关的软件或中间件,包括协议、数据、接口…… 269 | 270 | * 通过面向对象的这些方式,我们可以通过抽象来解耦,通过中间件来解耦,这样可以降低软件的复杂度。 271 | 272 | 总而言之,我们就是想通过一种标准来让业务更为规范。 273 | 274 | ## 小结 275 | 276 | 不过,我们也需要知道面向对象的优缺点。 277 | 278 | **优点** 279 | 280 | * 能和真实的世界交相辉映,符合人的直觉。 281 | 282 | * 面向对象和数据库模型设计类型,更多地关注对象间的模型设计。 283 | 284 | * 强调于“名词”而不是“动词”,更多地关注对象和对象间的接口。 285 | 286 | * 根据业务的特征形成一个个高内聚的对象,有效地分离了抽象和具体实现,增强了可重用性和可扩展性。 287 | 288 | * 拥有大量非常优秀的设计原则和设计模式。 289 | 290 | * S.O.L.I.D(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转,是面向对象设计的五个基本原则)、IoC/DIP…… 291 | 292 | **缺点** 293 | 294 | * 代码都需要附着在一个类上,从一侧面上说,其鼓励了类型。 295 | 296 | * 代码需要通过对象来达到抽象的效果,导致了相当厚重的“代码粘合层”。 297 | 298 | * 因为太多的封装以及对状态的鼓励,导致了大量不透明并在并发下出现很多问题。 299 | 300 | 还是好多人并不是喜欢面向对象,尤其是喜欢函数式和泛型那些人,似乎都是非常讨厌面向对象的。 301 | 302 | 通过对象来达到抽象结果,把代码分散在不同的类里面,然后,要让它们执行起来,就需要把这些类粘合起来。所以,它另外一方面鼓励相当厚重的代码黏合层(代码黏合层就是把代码黏合到这里面)。 303 | 304 | 在 Java 里有很多注入方式,像 Spring 那些注入,鼓励黏合,导致了大量的封装,完全不知道里面在干什么事情。而且封装屏蔽了细节,具体发生啥事你还不知道。这些都是面向对象不太好的地方。 -------------------------------------------------------------------------------- /28_编程范式游记-面向对象编程/pic_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/28_编程范式游记-面向对象编程/pic_1.png -------------------------------------------------------------------------------- /28_编程范式游记-面向对象编程/pic_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/28_编程范式游记-面向对象编程/pic_2.jpeg -------------------------------------------------------------------------------- /28_编程范式游记-面向对象编程/pic_3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/28_编程范式游记-面向对象编程/pic_3.jpeg -------------------------------------------------------------------------------- /28_编程范式游记-面向对象编程/pic_4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/28_编程范式游记-面向对象编程/pic_4.jpeg -------------------------------------------------------------------------------- /29_编程范式游记-基于原型的编程范式/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(7)- 基于原型的编程范式 2 | 3 | 基于原型(Prototype)的编程其实也是面向对象编程的一种方式。没有 class 化的,直接使用对象。又叫,基于实例的编程。其主流的语言就是 JavaScript,与传统的面对象编程的比较如下: 4 | 5 | * 在基于类的编程当中,对象总共有两种类型。类定义了对象的基本布局和函数特性,而接口是“可以使用的”对象,它基于特定类的样式。在此模型中,类表现为行为和结构的集合,对所有接口来说这些类的行为和结构都是相同的。因而,区分规则首先是基于行为和结构,而后才是状态。 6 | 7 | * 原型编程的主张者经常争论说,基于类的语言提倡使用一个关注分类和类之间关系的开发模型。与此相对,原型编程看起来提倡程序员关注一系列对象实例的行为,而之后才关心如何将这些对象划分到最近的使用方式相似的原型对象,而不是分成类。 8 | 9 | 因为如此,很多基于原型的系统提倡运行时进行原型的修改,而只有极少数基于类的面向对象系统(比如第一个动态面向对象的系统 Smalltalk)允许类在程序运行时被修改。 10 | 11 | * 在基于类的语言中,一个新的实例通过类构造器和构造器可选的参数来构造,结果实例由类选定的行为和布局创建模型。 12 | 13 | * 在基于原型的系统中构造对象有两种方法,通过复制已有的对象或者通过扩展空对象创建。很多基于原型的系统提倡运行时进行原型的修改,而基于类的面向对象系统只有动态语言允许类在运行时被修改(Common Lisp、Dylan、Objective-C、Perl、Python、Ruby 和 Smalltalk)。 14 | 15 | ## JavaScript 的原型概念 16 | 17 | 这里,我们主要以 JavaScript 举例,面向对象里面要有个 Class。但是 JavaScript 觉得不是这样的,它就是要基于原型编程,就不要 Class,就直接在对象上改就行了,基于编程的修改,直接对类型进行修改。 18 | 19 | 我们先来看一个示例。 20 | 21 | ```javascript 22 | var foo = {name: "foo", one: 1, two: 2}; 23 | 24 | var bar = {three: 3}; 25 | ``` 26 | 27 | 每个对象都有一个 __ proto __ 的属性,这个就是“原型”。对于上面的两个对象,如果我们把 foo 赋值给 bar.__ proto __,那就意味着,bar 的原型就成了 foo的。 28 | 29 | ```javascript 30 | bar.__proto__ = foo; // foo is now the prototype of bar. 31 | ``` 32 | 33 | 于是,我们就可以在 bar 里面访问 foo 的属性了。 34 | 35 | ```javascript 36 | // If we try to access foo's properties from bar 37 | // from now on, we'll succeed. 38 | bar.one // Resolves to 1. 39 | 40 | // The child object's properties are also accessible. 41 | bar.three // Resolves to 3. 42 | 43 | // Own properties shadow prototype properties 44 | bar.name = "bar"; 45 | foo.name; // unaffected, resolves to "foo" 46 | bar.name; // Resolves to "bar" 47 | ``` 48 | 49 | 需要解释一下 JavaScript 的两个东西,一个是 __ proto __,另一个是 prototype,这两个东西很容易混淆。这里说明一下: 50 | 51 | * __ proto __ 主要是安放在一个实际的对象中,用它来产生一个链接,一个原型链,用于寻找方法名或属性,等等。 52 | 53 | * **prototype** 是用 new 来创建一个对象时构造 __ proto __ 用的。它是构造函数的一个属性。 54 | 55 | 在 JavaScript 中,对象有两种表现形式, 一种是 Object ([ES5 关于 Object 的文档](https://262.ecma-international.org/5.1/#sec-15.2)),一种是 Function ([ES5 关于 Function 的文档](https://262.ecma-international.org/5.1/#sec-15.2))。 56 | 57 | 我们可以简单地认为,__ proto __ 是所有对象用于链接原型的一个指针,而 prototype 则是 Function 对象的属性,其主要是用来当需要new一个对象时让 __ proto __ 指针所指向的地方。 对于超级对象 Function 而言, Function.__ proto __ 就是 Function.prototype。 58 | 59 | 比如我们有如下的代码: 60 | 61 | ```javascript 62 | var a = { 63 | x: 10, 64 | calculate: function (z) { 65 | return this.x + this.y + z; 66 | } 67 | }; 68 | 69 | var b = { 70 | y: 20, 71 | __proto__: a 72 | }; 73 | 74 | var c = { 75 | y: 30, 76 | __proto__: a 77 | }; 78 | 79 | // call the inherited method 80 | b.calculate(30); // 60 81 | c.calculate(40); // 80 82 | ``` 83 | 84 | 其中的“原型链”如下所示: 85 | 86 | ![](/Users/likejun/ProgrammersLevelUp/29_编程范式游记-基于原型的编程范式/pic_1.png) 87 | 88 | 注意:ES5 中,规定原型继承需要使用 Object.create() 函数。如下所示: 89 | 90 | ```javascript 91 | var b = Object.create(a, {y: {value: 20}}); 92 | var c = Object.create(a, {y: {value: 30}}); 93 | ``` 94 | 95 | 好了,我们再来看一段代码: 96 | 97 | ```javascript 98 | // 一种构造函数写法 99 | function Foo(y) { 100 | this.y = y; 101 | } 102 | 103 | // 修改 Foo 的 prototype,加入一个成员变量 x 104 | Foo.prototype.x = 10; 105 | 106 | // 修改 Foo 的 prototype,加入一个成员函数 calculate 107 | Foo.prototype.calculate = function (z) { 108 | return this.x + this.y + z; 109 | }; 110 | 111 | // 现在,我们用 Foo 这个原型来创建 b 和 c 112 | var b = new Foo(20); 113 | var c = new Foo(30); 114 | 115 | // 调用原型中的方法,可以得到正确的值 116 | b.calculate(30); // 60 117 | c.calculate(40); // 80 118 | ``` 119 | 120 | 那么,在内存中的布局是怎么样的呢?大概是下面这个样子。 121 | 122 | ![](/Users/likejun/ProgrammersLevelUp/29_编程范式游记-基于原型的编程范式/pic_2.png) 123 | 124 | 这个图应该可以让你很好地看明白 __ proto __ 和 prototype 的差别了。 125 | 126 | 我们可以测试一下: 127 | 128 | ```javascript 129 | b.__proto__ === Foo.prototype, // true 130 | c.__proto__ === Foo.prototype, // true 131 | 132 | b.constructor === Foo, // true 133 | c.constructor === Foo, // true 134 | Foo.prototype.constructor === Foo, // true 135 | 136 | b.calculate === b.__proto__.calculate, // true 137 | b.__proto__.calculate === Foo.prototype.calculate // true 138 | ``` 139 | 140 | 这里需要说明的是: 141 | 142 | **Foo.prototype 自动创建了一个属性 constructor,这是一个指向函数自己的一个 reference。这样一来,对于实例 b 或 c 来说,就能访问到这个继承的 constructor 了**。 143 | 144 | 有了这些基本概念,我们就可以讲一下 JavaScript 的面向对象编程了。 145 | 146 | > 注: 上面示例和图示来源于 [JavaScript, The Core](http://dmitrysoshnikov.com/ecmascript/javascript-the-core/) 一文。 147 | 148 | ### JavaScript 原型编程的面向对象 149 | 150 | 我们再来重温一下上面讲述的内容: 151 | 152 | ```javascript 153 | function Person(){} 154 | var p = new Person(); 155 | 156 | Person.prototype.name = "Hao Chen"; 157 | Person.prototype.sayHello = function(){ 158 | console.log("Hi, I am " + this.name); 159 | } 160 | 161 | console.log(p.name); // "Hao Chen" 162 | p.sayHello(); // "Hi, I am Hao Chen" 163 | ``` 164 | 165 | 在上面这个例子中: 166 | 167 | * 我们先生成了一个空的函数对象 Person(); 168 | * 然后将这个空的函数对象 new 出另一个对象,存在 p 中; 169 | * 这时再改变 Person.prototype,让其有一个 name 的属性和一个 sayHello() 的方法; 170 | * 我们发现,另外那个 p 的对象也跟着一起改变了。 171 | 172 | 注意一下: 173 | 174 | * 当创建 function Person(){} 时,Person.__proto__ 指向 Function.prototype; 175 | * 当创建 var p = new Person() 时,p.__proto__ 指向 Person.prototype; 176 | * 当修改了 Person.prototype 的内容后,p.__proto__ 的内容也就被改变了。 177 | 178 | 好了,我们再来看一下“原型编程”中面向对象的编程玩法。 179 | 180 | 首先,我们定义一个 Person 类。 181 | 182 | ```javascript 183 | //Define human class 184 | var Person = function (fullName, email) { 185 | this.fullName = fullName; 186 | this.email = email; 187 | 188 | this.speak = function(){ 189 | console.log("I speak English!"); 190 | }; 191 | this.introduction = function(){ 192 | console.log("Hi, I am " + this.fullName); 193 | }; 194 | } 195 | ``` 196 | 197 | 上面这个对象中,包含了: 198 | 199 | * 属性: fullName 和 email; 200 | 201 | * 方法: speak() 和 introduction()。 202 | 203 | 其实,所谓的方法也是属性。 204 | 205 | 然后,我们可以定义一个 Student 对象。 206 | 207 | ```javascript 208 | //Define Student class 209 | var Student = function(fullName, email, school, courses) { 210 | 211 | Person.call(this, fullName, email); 212 | 213 | // Initialize our Student properties 214 | this.school = school; 215 | this.courses = courses; 216 | 217 | // override the "introduction" method 218 | this.introduction= function(){ 219 | console.log("Hi, I am " + this.fullName + 220 | ". I am a student of " + this.school + 221 | ", I study "+ this.courses +"."); 222 | }; 223 | 224 | // Add a "exams" method 225 | this.takeExams = function(){ 226 | console.log("This is my exams time!"); 227 | }; 228 | }; 229 | ``` 230 | 231 | 在上面的代码中: 232 | 233 | * 使用了 Person.call(this, fullName, email),call() 或 apply() 都是为了动态改变 this 所指向的对象的内容而出现的。这里的 this 就是 Student。 234 | * 上面的例子中,我们重载了 introduction() 方法,并新增加了一个 takeExams()的方法。 235 | 236 | 虽然,我们这样定义了 Student,但是它还没有和 Person 发生继承关系。为了要让它们发生关系,我们就需要修改 Student 的原型。 237 | 238 | 我们可以简单粗暴地做赋值:Student.__ proto __ = Person.prototype ,但是,这太粗暴了。 239 | 240 | 我们还是使用比较规范的方式: 241 | 242 | * 先用 Object.create() 来将Person.prototype 和 Student.prototype 关联上。 243 | * 然后,修改一下构造函数 Student.prototype.constructor = Student;。 244 | 245 | ```javascript 246 | // Create a Student.prototype object that inherits 247 | // from Person.prototype. 248 | Student.prototype = Object.create(Person.prototype); 249 | 250 | // Set the "constructor" property to refer to Student 251 | Student.prototype.constructor = Student; 252 | ``` 253 | 254 | 这样,我们就可以这样使用了。 255 | 256 | ```javascript 257 | var student = new Student("Hao Chen", 258 | "haoel@hotmail.com", 259 | "XYZ University", 260 | "Computer Science"); 261 | student.introduction(); 262 | student.speak(); 263 | student.takeExams(); 264 | 265 | // Check that instanceof works correctly 266 | console.log(student instanceof Person); // true 267 | console.log(student instanceof Student); // true 268 | ``` 269 | 270 | 上述就是基于原型的面向对象编程的玩法了。 271 | 272 | > 注:在 ECMAScript 标准的第四版开始寻求使 JavaScript 提供基于类的构造,且 ECMAScript 第六版有提供 "class"(类) 作为原有的原型架构之上的语法糖,提供构建对象与处理继承时的另一种语法。 273 | 274 | ## 小结 275 | 276 | 我们可以看到,这种玩法就是一种委托的方式。在使用委托的基于原型的语言中,运行时语言可以“仅仅通过序列的指针找到匹配”这样的方式来定位属性或者寻找正确的数据。所有这些创建行为、共享的行为需要的是委托指针。 277 | 278 | 不像是基于类的面向对象语言中类和接口的关系,原型和它的分支之间的关系并不要求子对象有相似的内存结构,因为如此,子对象可以继续修改而无需像基于类的系统那样整理结构。还有一个要提到的地方是,不仅仅是数据,方法也能被修改。因为这个原因,大多数基于原型的语言把数据和方法提作“slots”。 279 | 280 | 这种在对象里面直接修改的玩法,虽然这个特性可以带来运行时的灵活性,我们可以在运行时修改一个 prototype,给它增加甚至删除属性和方法。但是其带来了执行的不确定性,也有安全性的问题,而代码还变得不可预测,这有点黑科技的味道了。因为这些不像静态类型系统,没有一个不可变的契约对代码的确定性有保证,所以,需要使用者来自己保证。 -------------------------------------------------------------------------------- /29_编程范式游记-基于原型的编程范式/pic_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/29_编程范式游记-基于原型的编程范式/pic_1.png -------------------------------------------------------------------------------- /29_编程范式游记-基于原型的编程范式/pic_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/29_编程范式游记-基于原型的编程范式/pic_2.png -------------------------------------------------------------------------------- /30_编程范式游记-Go语言的委托模式/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/30_编程范式游记-Go语言的委托模式/.DS_Store -------------------------------------------------------------------------------- /30_编程范式游记-Go语言的委托模式/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(8)- Go 语言的委托模式 2 | 3 | 我们再来看 Go 语言这个模式,Go 语言的这个模式挺好玩儿的。声明一个 struct,跟 C 很一样,然后直接把这个 struct 类型放到另一个 struct 里。 4 | 5 | ## 委托的简单示例 6 | 7 | 我们来看几个示例: 8 | 9 | ```go 10 | type Widget struct { 11 | X, Y int 12 | } 13 | 14 | type Label struct { 15 | Widget // Embedding (delegation) 16 | Text string // Aggregation 17 | X int // Override 18 | } 19 | 20 | func (label Label) Paint() { 21 | // [0xc4200141e0] - Label.Paint("State") 22 | fmt.Printf("[%p] - Label.Paint(%q)\n", 23 | &label, label.Text) 24 | } 25 | ``` 26 | 27 | 由上面可知: 28 | 29 | * 我们声明了一个 Widget,其有 X和Y; 30 | * 然后用它来声明一个 Label,直接把 Widget 委托进去; 31 | * 然后再给 Label 声明并实现了一个 Paint() 方法。 32 | 33 | 于是,我们就可以这样编程了: 34 | 35 | ```go 36 | label := Label{Widget{10, 10}, "State", 100} 37 | 38 | // X=100, Y=10, Text=State, Widget.X=10 39 | fmt.Printf("X=%d, Y=%d, Text=%s Widget.X=%d\n", 40 | label.X, label.Y, label.Text, 41 | label.Widget.X) 42 | fmt.Println() 43 | // {Widget:{X:10 Y:10} Text:State X:100} 44 | // {{10 10} State 100} 45 | fmt.Printf("%+v\n%v\n", label, label) 46 | 47 | label.Paint() 48 | ``` 49 | 50 | 我们可以看到,如果有成员变量重名,则需要手动地解决冲突。 51 | 52 | 我们继续扩展代码。 53 | 54 | 先来一个 Button: 55 | 56 | ```go 57 | type Button struct { 58 | Label // Embedding (delegation) 59 | } 60 | 61 | func NewButton(x, y int, text string) Button { 62 | return Button{Label{Widget{x, y}, text, x}} 63 | } 64 | func (button Button) Paint() { // Override 65 | fmt.Printf("[%p] - Button.Paint(%q)\n", 66 | &button, button.Text) 67 | } 68 | func (button Button) Click() { 69 | fmt.Printf("[%p] - Button.Click()\n", &button) 70 | } 71 | ``` 72 | 73 | 再来一个 ListBox: 74 | 75 | ```go 76 | type ListBox struct { 77 | Widget // Embedding (delegation) 78 | Texts []string // Aggregation 79 | Index int // Aggregation 80 | } 81 | func (listBox ListBox) Paint() { 82 | fmt.Printf("[%p] - ListBox.Paint(%q)\n", 83 | &listBox, listBox.Texts) 84 | } 85 | func (listBox ListBox) Click() { 86 | fmt.Printf("[%p] - ListBox.Click()\n", &listBox) 87 | } 88 | ``` 89 | 90 | 然后,声明两个接口用于多态: 91 | 92 | ```go 93 | type Painter interface { 94 | Paint() 95 | } 96 | 97 | type Clicker interface { 98 | Click() 99 | } 100 | ``` 101 | 102 | 于是我们就可以这样泛型地使用(注意其中的两个 for 循环): 103 | 104 | ```go 105 | button1 := Button{Label{Widget{10, 70}, "OK", 10}} 106 | button2 := NewButton(50, 70, "Cancel") 107 | listBox := ListBox{Widget{10, 40}, 108 | []string{"AL", "AK", "AZ", "AR"}, 0} 109 | 110 | fmt.Println() 111 | //[0xc4200142d0] - Label.Paint("State") 112 | //[0xc420014300] - ListBox.Paint(["AL" "AK" "AZ" "AR"]) 113 | //[0xc420014330] - Button.Paint("OK") 114 | //[0xc420014360] - Button.Paint("Cancel") 115 | for _, painter := range []Painter{label, listBox, button1, button2} { 116 | painter.Paint() 117 | } 118 | 119 | fmt.Println() 120 | //[0xc420014450] - ListBox.Click() 121 | //[0xc420014480] - Button.Click() 122 | //[0xc4200144b0] - Button.Click() 123 | for _, widget := range []interface{}{label, listBox, button1, button2} { 124 | if clicker, ok := widget.(Clicker); ok { 125 | clicker.Click() 126 | } 127 | } 128 | ``` 129 | 130 | ### 一个 Undo 的委托重构 131 | 132 | 上面这个是 Go 语中的委托和接口多态的编程方式,其实是面向对象和原型编程综合的玩法。这个玩法可不可以玩得更有意思呢?这是可以的。 133 | 134 | 首先,我们先声明一个数据容器,其中有 Add()、 Delete() 和 Contains() 方法。还有一个转字符串的方法。 135 | 136 | ```go 137 | type IntSet struct { 138 | data map[int]bool 139 | } 140 | 141 | func NewIntSet() IntSet { 142 | return IntSet{make(map[int]bool)} 143 | } 144 | 145 | func (set *IntSet) Add(x int) { 146 | set.data[x] = true 147 | } 148 | 149 | func (set *IntSet) Delete(x int) { 150 | delete(set.data, x) 151 | } 152 | 153 | func (set *IntSet) Contains(x int) bool { 154 | return set.data[x] 155 | } 156 | 157 | func (set *IntSet) String() string { // Satisfies fmt.Stringer interface 158 | if len(set.data) == 0 { 159 | return "{}" 160 | } 161 | ints := make([]int, 0, len(set.data)) 162 | for i := range set.data { 163 | ints = append(ints, i) 164 | } 165 | sort.Ints(ints) 166 | parts := make([]string, 0, len(ints)) 167 | for _, i := range ints { 168 | parts = append(parts, fmt.Sprint(i)) 169 | } 170 | return "{" + strings.Join(parts, ",") + "}" 171 | } 172 | ``` 173 | 174 | 我们如下使用这个数据容器: 175 | 176 | ```go 177 | ints := NewIntSet() 178 | for _, i := range []int{1, 3, 5, 7} { 179 | ints.Add(i) 180 | fmt.Println(ints) 181 | } 182 | for _, i := range []int{1, 2, 3, 4, 5, 6, 7} { 183 | fmt.Print(i, ints.Contains(i), " ") 184 | ints.Delete(i) 185 | fmt.Println(ints) 186 | } 187 | ``` 188 | 189 | 这个数据容器平淡无奇,我们想给它加一个 Undo 的功能。我们可以这样来: 190 | 191 | ```go 192 | type UndoableIntSet struct { // Poor style 193 | IntSet // Embedding (delegation) 194 | functions []func() 195 | } 196 | 197 | func NewUndoableIntSet() UndoableIntSet { 198 | return UndoableIntSet{NewIntSet(), nil} 199 | } 200 | 201 | func (set *UndoableIntSet) Add(x int) { // Override 202 | if !set.Contains(x) { 203 | set.data[x] = true 204 | set.functions = append(set.functions, func() { set.Delete(x) }) 205 | } else { 206 | set.functions = append(set.functions, nil) 207 | } 208 | } 209 | 210 | func (set *UndoableIntSet) Delete(x int) { // Override 211 | if set.Contains(x) { 212 | delete(set.data, x) 213 | set.functions = append(set.functions, func() { set.Add(x) }) 214 | } else { 215 | set.functions = append(set.functions, nil) 216 | } 217 | } 218 | 219 | func (set *UndoableIntSet) Undo() error { 220 | if len(set.functions) == 0 { 221 | return errors.New("No functions to undo") 222 | } 223 | index := len(set.functions) - 1 224 | if function := set.functions[index]; function != nil { 225 | function() 226 | set.functions[index] = nil // Free closure for garbage collection 227 | } 228 | set.functions = set.functions[:index] 229 | return nil 230 | } 231 | ``` 232 | 233 | 于是就可以这样使用了: 234 | 235 | ```go 236 | ints := NewUndoableIntSet() 237 | for _, i := range []int{1, 3, 5, 7} { 238 | ints.Add(i) 239 | fmt.Println(ints) 240 | } 241 | for _, i := range []int{1, 2, 3, 4, 5, 6, 7} { 242 | fmt.Println(i, ints.Contains(i), " ") 243 | ints.Delete(i) 244 | fmt.Println(ints) 245 | } 246 | fmt.Println() 247 | for { 248 | if err := ints.Undo(); err != nil { 249 | break 250 | } 251 | fmt.Println(ints) 252 | } 253 | ``` 254 | 255 | 但是,需要注意的是,我们用了一个新的 UndoableIntSet 几乎重写了所有的 IntSet 和 “写” 相关的方法,这样就可以把操作记录下来,然后 **Undo** 了。 256 | 257 | 但是,可能别的类也需要 Undo 的功能,我是不是要重写所有的需要这个功能的类啊?这样的代码类似,就是因为数据容器不一样,我就要去重写它们,这太二了。 258 | 259 | 我们能不能利用前面学到的泛型编程、函数式编程、IoC 等范式来把这个事干得好一些呢?当然是可以的。 260 | 261 | 如下所示: 262 | 263 | * 我们先声明一个 Undo[] 的函数数组(其实是一个栈); 264 | * 并实现一个通用 Add()。其需要一个函数指针,并把这个函数指针存放到 Undo[] 函数数组中。 265 | * 在 Undo() 的函数中,我们会遍历Undo[]函数数组,并执行之,执行完后就弹栈。 266 | 267 | ```go 268 | type Undo []func() 269 | 270 | func (undo *Undo) Add(function func()) { 271 | *undo = append(*undo, function) 272 | } 273 | 274 | func (undo *Undo) Undo() error { 275 | functions := *undo 276 | if len(functions) == 0 { 277 | return errors.New("No functions to undo") 278 | } 279 | index := len(functions) - 1 280 | if function := functions[index]; function != nil { 281 | function() 282 | functions[index] = nil // Free closure for garbage collection 283 | } 284 | *undo = functions[:index] 285 | return nil 286 | } 287 | 288 | ``` 289 | 290 | 那么我们的 IntSet 就可以改写成如下的形式: 291 | 292 | ```go 293 | type IntSet struct { 294 | data map[int]bool 295 | undo Undo 296 | } 297 | 298 | func NewIntSet() IntSet { 299 | return IntSet{data: make(map[int]bool)} 300 | } 301 | 302 | ``` 303 | 304 | 然后在其中的 Add 和 Delete中实现 Undo 操作。 305 | 306 | * Add 操作时加入 Delete 操作的 Undo。 307 | * Delete 操作时加入 Add 操作的 Undo。 308 | 309 | ```go 310 | func (set *IntSet) Add(x int) { 311 | if !set.Contains(x) { 312 | set.data[x] = true 313 | set.undo.Add(func() { set.Delete(x) }) 314 | } else { 315 | set.undo.Add(nil) 316 | } 317 | } 318 | 319 | func (set *IntSet) Delete(x int) { 320 | if set.Contains(x) { 321 | delete(set.data, x) 322 | set.undo.Add(func() { set.Add(x) }) 323 | } else { 324 | set.undo.Add(nil) 325 | } 326 | } 327 | 328 | func (set *IntSet) Undo() error { 329 | return set.undo.Undo() 330 | } 331 | 332 | func (set *IntSet) Contains(x int) bool { 333 | return set.data[x] 334 | } 335 | 336 | ``` 337 | 338 | 我们再次看到,Go 语言的 Undo 接口把 Undo 的流程给抽象出来,而要怎么 Undo 的事交给了业务代码来维护(通过注册一个 Undo 的方法)。这样在 Undo 的时候,就可以回调这个方法来做与业务相关的 Undo 操作了。 339 | 340 | ## 小结 341 | 342 | 这是不是和最一开始的 C++ 的泛型编程很像?也和 map、reduce、filter 这样的只关心控制流程,不关心业务逻辑的做法很像?而且,一开始用一个 UndoableIntSet 来包装IntSet类,到反过来在IntSet里依赖Undo类,这就是控制反转 IoC。 -------------------------------------------------------------------------------- /31_编程范式游记-编程的本质/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(9)- 编程的本质 2 | 3 | 前面我们讲了各式各样的不同语言的编程范式,从 C 语言的泛型,讲到 C++ 的泛型,再讲到函数式的 Map/Reduce/Filter,以及 Pipeline 和 Decorator,还有面向对象的多态通过依赖接口而不是实现的桥接模式、策略模式和代理模式,以及面向对象的 IoC,还有 JavaScript 的原型编程在运行时对对象原型进行修改,以及 Go 语言的委托模式…… 4 | 5 | 所有的这一切,不知道你是否看出一些端倪,或是其中的一些共性来了? 6 | 7 | ## 两篇论文 8 | 9 | 1976 年,瑞士计算机科学家,Algol W,Modula,Oberon 和 Pascal 语言的设计师 [Niklaus Emil Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth)写了一本非常经典的书《[Algorithms + Data Structures = Programs](http://www.ethoberon.ethz.ch/WirthPubl/AD.pdf)》(链接为 1985 年版) ,即算法 + 数据结构 = 程序。 10 | 11 | 这本书主要写了算法和数据结构的关系,这本书对计算机科学的影响深远,尤其在计算机科学的教育中。 12 | 13 | 1979 年,英国逻辑学家和计算机科学家 [Robert Kowalski](https://en.wikipedia.org/wiki/Robert_Kowalski) 发表论文 [Algorithm = Logic + Control](https://www.doc.ic.ac.uk/~rak/papers/algorithm%20=%20logic%20+%20control.pdf),并且主要开发“逻辑编程”相关的工作。 14 | 15 | Robert Kowalski 是一位逻辑学家和计算机科学家,从 20 世纪 70 年代末到整个 80 年代致力于数据库的研究,并在用计算机证明数学定理等当年的重要应用上颇有建树,尤其是在逻辑、控制和算法等方面提出了革命性的理论,极大地影响了数据库、编程语言,直至今日的人工智能。 16 | 17 | Robert Kowalski 在这篇论文里提到: 18 | 19 | > An algorithm can be regarded as consisting of a logic component, which specifies the knowledge to be used in solving problems, and a control component, which determines the problem-solving strategies by means of which that knowledge is used. The logic component determines the meaning of the algorithm whereas the control component only affects its efficiency. The efficiency of an algorithm can often be improved by improving the control component without changing the logic of the algorithm. We argue that computer programs would be more often correct and more easily improved and modified if their logic and control aspects were identified and separated in the program text. 20 | 21 | 翻译过来的意思大概就是: 22 | 23 | > 任何算法都会有两个部分, 一个是 Logic 部分,这是用来解决实际问题的。另一个是 Control 部分,这是用来决定用什么策略来解决问题。Logic 部分是真正意义上的解决问题的算法,而 Control 部分只是影响解决这个问题的效率。程序运行的效率问题和程序的逻辑其实是没有关系的。我们认为,如果将 Logic 和 Control 部分有效地分开,那么代码就会变得更容易改进和维护。 24 | 25 | 注意,最后一句话是重点——**如果将 Logic 和 Control 部分有效地分开,那么代码就会变得更容易改进和维护**。 26 | 27 | ### 编程的本质 28 | 29 | 两位老先生的两个表达式: 30 | 31 | * Programs = Algorithms + Data Structures 32 | * Algorithm = Logic + Control 33 | 34 | 第一个表达式倾向于数据结构和算法,它是想把这两个拆分,早期都在走这条路。他们认为,如果数据结构设计得好,算法也会变得简单,而且一个好的通用的算法应该可以用在不同的数据结构上。 35 | 36 | 第二个表达式则想表达的是数据结构不复杂,复杂的是算法,也就是我们的业务逻辑是复杂的。我们的算法由两个逻辑组成,一个是真正的业务逻辑,另外一种是控制逻辑。程序中有两种代码,一种是真正的业务逻辑代码,另一种代码是控制我们程序的代码,叫控制代码,这根本不是业务逻辑,业务逻辑不关心这个事情。 37 | 38 | 算法的效率往往可以通过提高控制部分的效率来实现,而无须改变逻辑部分,也就无须改变算法的意义。举个阶乘的例子, X(n)!= X(n) * X(n-1) * X(n-2) * X(n-3)* … * 3 * 2 * 1。逻辑部分用来定义阶乘:1) 1 是 0 的阶乘; 2)如果 v 是 x 的阶乘,且 u=v*(x+1),那么 u 是 x+1 的阶乘。 39 | 40 | 用这个定义,既可以从上往下地将 x+1 的阶乘缩小为先计算 x 的阶乘,再将结果乘以 1(recursive,递归),也可以由下而上逐个计算一系列阶乘的结果(iteration,遍历)。 41 | 42 | 控制部分用来描述如何使用逻辑。最粗略的看法可以认为“控制”是解决问题的策略,而不会改变算法的意义,因为算法的意义是由逻辑决定的。对同一个逻辑,使用不同控制,所得到的算法,本质是等价的,因为它们解决同样的问题,并得到同样的结果。 43 | 44 | 因此,我们可以通过逻辑分析,来提高算法的效率,保持它的逻辑,而更好地使用这一逻辑。比如,有时用自上而下的控制替代自下而上,能提高效率。而将自上而下的顺序执行改为并行执行,也会提高效率。 45 | 46 | 总之,通过这两个表达式,我们可以得出: 47 | 48 | **Program = Logic + Control + Data Structure** 49 | 50 | 前面讲了这么多的编程范式,或是程序设计的方法。其实,我们都是在围绕着这三件事来做的。比如: 51 | 52 | * 就像函数式编程中的 Map/Reduce/Filter,它们都是一种控制。而传给这些控制模块的那个 Lambda 表达式才是我们要解决的问题的逻辑,它们共同组成了一个算法。最后,我再把数据放在数据结构里进行处理,最终就成为了我们的程序。 53 | * 就像我们 Go 语言的委托模式的那个 Undo 示例一样。Undo 这个事是我们想要解决的问题,是 Logic,但是 Undo 的流程是控制。 54 | * 就像我们面向对象中依赖于接口而不是实现一样,接口是对逻辑的抽象,真正的逻辑放在不同的具现类中,通过多态或是依赖注入这样的控制来完成对数据在不同情况下的不同处理。 55 | 56 | 如果你再仔细地结合我们之前讲的各式各样的编程范式来思考上述这些概念的话,你是否会觉得,所有的语言或编程范式都在解决上面的这些问题。也就是下面的这几个事。 57 | 58 | * Control 是可以标准化的。比如:遍历数据、查找数据、多线程、并发、异步等,都是可以标准化的。 59 | * 因为 Control 需要处理数据,所以标准化 Control,需要标准化 Data Structure,我们可以通过泛型编程来解决这个事。 60 | * 而 Control 还要处理用户的业务逻辑,即 Logic。所以,我们可以通过标准化接口 / 协议来实现,我们的 Control 模式可以适配于任何的 Logic。 61 | 62 | 上述三点,就是编程范式的本质。 63 | 64 | * **有效地分离 Logic、Control 和 Data 是写出好程序的关键所在!** 65 | * **有效地分离 Logic、Control 和 Data 是写出好程序的关键所在!** 66 | * **有效地分离 Logic、Control 和 Data 是写出好程序的关键所在!** 67 | 68 | 我们在写代码当中,就会看到好多这种代码,会把控制逻辑和业务逻辑放在一块。里面有些变量和流程是跟业务相关的,有些是不相关的。业务逻辑决定了程序的复杂度,业务逻辑本身就复杂,你的代码就不可能写得简单。 69 | 70 | Logic,它是程序复杂度的的下限,然后,我们为了控制程序,需要再搞出很多控制代码,于是 Logic+Control 的相互交织成为了最终的程序复杂度。 71 | 72 | ### 把逻辑和控制混淆的示例 73 | 74 | 我们来看一个示例,这是我在 leetcode 上做的一道题,这是通配符匹配,给两个字符串匹配。需求如下: 75 | 76 | ``` 77 | 通配符匹配 78 | isMatch("aa","a") → false 79 | isMatch("aa","aa") → true 80 | isMatch("aaa","aa") → false 81 | isMatch("aa", "*") → true 82 | isMatch("aa", "a*") → true 83 | isMatch("ab", "?*") → true 84 | isMatch("aab", "c*a*b") → false 85 | ``` 86 | 87 | 现在你再看看我写出来的代码: 88 | 89 | ```c++ 90 | bool isMatch(const char *s, const char *p) { 91 | const char *last_s = NULL; 92 | const char *last_p = NULL; 93 | 94 | while ( *s != '\0' ) { 95 | if ( *p == '*' ) { 96 | p++; 97 | if ( *p == '\0' ) return true; 98 | last_s = s; 99 | last_p = p; 100 | } else if ( *p == '?' || *s == *p ) { 101 | s++; 102 | p++; 103 | } else if ( last_s != NULL ) { 104 | p = last_p; 105 | s = ++last_s; 106 | } else { 107 | return false; 108 | } 109 | } 110 | while ( *p == '*' ) p++; 111 | return *p == '\0'; 112 | } 113 | ``` 114 | 115 | 我也不知道我怎么写出来的,好像是为了要通过,我需要关注于性能,你看,上面这段代码有多乱。如果我不写注释你可能都看不懂了。就算我写了注释以后,你敢改吗?你可能连动都不敢动(哈哈)。上面这些代码里面很多都不是业务逻辑,是用来控制程序的逻辑。 116 | 117 | 业务逻辑是相对复杂的,但是控制逻辑跟业务逻辑交叉在一块,虽然代码写得不多,但是这个代码已经够复杂了。两三天以后,我回头看,我到底写的什么,我也不懂,为什么会写成这样?我当时脑子是怎么想的?我完全不知道。我现在就是这种感觉。 118 | 119 | 那么,怎么把上面那段代码写得更好一些呢? 120 | 121 | * 首先,我们需要一个比较通用的状态机(NFA,非确定有限自动机,或者 DFA,确定性有限自动机),来维护匹配的开始和结束的状态。这属于 Control。 122 | * 如果我们做得好的话,还可以抽象出一个像程序的文法分析一样的东西。这也是 Control。 123 | * 然后,我们把匹配 * 和 ? 的算法形成不同的匹配策略。 124 | 125 | 这样,我们的代码就会变得漂亮一些了,而且也会快速一些。 126 | 127 | 这里有篇正则表达式的高效算法的论文[Regular Expression Matching Can Be Simple And Fast](https://swtch.com/~rsc/regexp/regexp1.html),推荐你读一读,里面有相关的实现,我在这里就不多说了。 128 | 129 | 这里,想说的程序的本质是 Logic+Control+Data,而其中,Logic 和 Control 是关键。注意,这个和系统架构也有相通的地方,逻辑是你的业务逻辑,逻辑过程的抽象,加上一个由术语表示的数据结构的定义,控制逻辑跟你的业务逻辑是没关系的,你控制,它执行。 130 | 131 | 控制一个程序流转的方式,即程序执行的方式,并行还是串行,同步还是异步,以及调度不同执行路径或模块,数据之间的存储关系,这些和业务逻辑没有关系。 132 | 133 | ![](./pic_1.png) 134 | 135 | 如果你看过那些混乱不堪的代码,你会发现其中最大的问题是我们把这 Logic 和 Control 纠缠在一起了,所以会导致代码很混乱,难以维护,Bug 很多。绝大多数程序复杂的原因就是这个问题,就如同下面这幅图中表现的情况一样。 136 | 137 | ![](./pic_2.png) 138 | 139 | ### 再来一个简单的示例 140 | 141 | 这里给一个简单的示例。 142 | 143 | 下面是一段检查用户表单信息的常见代码,我相信这样的代码你见得多了。 144 | 145 | ```javascript 146 | function check_form_x() { 147 | var name = $('#name').val(); 148 | if (null == name || name.length <= 3) { 149 | return { status : 1, message: 'Invalid name' }; 150 | } 151 | 152 | var password = $('#password').val(); 153 | if (null == password || password.length <= 8) { 154 | return { status : 2, message: 'Invalid password' }; 155 | } 156 | 157 | var repeat_password = $('#repeat_password').val(); 158 | if (repeat_password != password.length) { 159 | return { status : 3, message: 'Password and repeat password mismatch' }; 160 | } 161 | 162 | var email = $('#email').val(); 163 | if (check_email_format(email)) { 164 | return { status : 4, message: 'Invalid email' }; 165 | } 166 | 167 | ... 168 | 169 | return { status : 0, message: 'OK' }; 170 | 171 | } 172 | ``` 173 | 174 | 但其实,我们可以做一个 DSL+ 一个 DSL 的解析器,比如: 175 | 176 | ```javascript 177 | var meta_create_user = { 178 | form_id : 'create_user', 179 | fields : [ 180 | { id : 'name', type : 'text', min_length : 3 }, 181 | { id : 'password', type : 'password', min_length : 8 }, 182 | { id : 'repeat-password', type : 'password', min_length : 8 }, 183 | { id : 'email', type : 'email' } 184 | ] 185 | }; 186 | 187 | var r = check_form(meta_create_user); 188 | ``` 189 | 190 | 这样,DSL 的描述是“Logic”,而我们的 check_form 则成了“Control”,代码就非常好看了。 191 | 192 | ## 小结 193 | 194 | 代码复杂度的原因: 195 | 196 | * 业务逻辑的复杂度决定了代码的复杂度; 197 | * 控制逻辑的复杂度 + 业务逻辑的复杂度 ==> 程序代码的混乱不堪; 198 | * 绝大多数程序复杂混乱的根本原因:**业务逻辑与控制逻辑的耦合**。 199 | 200 | 如何分离 control 和 logic 呢?我们可以使用下面的这些技术来解耦。 201 | 202 | * **State Machine** 203 | * 状态定义 204 | * 状态变迁条件 205 | * 状态的 action 206 | 207 | * DSL – Domain Specific Language 208 | * HTML,SQL,Unix Shell Script,AWK,正则表达式…… 209 | 210 | * 编程范式 211 | * 面向对象:委托、策略、桥接、修饰、IoC/DIP、MVC…… 212 | * 函数式编程:修饰、管道、拼装 213 | * 逻辑推导式编程:Prolog 214 | 215 | **这就是编程的本质**: 216 | 217 | * Logic 部分才是真正有意义的(What) 218 | * Control 部分只是影响 Logic 部分的效率(How) 219 | 220 | -------------------------------------------------------------------------------- /31_编程范式游记-编程的本质/pic_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/31_编程范式游记-编程的本质/pic_1.png -------------------------------------------------------------------------------- /31_编程范式游记-编程的本质/pic_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/31_编程范式游记-编程的本质/pic_2.png -------------------------------------------------------------------------------- /32_编程范式游记-逻辑编程/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(10)- 逻辑编程范式 2 | 3 | 这篇文章重点介绍 Prolog 语言。Prolog(Programming in Logic)是一种逻辑编程语言,它创建在逻辑学的理论基础之上,最初被运用于自然语言等研究领域。现在它已被广泛地应用在人工智能的研究中,可以用来建造专家系统、自然语言理解、智能知识库等。 4 | 5 | Prolog 语言最早由艾克斯马赛大学(Aix-Marseille University)的 Alain Colmerauer 与 Philippe Roussel 等人于 20 年代 60 年代末研究开发的。1972 年被公认为是 Prolog 语言正式诞生的年份,自 1972 年以后,分支出多种 Prolog 的方言。 6 | 7 | 最主要的两种方言为 Edinburgh 和 Aix-Marseille。最早的 Prolog 解释器由 Roussel 建造,而第一个 Prolog 编译器则是 David Warren 编写的。 8 | 9 | Prolog 一直在北美和欧洲被广泛使用。日本政府曾经为了建造智能计算机而用 Prolog 来开发 ICOT 第五代计算机系统。在早期的机器智能研究领域,Prolog 曾经是主要的开发工具。 10 | 11 | 20 世纪 80 年代 Borland 开发的 Turbo Prolog,进一步普及了 Prolog 的使用。1995 年确定了 ISO Prolog 标准。 12 | 13 | 有别于一般的函数式语言,Prolog 的程序是基于谓词逻辑的理论。最基本的写法是定立对象与对象之间的关系,之后可以用询问目标的方式来查询各种对象之间的关系。系统会自动进行匹配及回溯,找出所询问的答案。 14 | 15 | Prolog 代码中以大写字母开头的元素是变量,字符串、数字或以小写字母开头的元素是常量,下划线(_)被称为匿名变量。 16 | 17 | ## Prolog 的语言特征 18 | 19 | 逻辑编程是靠推理,比如下面的示例: 20 | 21 | ```prolog 22 | program mortal(X) :- philosopher(X). 23 | 24 | philosopher(Socrates). 25 | philosopher(Plato). 26 | philosopher(Aristotle). 27 | 28 | mortal_report:- 29 | write('Known mortals are:'), nl, mortal(X), 30 | write(X),nl, 31 | fail. 32 | ``` 33 | 34 | 我们可以看到下面的几个步骤。 35 | 36 | 1. 先定义一个规则:哲学家是人类。 37 | 2. 然后陈述事实:苏格拉底、亚里士多德、柏拉图都是哲学家。 38 | 3. 然后,我们问,谁是人类?于是就会输出苏格拉底、亚里士多德、柏拉图。 39 | 40 | 下面是逻辑编程范式的几个特征。 41 | 42 | * 逻辑编程的要点是将正规的逻辑风格带入计算机程序设计之中。 43 | * 逻辑编程建立了描述一个问题里的世界的逻辑模型。 44 | * 逻辑编程的目标是对它的模型建立新的陈述。 45 | * 通过陈述事实——因果关系。 46 | * 程序自动推导出相关的逻辑。 47 | 48 | ### 经典问题:地图着色问题 49 | 50 | 我们再来看一个经典的四色地图问题。任何一个地图,相邻区域不能用相同颜色,只要用四种不同的颜色就够了。 51 | 52 | ![](/Users/likejun/ProgrammersLevelUp/32_编程范式游记-逻辑编程/pic_1.png) 53 | 54 | 首先,定义四种颜色。 55 | 56 | ```prolog 57 | color(red). 58 | color(green). 59 | color(blue). 60 | color(yellow). 61 | ``` 62 | 63 | 然后,定义一个规则:相邻的两个地区不能用相同的颜色。 64 | 65 | ```prolog 66 | neighbor(StateAColor, StateBColor) :- color(StateAColor), color(StateBColor), 67 | StateAColor \= StateBColor. /* \= is the not equal operator */ 68 | ``` 69 | 70 | 最前面的两个条件:color(StateAColor) 和 color(StateBColor) 表明了两个变量 StateAColor 和 StateBColor。然后,第三个条件: StateAColor \= StateBColor 表示颜色不能相同。 71 | 72 | 接下来的事就比较简单了。我们描述事实就好了,描述哪些区域是相邻的事实。 73 | 74 | 比如,下面描述了 BW 和 BY 是相邻的。 75 | 76 | germany(BW, BY) :- neighbor(BW, BY). 77 | 78 | 下面则描述多个区 BW、 BY、 SL、 RP、 和 ND 的相邻关系: 79 | 80 | germany(BW, BY, SL, RP, HE) :- neighbor(BW, BY), neighbor(BW, RP), neighbor(BW, HE). 81 | 82 | 于是,我们就可以描述整个德国地图的相邻关系了。 83 | 84 | ```prolog 85 | germany(SH, MV, HH, HB, NI, ST, BE, BB, SN, NW, HE, TH, RP, SL, BW, BY) :- 86 | neighbor(SH, NI), neighbor(SH, HH), neighbor(SH, MV), 87 | neighbor(HH, NI), 88 | neighbor(MV, NI), neighbor(MV, BB), 89 | neighbor(NI, HB), neighbor(NI, BB), neighbor(NI, ST), neighbor(NI, TH), 90 | neighbor(NI, HE), neighbor(NI, NW), 91 | neighbor(ST, BB), neighbor(ST, SN), neighbor(ST, TH), 92 | neighbor(BB, BE), neighbor(BB, SN), 93 | neighbor(NW, HE), neighbor(NW, RP), 94 | neighbor(SN, TH), neighbor(SN, BY), 95 | neighbor(RP, SL), neighbor(RP, HE), neighbor(RP, BW), 96 | neighbor(HE, BW), neighbor(HE, TH), neighbor(HE, BY), 97 | neighbor(TH, BY), 98 | neighbor(BW, BY). 99 | ``` 100 | 101 | 最后,我们使用如下语句,就可以让 Prolog 推导到各个地区的颜色。 102 | 103 | ```prolog 104 | ?- germany(SH, MV, HH, HB, NI, ST, BE, BB, SN, NW, HE, TH, RP, SL, BW, BY). 105 | ``` 106 | 107 | ## 小结 108 | 109 | Prolog 这种逻辑编程,把业务逻辑或是说算法抽象成只关心规则、事实和问题的推导这样的标准方式,不需要关心程序控制,也不需要关心具体的实现算法。只需要给出可以用于推导的规则和相关的事实,问题就可以被通过逻辑推导来解决掉。是不是很有意思,也很好玩? 110 | 111 | 如果有兴趣,你可以学习一下,这里推荐两个学习资源: 112 | 113 | * [Prolog Tutorial](http://www.doc.gold.ac.uk/~mas02gw/prolog_tutorial/prologpages/) 114 | 115 | * [Learn Prolog Now!](http://www.let.rug.nl/bos/lpn//) 116 | 117 | -------------------------------------------------------------------------------- /32_编程范式游记-逻辑编程/pic_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/32_编程范式游记-逻辑编程/pic_1.png -------------------------------------------------------------------------------- /33_编程范式游记-程序世界里的编程范式/README.md: -------------------------------------------------------------------------------- 1 | # 编程范式游记(11)- 程序世界里的编程范式 2 | 3 | 这个世界到今天已经有很多很多的编程范式,相当复杂。下面这个图比较好地描绘了这些各式各样的编程范式,这个图越往左边就越是“声明式的”,越往右边就越不是“声明式的”(指令式的),我们可以看到,函数式编程和逻辑编程,都在左边,而右边是指令式的,有状态的,有类型的。 4 | 5 | ![](/Users/likejun/ProgrammersLevelUp/33_编程范式游记-程序世界里的编程范式/pic_1.png) 6 | 7 | 上面这个图有点乱,不过总体说来,我们可以简单地把这世界上纷乱的编程范式,分成这几类:**声明式、命令式、逻辑的、函数式、面向对象的、面向过程的**。 8 | 9 | 于是我们归纳一下,就可以得到下面这个简单的图。简单描述一下: 10 | 11 | * 中间两个声明式编程范式(函数式和逻辑式)偏向于你定义要什么,而不是怎么做。 12 | * 而两边的命令式编程范式和面向对象编程范式,偏向于怎么做,而不是要做什么。 13 | 14 | ![](/Users/likejun/ProgrammersLevelUp/33_编程范式游记-程序世界里的编程范式/pic_2.png) 15 | 16 | 我们再归纳一下,基本上来说,就是两大分支,一边是在解决数据和算法,一边是在解决逻辑和控制。 17 | 18 | ![](/Users/likejun/ProgrammersLevelUp/33_编程范式游记-程序世界里的编程范式/pic_3.png) 19 | 20 | 下面再结合一张表格说明一下这世界上四大编程范式的类别,以及它们的特性和主要的编程语言。 21 | 22 | ![](/Users/likejun/ProgrammersLevelUp/33_编程范式游记-程序世界里的编程范式/pic_4.png) 23 | 24 | 程序编程范式。一个是左脑,一个右脑。我们程序员基本上是在用左脑,左脑是理性分析,喜欢数据证据,线性思维,陷入细节,具体化的,不抽象。但是,实际上玩儿出这些东西的都在右脑,函数式,还有像逻辑式的抽象能力都在右脑。所以我们非线性的想象力都在这边,而标准化教育把我们这边已经全部干掉了,我们只剩左边。我们陷入细节,我一说 Java 是最好的程序设计语言,一堆人就来了,找各种各样的细节问题跟你纠缠。 25 | 26 | 离我们最近的是函数式编程,但既然函数式编程这么好,为什么函数式编程火不起来呢?首先,这里有个逻辑上的问题,并不是用的人越多的东西就越好。因为还要看是不是大多数人都能理解的东西。函数式编程或是声明式编程,需要的是用我们的右脑,而指令式的则需要用我们的左脑。 27 | 28 | 参看下图: 29 | 30 | ![](/Users/likejun/ProgrammersLevelUp/33_编程范式游记-程序世界里的编程范式/pic_5.png) 31 | 32 | 我们可以看到, 33 | 34 | **人的左脑的特性是**: 35 | 36 | * 理性分析型 37 | * 喜欢数据证据 38 | * 线性思维 39 | * 陷入细节 40 | * 具体化的 41 | 42 | **人的右脑的特性是**: 43 | 44 | * 直觉型 45 | * 想象力 46 | * 非线性 47 | * 宏观思维 48 | * 抽象化的 49 | 50 | 人类社会中,绝大多数人都是左脑型的人,而只有少数人是右脑型的人,比如那些哲学家、艺术家,以及能够创造理论知识的人。这些人在这个世界上太少了。 51 | 52 | 这是为什么很多人理解和使用声明式的编程范式比较有困难,因为这要用你的右脑,但是我们习惯于用我们的左脑,左脑用多了以后右脑就有点跟不上了。 53 | 54 | 说到人类的大脑了,已经到了不是我专长的地方了,这个话题太大了,所以,也是时候结束《编程范式游记》这一系列文章了。希望你能从这一系列文章中有所收获。如果有什么疑问或是我有什么没有讲对的,还希望得到你的批评和指正。先谢谢了。 55 | 56 | -------------------------------------------------------------------------------- /33_编程范式游记-程序世界里的编程范式/pic_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/33_编程范式游记-程序世界里的编程范式/pic_1.png -------------------------------------------------------------------------------- /33_编程范式游记-程序世界里的编程范式/pic_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/33_编程范式游记-程序世界里的编程范式/pic_2.png -------------------------------------------------------------------------------- /33_编程范式游记-程序世界里的编程范式/pic_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/33_编程范式游记-程序世界里的编程范式/pic_3.png -------------------------------------------------------------------------------- /33_编程范式游记-程序世界里的编程范式/pic_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/33_编程范式游记-程序世界里的编程范式/pic_4.png -------------------------------------------------------------------------------- /33_编程范式游记-程序世界里的编程范式/pic_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leesper/ProgrammersLevelUp/e3df313a195bfc353c1b6ba621b0721520bc2698/33_编程范式游记-程序世界里的编程范式/pic_5.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ProgrammersLevelUp 2 | 用20年时间跟着皓叔刷“程序员练级攻略” 3 | 4 | ## 目录 5 | 6 | * 开篇词 7 | * 入门篇 8 | * 零基础启蒙 9 | * 正式入门 10 | * 修养篇 11 | * 程序员修养 12 | * 专业基础篇 13 | * 编程语言 14 | * 理论学科 15 | * 系统知识 16 | * 软件设计篇 17 | * 软件设计 18 | * 高手成长篇 19 | * Linux 系统、内存和网络(系统底层知识) 20 | * 异步 I/O 模型和 Lock-Free 编程(系统底层知识) 21 | * Java 底层知识 22 | * 数据库 23 | * 分布式架构入门(分布式架构) 24 | * 分布式架构经典图书和论文(分布式架构) 25 | * 分布式架构工程设计(分布式架构) 26 | * 微服务 27 | * 容器化和自动化运维 28 | * 机器学习和人工智能 29 | * 前端基础和底层原理(前端方向) 30 | * 前端性能优化和框架(前端方向) 31 | * UI/UX 设计(前端方向) 32 | * 技术资源集散地 33 | 34 | ## 皓叔谈攻略的正确打开方式 35 | 36 | 到这里,我估计《程序员练级攻略》系列文章你都已经了解个大概了,不知道此时此刻你有什么样的感受?这份攻略其实是给了一个进阶的地图,也罗列了很多书籍和文档。但我可以确定地说,只是看这些列表,你肯定会抱怨说头都要大了,而且,你可能还会觉得纸上谈兵,不知道怎么把这些知识转变成自己的能力,尤其是你的工作中没有这些场景,你都可能不知道怎么实操。 37 | 38 | 所以,在这里,我把我个人相关的实践都写一下,这样会让你更好地掌握这份攻略。如果大家有更好的方法,也欢迎留言。 39 | 40 | 对于本攻略来说,你并不需要按顺序学习,你可以从自己喜欢的切入点,按自己喜欢的路线学习,通常来说,有如下的一些注意事项。 41 | 42 | * 《入门篇》和《专业基础篇》中的那些书和文章,你肯定是得认真精读的,这是基础。但是也没有必要揪住细节不放,重要的是知道这个技术的“解题思路”,抓住其中的重点,一个技术的关键点就那么几个。 43 | 44 | * 《高手成长篇》的相关书籍、文章和论文,你不一定全读,可以挑感兴趣的内容研究。 45 | 46 | * 《修养篇》和《设计篇》里的内容,你可能要经常拿出来读,因为这些都是经验,随着你的成长,以及阅历的增加,你每次读都会收获更多新东西,正所谓常看常新。另外,你还可以顺着这些东西找到更多的“修养”和“设计”。 47 | 48 | 但是读这些资料,很多人都是记忆式的学习方式。但,你也知道,记忆学习是简单粗暴的,所以也很容易忘,如果你不实操一下,就不会有具体、真实的感觉。所以,一定要动手实践。 49 | 50 | 下面是一些配合程序员练级攻略中技术成长的相关的建议。 51 | 52 | **首先,你需要建一个自己的实验室**。咱们讲了很多内容,看完之后,你要动起来,徒手把环境搭出来,写一些实验性的程序验证或感受一下相关的技术点,出了问题也要自己进行调试和修复。因为只有这样,你才可以获得一些感性认识。 53 | 54 | * 《入门篇》和《专业基础篇》都有很多的编程语言要学,你并不需要一下全部都学,但是为了你可以一个人 solo,你需要至少学一个后端和一个前端语言,我给你的建议是 Java 和 JavaScript。 55 | 56 | * 在《入门篇》和《专业基础篇》我都给了一些实践项目,如果你没有太多的工作经验,这些实践项目会对你的学习非常有帮助。因为在实现代码的时候,你会遇到很多细节问题,这些细节问题会倒逼你去看文档,去 Google,去提问,这相当于是把你扔到具体的问题场景里锻炼你、打磨你。 57 | 58 | * 对于《数据结构》,其实都是在围绕增删改查的相关性能,在平衡时间和空间。对于《算法》则要么这些数据结构的操作,要么就是数学逻辑的推导,比如动态规划。这些东西可能在你的生活当中用不到,但是你可以把它作为一个脑筋体操来不断训练自己的数学思维。 59 | 60 | * 对于《高手成长篇》中的很多东西,也是需要你自己先搭个环境,自己写一些 Hello World 式的程序先体会一下那些知识。比如内存分配、异步 I/O 模型、locker-free、JVM 和字节码操作,还有浏览器原理等等这些东西,写几个小程序就可以体会到了。而还有一些中间件的知识,你也是可以搭个环境自己玩玩,并且最好能够搭出一些比较高级的用法。 61 | 62 | **其次,把你的实验室升级成一个工作室**。工作室和实验室不一样的地方是,实验室只是在做一些验证型的实验,以跑通一个小技术功能为主。而工作室则是要以完成一个比较完整的软件功能为主,也就是说,可以让别人 / 用户来用的东西(哪怕很丑很难用,但是可以让别人来用)。这个阶段,我给你如下的几个建议。 63 | 64 | * 你得选用一个主流的开发框架,并且在写这个软件的时候,你需要有一定的修养,比如有不错的编程风格,追求代码的可读性,有一定的测试案例,等等这些我们在《修养篇》和《软件设计篇》里提到的东西。这个时候,你需要大量学习一些优秀项目的代码,因为你可以在开源软件中找到一些不错的代码实现(你可以做一些源码分析的事,但不是去整理其中的编程逻辑,而是要去学习代码组织的方法)。然后你需要照葫芦画瓢似的练习,无论你完成得好不好其实都没有关系,这就像画画一样,一开始总是画的很不好的,但是只要你坚持,并且多思考别人为什么要写成那样,那么,我相信你提高得也会很快。 65 | 66 | * 你需要完成一个能用的项目,对于选择什么样的项目,这里,我也有几个建议。第一,从自己的痛点出发,写一个能解决自己问题的东西。第二,临摹别人的作品,复刻一个其它的成功产品。有人说,学好一门语言或是一个开源软件最好的方式,就是用想学 / 喜欢的编程语言翻译下这个开源软件,比如,你用 Go 语言翻译一下某个 Java 的组件。第三,深度参与一些你喜欢的开源项目。第四,在工作中找到风险可控的项目和需求。 67 | 68 | * 你最好跟别人一起组队升级打怪。这里需要注意的是,一定要找好队友,要那种有热情,爱专研,能相互打气的队友,千万别找那些为自己的不努力找各种各样借口的人。 69 | 70 | * 在这个工作室中,你还可以尝试使用各种前沿的或是你没有玩过的技术和中间件。这里,你需要注意的是你一定要使用一些高级技术,比如一些高级算法,或是分布式技术等。 71 | 72 | * 当你的东西做好后,一定要做压力测试或 Benchmark,这样你才知道自己产品与其他软件的差距,然后还会逼着你对自己的系统或软件进行调优。 73 | 74 | **最后,把你的工作室升级成工厂**。工作室与工厂最大的差别就是,工作室是比较自我比较随意的,而工厂是有相关的工业标准的,是有一整套的规范和标准的。对此,有如下的一些建议: 75 | 76 | * 当有了“工作室”的能力后,一般来说,你就可以去头部的互联网公司或是一些技术公司了。但是你一定要在一些核心的项目或产品工作,也就是说,你要在那些有技术挑战的地方工作,并在那里收割更多的经验和技能。 77 | 78 | * 你需要读各种各样的 RFC、论文、Specificaiton、标准化文档,还要使用工业级的工程能力要求自己,比如,CI/CD 这样的软件流程。你得不断告诉自己,把代码提高到可维护、可扩展,甚至可重用的级别。 79 | 80 | * 你必须对技术有更深入的了解,对软件开发的套路和各种 trade-off 还有各种解决方案的优缺点都非常熟悉。这就需要你了解软件内部的设计和原理,并知道优缺点和使用场景。 81 | 82 | * 你需要开始追求软件运行的 SLA,也就是能在什么样的性能下做到多少个 9。还要关注系统的可运维性,也就是你需要为你的软件做很多的配套设施。就像为了汽车,建加油站,建 4S 店,建公路,建交通管理部门…… 83 | 84 | * 你需要找那些有工业素养的工程师一起讨论或工作。这类的工程师有丰富的工作和项目经验,也见过大世面。他们通常来说会对外有输出(不是那些写微信公众号的人,或是在知乎上输出的人,而是那些在软件开发工作上有丰富工程经验的人) 85 | 86 | * 这个时候,对于你要做的软件,你不仅仅只是为了完成,你追求的是一种技术高度,追求那种严谨和科学的态度。你已经把这个软件当成了自己作品,变成了自己的名片,你在等待接受别人的学习和膜拜。 87 | 88 | 好了,基本就是上面这些,你还要记住我的学习能力一文中的那个学习金字塔,在上面的过程中不断地输出你的认识和体会。 89 | 90 | 最后,我用下面的几个观点来结束这篇文章,希望对你有所帮助: 91 | 92 | * 带着一些具体的问题来学习,能够让你更有感觉,也容易获得正反馈和成就感。 93 | 94 | * 开拓视野,尽可能只读英文社区的一手文章,这样你会得到更有营养的知识。 95 | 96 | * 多问为什么,为什么要设计成这样,为什么要有这个技术,到底解决了什么样的问题?这会让你对技术有更深的认识。 97 | 98 | * 学会归纳总结,在不同的技术中找到相似或是相同的东西,更容易让你触及技术的本质。 99 | 100 | * 把自己的理解用自己的语言表达出来,对外输出,这是最好的学习方式。 101 | 102 | * “动手”和“坚持”,这是一个动手能力很强的学科,不动手,你什么都不可能学精、学深。这是一个需要你不断坚持的事,在这条路上,你有很多很多的理由可以让你放弃,只有坚持才有可能有突破。 --------------------------------------------------------------------------------