[陈皓],网名“左耳朵耗子”,人称耗子叔,资深技术专家。
20年软件开发及相关工作经验,先后在阿里巴巴、亚马逊、汤森路透等知名公司任职。
对IT底层技术平台和大规模分布式系统的基础架构有深入的了解。
从2002年开始写技术博客,到2009年左右在独立域名CoolShell.cn(酷壳)上分享技术观点和实践总结。
文章观点鲜明、文风犀利。
R.I.P. = Requiescat/Rest in Peace,天主教会葬礼祈祷词,希望逝者永享安宁
左耳听风
极客时间 专栏
- 学习是没有捷径的,是逆人性的。
- 这个世界上的学习只有两种,一种是被动学习,如:听课,看书,看视频,看别人的演讲,留存度最多只有 30%;一种是主动学习,如:与别人讨论,实践和传授给别人,可以掌握知识的 50% 到 90% 以上。
技术基础
程序猿技术变现
要去经历大多数人经历不到的,要把学习时间花在那些比较难的地方。
要写文章就要写没有人写过的,或是别人写过,但我能写得更好的。
更重要的是,技术和知识完全是可以变现的。
你得让自己身边的人有求于你,或是向别人推荐你。
关注技术付费点。技术付费点基本体现在两个地方,一个是,能帮别人“挣钱”的地方;另一个是,能帮别人“省钱”的地方。
会挣钱的人一定是会投资的人。最宝贵的财富并不是钱,而是你的时间,时间比钱更宝贵,因为钱你不用还在那里,而时间你不用就浪费掉了。你把你的时间投资在哪些地方,就意味着你未来会走什么样的路。所以,利用好你的时间,投到一些有意义的地方吧。
数据安全
所谓的安全方案基本上来说就是能够把这个风险控制在一个很小的范围。
技术领导力
技术领导力不仅仅是呈现出来的技术,而是一种可以获得绝对优势的技术能力。
总是在提供解决问题的思路和方案的人才是有技术领导力的人。
技术领导力是:
- 尊重技术,追求核心基础技术。
- 追逐自动化的高效率的工具和技术,同时避免无效率的组织架构和管理。
- 解放生产力,追逐人效的提高。
- 开发抽象和高质量的可以重用的技术组件。
- 坚持高于社会主流的技术标准和要求。
如何拥有技术领导力:(夯实基础,提升能力)
- 吃透基础技术。
基础技术是各种上层技术共同的基础。
编程部分:C语言,编程范式,算法和数据结构
系统部分:计算机系统原理,操作系统原理和基础,网络基础,数据库原理,分布式技术架构。 - 提高学习能力。
所谓学习能力,就是能够很快地学习新技术,又能在关键技术上深入的能力。
学习的信息源,与高手交流,举一反三的思考,不怕困难的态度,开放的心态。 - 坚持做正确的事。
做正确的事,比用正确的方式做事更重要,因为这样才始终会向目的地靠拢。
提高效率的事,自动化的事,掌握前沿技术的事,知识密集型的事,技术驱动的事。 - 高标准要求自己。
只有不断地提高标准 ,你才可能越走越高,所以,要以高标准要求自己,不断地反思、总结和审视自己,才能够提升自己。
Google的自我评分卡;敏锐的技术嗅觉;强调实践,学以致用;Lead by Example,永远在编程。
从人类社会的发展过程中来看,基本上可以总结为几个发展阶段:
- 第一个阶段:野蛮开采。
- 第二个阶段:资源整合。
- 第三个阶段:精耕细作。
- 第四个阶段:发明创造。
新技术
一个技术能不能发展起来,关键还要看三点:有没有一个比较好的社区,有没有一个工业化的标准,有没有一个或多个杀手级应用。
软件工程中的核心就是软件能力的复用。
渴望、热情和选择
学习这事其实挺反人性的。反人性的事基本上都是要付出很多,而且还要坚持很久。所以,如果对学习没有渴望的话,或是不能从学习中找到快乐的话,那么其实是很难坚持的,无论你有没有时间。
在职场上,审视自己的最佳方式,就是隔三差五就出去面试一把,看看自己在市场上能够到什么样的级别。
人最害怕的不是自己什么都不会,而是自己不知道自己不会。
Leader 领头人
帮助别人其实就是帮助自己,成就他人其实也是在成就自己。
成为众人愿意追随的Leader的素质:
- 帮人解决问题。
- 被人依赖。
- 赢得他人的信任。
- 开放的心态 + 倾向性的价值观。
- Lead by Example 以身作则。ABC = Always Be Coding 终身写代码
- 保持热情和冲劲。
- 能够抓住重点,看透事物的本质。
- 描绘令人激动的方向,提供令人向住的环境。
- 甘当铺路石,为他人创造机会。
BOSS vs LEADER:
BOSS | LEADER |
---|---|
驱动员工 | 指导员工 |
制造畏惧 | 制造热情 |
面对错误,喜欢使用人事惩罚手段 | 面对错误,喜欢寻找解决问题的技术或管理方法 |
只知道怎么做 | 展示怎么做 |
用人 | 发展人 |
从团队收割成绩 | 给予团队成绩 |
喜欢命令和控制 | 喜欢沟通和协作 |
喜欢说,“给我上” | 喜欢说,“跟我上” |
错误处理
错误分类:资源的错误,程序的错误,用户的错误。
同步编程
使用错误返回码或异常捕获。
对于我们觉得可能会发生的事,使用错误返回码;
对于我们并不期望会发生的事,我们可以使用异常捕获。
异步编程
JavaScript Promise,Java CompletableFuture,Go 自定义+sync.WaitGroup
错误处理的最佳实践:
- 统一分类的错误字典。
- 同类错误的定义最好是可以扩展的。
- 定义错误的严重程度。
Fatal表示重大错误
Error表示资源或需求得不到满足
Warning表示并不一定是个错误但还是需要引起注意
Info表示不是错误只是一个信息
Debug表示这是给内部开发人员用于调试程序的 - 错误日志的输出最好使用错误码,而不是错误信息。
- 忽略错误最好有日志。
- 对于同一个地方不停的报错,最好不要都打到日志里。
- 不要用错误处理逻辑来处理业务逻辑。
- 对于同类的错误处理,用一样的模式。
- 尽可能在错误发生的地方处理错误。
- 向上尽可能地返回原始的错误。
- 处理错误时,总是要清理已分配的资源。
- 不推荐在循环体里处理错误。
- 不要把大量的代码都放在一个try语句块内。
- 为你的错误定义提供清楚的文档以及每种错误的代码示例。
- 对于异步的方式,推荐使用Promise模式处理错误。
- 对于分布式的系统,推荐使用APM相关的软件。
APM = Application Performance Management 应用性能管理
机器学习
机器学习主要来说有两种方法:
- 监督式学习(Supervised Learning)
需要提供一组学习样本,包括相关的特征数据以及相应的标签。程序可以通过这组样本来学习相关的规律或是模式,然后通过得到的规律或模式来判断没有被打过标签的数据是什么样的数据。
这组数据叫样本数据或训练数据(training data)。
经典算法:- 决策树(Decision Tree)。比如自动化放贷、风控。
- 朴素贝叶斯分类(Native Bayesian classification)。可以用于判断垃圾邮件,对新闻的类别进行分类,比如科技、政治、运动,判断文本表达的感情是积极的还是消极的,以及人脸识别等。
- 最小二乘法(Ordinary Least Squares Regression)。算是一种线性回归。
- 逻辑回归(Logistic Regression)。一种强大的统计学方法,可以用一个或多个变量来表示一个二项式结果。它可以用于信用评分、计算营销活动的成功率、预测某个产品的收入等。
- 支持向量机(Support Vector Machine,SVM)。可以用于基于图像的性别检测,图像分类等。
- 集成方法(Ensemble methods)。通过构建一组分类器,然后根据它们的预测结果进行加权投票来对新的数据点进行分类。原始的集成方法是贝叶斯平均,但是最近的算法包括纠错输出编码、Bagging和Boosting。
- 非监督式学习(Unsupervised Learning)
数据是没有被标注过的,相关的机器学习算法需要找到这些数据中的共性。因为大量的数据是没有被标识过的,所以这种学习方式可以让大量未标识的数据能够更有价值。
经典算法:- 聚类算法(Clustering Algorithms)。聚类算法有很多,目标是给数据分类。
- 主成分分析(Principal Component Analysis,PCA)。PCA的一些应用包括压缩、简化数据,便于学习和可视化等。
- 奇异值分解(Singular Value Decomposition,SVD)。实际上,PCA是SVD的一个简单应用。在计算机视觉中,第一个人脸识别算法使用PCA和SVD来将面部表示为“特征面”的线性组合,进行降维,然后通过简单的方法将面部匹配到身份。虽然现代方法更复杂,但很多方面仍然依赖于类似的技术。
- 独立成分分析(Independent Component Analysis,ICA)。ICA是一种统计技术,主要用于揭示随机变量、测量值或信号集中的隐藏因素。
监督式学习是在被告诉过正确的答案之后的学习,而非监督式学习是在没有被告诉正确答案时的学习。
时间管理
只有将使用时间的主动权掌握在自己手上,才能更好地利用时间,才能更为高效率的工作。所以,这才是时间管理的关键点。
- 争取时间
- 主动管理
你要主动管理的不是你的时间,而是管理你的同事,管理你的信息。 - 学会说“不”
给出另一个你可以做到的方案,而不是把对方的方案直接回绝掉。
我不说我不能完全满足你,但我说我可以部分满足你。
我不能说不,但是我要有条件地说是。而且,我要把你给我的压力再反过来还给你,看似我给了需求方选择,实际上,我掌握了主动。
在“积极主动的态度下对于不合理的事讨价还价”。只有学会了说“不”,你才能够控制好你的时间。 - 加班和开会
开会,不是讨论问题,而是讨论方案,开会不是要有议题,而是要有议案。
- 利用时间
- 投资自己的时间
花时间学习基础知识,花时间读文档。
花时间在解放自己生产力的事上。
花时间在让自己成长的事上。晋升并不代表成长,成长不应该只看在一个公司内,而是要看在行业内,在行业内的成长才是真正的成长。
花时间在建立高效的环境上。 - 规划自己的时间
定义好优先级。
最短作业优先。
想清楚再做。
关注长期利益规划。工作上的事你永远也做不完的,长痛不如短痛。 - 用好自己的时间
将军赶路不追小兔。
形成习惯。
形成正反馈。
反思和举一反三。
故障处理
- 应对故障
故障发生时
出现故障时,最重要的不是debug故障,而是尽可能地减少故障的影响范围,并尽可能快地修复问题。
故障源团队恢复系统的手段:重启和限流,回滚操作,降级操作,紧急更新。故障前的准备工作
以用户功能为索引的服务和资源的全视图。
为地图中的各个服务制定关键指标,以及一套运维流程和工具,包括应急方案。
设定故障的等级。
故障演练。要提升故障处理水平,最好的方式就是实践。见得多了,处理得多了,才能驾轻就熟。
灰度发布系统。
- 故障改进
解决一个故障可以通过技术和管理两方面的方法。我是一个技术人员,我更愿意使用技术手段。
对于故障处理,一个技术问题,后面隐藏的是工程能力问题,工程能力问题后面隐藏的是管理问题,管理问题后面隐藏的是一个公司文化的问题,公司文化的问题则隐藏着创始人的问题……
原则:
- 举一反三解决当下的故障。为自己赢得更多的时间。
- 简化复杂、不合理的技术架构、流程和组织。你不可能在一个复杂的环境下根本地解决问题。
- 全面改善和优化整个系统,包括组织。解决问题的根本方法是改善和调整整体结构。而只有简单优雅的东西才有被改善和优化的可能。
表象和本质
兴趣和投入
兴趣只是开始,而能让人不断投入时间和精力的则是正反馈,是成就感。
你需要找到让自己能够更有成就感的事情,兴趣总是可以培养的。学习和工作
本质上来说,并不是只有找到了相应的工作我们才可以学好一项技术,而是,我们在通过解决实际问题,在和他人讨论,获得高手帮助的环境中,才能更快更有效率地学习和成长。
找工作不只是找用这个技术的工作,更是要找场景,找实际问题,找团队。这些才是本质。
工作不过是提供了一个能够解决实际问题,能跟人讨论,有高手帮助的环境。
让我们成长的并不是工作本身,而是有利于学习的环境。
找到学习的方法,提升自己对新事物学习的能力,才是学习和成长的关键。技术和价值
技术无贵贱。能规模化低成本高效率解决实际问题的技术(自动化技术)及其基础技术,就算是很low,也是很有价值的。趋势和未来
这个世界的技术趋势和未来其实是被人控制的。就是被那些有权有势有钱的公司或国家来控制的。当然,他们控制的不是长期的未来,但短期的未来(3-5年)一定是他们控制着的。
Git协同工作流
协同工作流的本质,并不是怎么玩好代码仓库的分支策略,而是玩好我们的软件架构和软件开发流程。
与其花时间在Git协同工作流上,还不如把时间花在调整软件架构和自动化软件生产和运维流程上来,这才是真正简化协同工作流程的根本。
以微服务或是SOA为架构的方式。
以DevOps为主的开发流程。
推荐书单:
《代码大全》
《程序员修练之道》
《计算机的构造和解释》
《算法导论》
《设计模式》
《重构》
《人月神话》
《代码整洁之道》
《Effective C++》
《More Effective C++》
《Effective Java》
分布式架构
分布式系统架构
单体应用和分布式架构的优缺点:
传统单体架构 | 分布式服务化架构 | |
---|---|---|
新功能开发 | 需要时间 | 容易开发和实现 |
部署 | 不经常且容易部署 | 经常发布,部署复杂 |
隔离性 | 故障影响范围小 | 故障影响范围小 |
架构设计 | 难度小 | 难度级数增加 |
系统性能 | 响应时间快,吞吐量小 | 响应时间慢,吞吐量大 |
系统运维 | 运维简单 | 运维复杂 |
新人上手 | 学习曲线大(应用逻辑) | 学习曲线大(架构逻辑) |
技术 | 技术单一且封闭 | 技术多样且开发 |
测试和查错 | 简单 | 复杂 |
系统扩展性 | 扩展性很差 | 扩展性很好 |
系统管理 | 重点在于开发成本 | 重点在于服务治理和调度 |
使用分布式系统主要有两方面原因:
增大系统容量。
加强系统可用。
系统架构演化:
- 20世纪70年代,模块化编程
- 20世纪80年代,面向事件设计
- 20世纪90年代,基于接口/组件设计
- 21世纪初,SOA = Service-Oriented Architecture 面向服务架构
SOA架构是构造分布式计算应用程序的方法。
它将应用程序功能作为服务发送给最终用户或者其他服务。
它采用开放标准与软件资源进行交互,并采用标准的表示方式。
开发、维护和使用SOA要遵循的基本原则:
- 可重用,粒度合适,模块化,可组合,构件化以及有互操作性。
- 符合开放标准(通用的或行业的)。
- 服务的识别和分类,提供和发布,监控和跟踪。
分布式系统中需要注意的问题:
- 问题一:异构系统的不标准问题
一个好的配置管理,应该分成三层:底层和操作系统相关,中间层和中间件相关,最上面和业务应用相关。 - 问题二:系统架构中的服务依赖性问题
- 问题三:故障发生的概率更大
机器自动化。人管代码,代码管机器,人不管机器! - 问题四:多层架构的运维复杂度更大
系统分成四层:基础层、平台层、应用层和接入层。- 基础层就是机器、网络和存储设备等。
- 平台层就是中间件层,Tomcat、MySQL、Redis、Kafka之类的软件。
- 应用层就是业务软件,比如,各种功能的服务。
- 接入层就是接入用户请求的网关、负载均衡或是CDN、DNS。
分工不是问题,问题是分工后的协作是否统一和规范。
构建分布式服务需要从组织,到软件工程,再到技术上的一次大的改造,需要比较长的时间来磨合和改进,并不断地总结教训和成功经验。
分布式系统的技术栈
Orchestration 编排
分布式系统需要干的两件事:
一是提高整体架构的吞吐量,服务更多的并发和流量。
二是为了提高系统的稳定性,让系统的可用性更高。
提高系统性能的技术:
提高系统稳定性的技术:
分布式系统四个核心关键技术:
全栈监控
好的监控系统:服务调用链跟踪,服务调用时长分布,服务的TOP N视图,数据库操作关联。服务调度
对于故障迁移,也就是服务的某个实例出现问题时,需要自动地恢复它。
对于服务来说,有两种模式:
- 宠物模式。就是一定要救活,主要是对于stateful的服务。
- 奶牛模式。就是不救活了,重新生成一个实例。
- 流量与数据调度
服务治理是内部系统的、数据中心的事。
服务治理是流量调度的前提。
一个好的API Gateway需要具备关键技术:
高性能,使用集群技术扛流量,简单的业务逻辑,使用Admin API管理配置变更的服务化。
数据副本是分布式系统解决数据丢失异常的唯一手段。
在解决数据副本间的一致性问题时的技术方案:
- Master-Slave方案。
- Master-Master方案。
- 两阶段和三阶段提交方案。
- Paxos方案。
分布式系统事务处理:
PaaS
SaaS = Software as a Service 软件即服务
aPaas = Application Platform as a Service 应用程序平台即服务
PaaS = Platform as a Service 平台即服务
IaaS = Infrastructure as a Service 基础设施即服务
- 软件工程的本质:
- 第一,提高服务的SLA。
SLA = Service Level Agreement 服务级别协议:提供服务的企业与客户之间就服务的品质、水准、性能等方面所达成的双方共同认可的协议或契约。
提高系统的SLA主要表现在两个方面:高可用的系统;自动化的运维。 - 第二,能力和资源重用或复用。
软件工程另一个重要的能力就是让能力和资源可以重用。
主要表现在两个方面:软件模块的重用;软件运行环境和资源的重用。 - 第三,过程的自动化。
编程本来就是把一个重复工作自动化的过程。
把软件生产和运维的过程自动化起来表现在两个方面:软件生产流水线;软件运维自动化。
- PaaS平台 -> 云计算 | Cloud Native:
- 分布式多层的系统架构
- 服务化的能力供应
- 自动化的运维能力
一个好的PaaS平台应该具有分布式(根本特性)、服务化(本质)、自动化部署(灵魂)、高可用、敏捷以及分层开放的特征,并可与IaaS实现良好的联动。
- CAP定理
分布式系统设计中最基础,最为关键的理论。
它指出,分布式数据存储不可能同时满足以下三个条件:
- 一致性(Consistency):每次读取要么获得最近写入的数据,要么获得一个错误。
- 可用性(Availability):每次请求都能获得一个(非错误)响应,但不保证返回的是最新写入的数据。
- 分区容忍(Partition tolerance):尽管任意数量的消息被节点间的网络丢失(或延迟),系统仍继续运行。
在分布式系统中错误是不可能避免的,我们能做的不是避免错误,而是要把错误的处理当成功能写在代码中。
解决了共识问题的算法具备的三点特性:
给出一个处理者的集合,其中每一个处理者都有一个初始值:
- “终止” - 所有无错误的进程(处理过程)最终都将决定一个值。
- “一致同意” - 所有会做决定的无错误进程决定的都将是同一个值。
- “有效性” - 最终被决定的值必须被至少一个进程提出过。
“谷三篇”:Google File System、MapReduce、Bigtable
分布式的服务的调度需要一个分布式的存储系统来支持服务的数据调度。
各大公司都在分布式的数据库上做各种各样的创新,他们都在使用底层的分布式文件系统来做存储引擎,把存储和计算分离开来,然后使用分布式一致性的数据同步协议的算法来在上层提供高可用、高扩展的支持。
编程范式 Programming Paradigm
范即模范。
范式即模式、方法,是一类典型的编程风格,是指从事软件工程的一类典型的风格(“方法学”)。
编程范式其实就是程序的指导思想,它也代表了这门语言的设计方向,各有千秋。
通过使用抽象和隔离,让复杂的“世界”变得简单一些。
泛型编程 Generic Programming
- C语言
程序 = 算法 + 数据
一个通用的算法,需要对所处理的数据的数据类型进行适配。但在适配数据类型的过程中,C语言只能使用void*
或宏替换
的方式。
算法其实是在操作数据结构,而数据则是放到数据结构中的,所以,真正的泛型除了适配数据类型外,还要适配数据结构。
C语言设计目标是提供一种能以简易的方式编译、处理低层内存、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。
C语言的强大和优雅之处在于使用C语言的程序员在高级语言的特性之上还能简单地做任何底层上的微观控制。
C语言也很适合搭配汇编语言来使用。C语言把非常底层的控制权交给了程序员。
C语言的设计理念是:
- 相信程序员
- 不会阻止程序员做任何底层的事
- 保持语言的最小和最简的特性
- 保证C语言的最快的运行速度,那怕牺牲移值性
- C++
C++有效解决程序泛型问题的方式:
- 第一,通过类的方式来解决。
- 第二,通过模板达到类型和算法的妥协。
- 第三,通过虚函数和运行时类型识别。
一个良好的泛型编程需要解决的问题:算法的泛型;类型的泛型;数据结构(数据容器)的泛型。
STL的泛型方法包括:泛型的数据容器;泛型数据容器的迭代器;泛型的算法。
类型系统
类型是对内存的一种抽象。不同的类型,会有不同的内存布局和内存分配的策略。
程序语言的类型系统主要提供的功能:程序语言的安全性;利于编译器的优化;代码的可读性;抽象化。
任何语言都有类型系统。
如果一个语言强制实行类型规则(通常只允许以不丢失信息为前提的自动类型转换),那么称此处理为强类型,反之称为弱类型。
静态类型检查是在编译器进行语义分析时进行的。
在动态类型的语言中,会以类型标记维持程序所有数值的“标记”,并在运算任何数值之前检查标记。一个变量的类型是由运行时的解释器来动态标记的,可以动态地和底层的计算机指令或内存布局对应起来。
C++动用了非常繁多和复杂的技术来达到泛型编程的目标。
- 通过类中的构造、析构、拷贝构造,重载赋值操作符,标准化(隐藏)了类型的内存分配、释放和复制的操作。
- 通过重载操作符,可以标准化类型的比较等操作。
- 通过iostream,标准化了类型的输入、输出控制。
- 通过模板技术(包括模板的特化),来为不同的类型生成类型专属的代码。
- 通过迭代器来标准化数据容器的遍历操作。
- 通过面向对象的接口依赖(虚函数技术),来标准化了特定类型在特定算法上的操作。
- 通过函数式(函数对象),来标准化对于不同类型的特定操作。
泛型编程的本质就是:屏蔽掉数据和操作数据的细节,让算法更为通用,让编程者更多地关注算法的结构,而不是在算法中处理不同的数据类型。
编程语言作为机器代码和业务逻辑的粘合层,是在让程序员可以控制更多底层的灵活性,还是屏蔽底层细节,让程序员可以更多地关注于业务逻辑,这是很难两全需要trade-off的事。
不同的语言在设计上都会做相应的取舍,比如:
- C语言偏向于让程序员可以控制更多的底层细节
- Java和Python让程序员更多地关注业务功能的实现
- C++则是两者都想要,导致语言在设计上非常复杂。
函数式编程 Functional Programming
数学表达式里面的映射(mapping),输入的数据和输出的数据关系是什么样的,是用函数来定义的。
函数式编程,只关心定义输入数据和输出数据相关的关系;
核心思想是将运算过程尽量写成一系列嵌套的函数调用,关注的是做什么而不是怎么做(describe what to do, rather than how to do it);
以Stateless(无状态)和Immutable(不可变)为主要特点;
代码简洁,易于理解,能便于进行并行执行,易于做代码重构;
函数执行没有顺序上的问题,支持惰性求值,具有函数的确定性(无论在什么场景下都会得到同样的结果)。
函数式编程总结起来就是把一些功能或逻辑代码通过函数拼装方式来组织的玩法。
纯函数式:完全没有状态的函数。
特征:
- stateless 无状态
核心精神,函数不维护任何状态,即它不能存在状态。比如,你给我数据我处理完扔出来。 - immutable 不可变
输入数据是不能动的,动了输入数据就有危险,所以要返回新的数据集。
优势:
- 没有状态就没有伤害。
- 并行执行无伤害。
- Copy-Paste重构代码无伤害。
- 函数的执行没有顺序上的问题。
其他好处:
- 惰性求值:需要编译器的支持,表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值。
- 确定性:函数无论在什么场景下,都会得到同样的结果。
劣势:
- 数据复制比较严重。
用到的技术:
- first class function(头等函数)
可以让函数就像变量一样来使用。函数可以像变量一样被创建、修改,并当成变量一样传递、返回,或是在函数中嵌套函数。 - tail recursion optimization(尾递归优化)
每次递归时都会重用stack,这样能够提升性能。需要语言或编译器的支持。Python不支持。 - map & reduce
对一个集合做Map和Reduce操作。比起过程式的语言使用for/while循环,这在代码上要更容易阅读。 - pipeline(管道)
将函数实例成一个一个的action,然后将一组action放到一个数组或是列表中,再把数据传给这个action list,数据就像一个pipeline一样顺序地被各个函数所操作,最终得到想要的结果。 - recursing(递归)
递归最大的好处就简化代码,它可以把一个复杂的问题用很简单的代码描述出来。
递归的精髓是描述问题,这正是函数式编程的精髓。 - currying(柯里化)
将一个函数的多个参数分解成多个函数,然后将函数多层封装起来,每层函数都返回一个函数去接收下一个参数,这可以简化函数的多个参数。 - higher order function(高阶函数)
高阶函数就是把函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出。可以用来做 Decorator。
使用建议:
- 使用Map & Reduce,不要使用循环。
map和reduce不关心源输入数据,它们只是控制,并不是业务。控制是描述怎么干,而业务是描述要干什么。 - Generator(生成器)。指的是yield返回的是一个可迭代的对象,并没有真正的执行函数。只有其返回的迭代对象被迭代时,yield函数才会真正运行,运行到yield语句时就会停住,然后等下一次的迭代。
装饰器模式
# @注解语法糖(Syntactic sugar)
def decorator(fn):
def wrapper(*args, **kwargs):
# print("before")
fn(*args, **kwargs)
# print("after")
return wrapper
@decorator
def func(*args, **kwargs):
# print("func")
# 使用时,解释器会进行包装返回同名函数
func = decorator(func)
# 正常使用
func()
面向对象编程
面向对象的两个核心理念:
- Program to an “interface”, not an “implementation”.
针对接口编程,而不是实现。
其实就是子类实现超类,使用过程中调用超类的方法。 - Favor “object composition” over “class inheritance”.
优先使用对象组合,而不是类实现。
面向对象更多的是关注接口间的关系,通过多态来适配不同的具体实现。
原型
在JavaScript中,对象有两种表现形式, 一种是 Object,一种是 Function。
大多数基于原型的语言把数据和方法提作“slots”。
编程的本质
两条公式:
- Program = Algorithm + Data Structures
程序 = 算法 + 数据结构 - Algorithm = Logic + Control
算法 = 业务逻辑 + 控制逻辑
有效地分离Logic、Control和Data是写出好程序的关键所在!
编程范式的本质:
- Control是可以标准化的。比如:遍历数据、查找数据、多线程、并发、异步等。
- 因为Control需要处理数据,所以标准化Control,需要标准化Data Structure,可以通过泛型编程来解决。
- 而Control还要处理用户的业务逻辑,即Logic,可以通过标准化接口/协议来实现,让Control模式可以适配于任何的Logic。
代码复杂度的原因:
- 业务逻辑的复杂度决定了代码的复杂度,业务逻辑本身就复杂;
- 控制逻辑的复杂度 + 业务逻辑的复杂度 ==> 程序代码的混乱不堪;
- 绝大多数程序复杂混乱的根本原因:业务逻辑与控制逻辑的耦合。
分离control和logic的技术:
- State Machine
- 状态定义
- 状态变迁条件
- 状态的action
- DSL = Domain Specific Language 领域特定语言
- HTML,SQL,Unix Shell Script,AWK,正则表达式……
- 编程范式
- 面向对象编程:委托、策略、桥接、修饰、IoC/DIP、MVC……
- 函数式编程:修饰、管道、拼装
- 逻辑推导式编程:Prolog = Programming in Logic
编程的本质:
- Logic部分才是真正有意义的(What)
- 逻辑过程抽象
- 由术语表示的数据结构的定义
- Control部分只是影响Logic部分的效率(How)
- 程序流转的方式:自顶向下或自底向上
- 执行过程的策略:并行或串行
- 调度不同执行路径或模块
- 数据关系存储的定义
Prolog(Programming in Logic)是一种逻辑编程语言,它创建在逻辑学的理论基础之上,最初被运用于自然语言等研究领域。现在它已被广泛地应用在人工智能的研究中,可以用来建造专家系统、自然语言理解、智能知识库等。
Prolog语言最早由艾克斯马赛大学(Aix-Marseille University)的Alain Colmerauer与Philippe Roussel等人于20年代60年代末研究开发的。1972年被公认为是Prolog语言正式诞生的年份,自1972年以后,分支出多种Prolog的方言。
最主要的两种方言为Edinburgh和Aix-Marseille。最早的Prolog解释器由Roussel建造,而第一个Prolog编译器则是David Warren编写的。
Prolog一直在北美和欧洲被广泛使用。日本政府曾经为了建造智能计算机而用Prolog来开发ICOT第五代计算机系统。在早期的机器智能研究领域,Prolog曾经是主要的开发工具。
20世纪80年代Borland开发的Turbo Prolog,进一步普及了Prolog的使用。1995年确定了ISO Prolog标准。
有别于一般的函数式语言,Prolog的程序是基于谓词逻辑的理论。最基本的写法是定立对象与对象之间的关系,之后可以用询问目标的方式来查询各种对象之间的关系。系统会自动进行匹配及回溯,找出所询问的答案。
Prolog代码中以大写字母开头的元素是变量,字符串、数字或以小写字母开头的元素是常量,下划线(_)被称为匿名变量。
逻辑编程范式的特征:
- 逻辑编程的要点是将正规的逻辑风格带入计算机程序设计之中。
- 逻辑编程建立了描述一个问题里的世界的逻辑模型。
- 逻辑编程的目标是对它的模型建立新的陈述。
- 通过陈述事实——因果关系。
- 程序自动推导出相关的逻辑。
程序世界里的编程范式
世界上的编程范式可以简单分成几类:声明式、函数式、命名式、逻辑的、面向对象的、面向过程的。
归纳一下,
中间两个声明式编程范式(函数式和逻辑式)偏向于你定义要什么,而不是去怎么做。
两边的命令式编程范式和面向对象编程范式,偏向于怎么做,而不是要做什么。
基本上来说,就是两大分支,一边是在解决数据和算法,一边是在解决逻辑和控制。
世界上四大编程范式的类别,它们的特性和主要的编程语言:
人的左脑的特性是:
- 理性分析型
- 喜欢数据证据
- 线性思维
- 陷入细节
- 具体化的
人的右脑的特性是:
- 直觉型
- 想象力
- 非线性
- 宏观思维
- 抽象化的