运用之妙,存乎一心。
计算机编程领域的基本思想,是大量实践与经验的提炼总结,是近乎于“道”的东西。有了思想的指引,就如同有高人指路,行不迷惑,遇事有法,运用之妙,存乎一心。
理解这些基本思想,就能构建牢固的编程思想体系,更好地理解和汲取各种技术机制和技术知识,因具体的技术机制和知识通常是基本思想在不同场景下的演绎和应用。
- 编程
- 结构化
- 抽象
- 封装
- 分治
- 组合
- 复用
- 缓存
- 解耦
- 编码
- 自动化
- 预处理
- 时空权衡
- 统筹规划
- 递归
- 索引
- 迭代
- 遍历
- 中断
- 模板
- 模式
- 代理
- 并发
- 并行
- 批量
- 异步
- 回调
- 延迟
- 定时
- 订阅与推送
第一梯队思想
第一梯队思想,是计算机编程领域最为核心而根本的思想。因其威力强大,且无处不在,简直是天神般的存在。
如果能够掌握第一梯队思想,至少在编程领域,将会是游刃有余,可神游于太虚之境。
编程
编程没什么神秘的。本质上,编程与写作、绘画、雕塑一样,也是一种手艺,一种探索和求解问题的方式。只不过,编程高度依赖于算法步骤的正确性和可行性,需要高度的理性水平。
理性有三种类型:逻辑理性、感性理性和思想理性。编程属于逻辑理性。逻辑理性要求逻辑层次是缜密无矛盾的,不能有一丝纰漏,否则可能会导致全盘溃败。逻辑理性是一层层堆叠起来的。上层的缜密依赖于下层的缜密。
构建逻辑理性的缜密性:
- 原则与思想:从根本上构建行为一致性。
- 约定与惯例:减少多变环境下的管理。
- 推导与演绎:即给定若干条件及事实,能够正确推导出深入而广泛的结论。推导演绎能力是逻辑理性的生动体现和重要特征。
可阅: 编程漫谈(十一): 编程概要
结构化
结构化是编程领域的第一性原理。所有的程序,都是对现实世界中的实体和活动的结构化的产物。
抽象
编程世界始于抽象。
萃取出主要特征,而摒弃次要或不相关的特征;无需了解事物的内部实现细节而基于其提供的抽象来构造应用。声明与实现相分离;语义与细节分离。
计算机科学中的抽象俯拾即是,比如指令集是对机器硬件执行能力的抽象,高级程序语言是对机器硬件执行能力的更高层次的抽象,进程是对程序一次执行的抽象等。
封装
由抽象直接引出的重要概念就是封装。比如,函数是算法的封装,对象是状态与关联逻辑的封装。封装是实现软件模块化、提高软件可维护性、应对软件复杂性的重要思想,是许多软件工程思想的源头。比如隔离变化, 将变化的影响局部化等。
举个例子,汽车方向盘就是汽车驾驶的封装。你不需要知道汽车的具体原理和运转方式,只要掌握好方向盘就能驾驶汽车。比如火箭发射,不需要知道火箭的发射原理,只要知道按下哪个按钮即可。
分治
分治几乎是求解编程问题(乃至各种现实问题)的万能法宝。难以求解的问题,都可以分解成若干个可以在能力范围内解决的子问题;如果还不能求解,继续分解。
自顶向下,逻辑树法,都是分治法的典型应用。自顶向下,即是根据目标先分解成一系列的子目标,然后再根据这些子目标进一步分解成子子目标,就像公司的组织结构一样。直到子子目标可以被完成。
逻辑树,即是先根据一个论点,引出若干个子论点,然后每个子论点又可以引出若干个子子论点,每个子子论点都可以由相应的论据支撑,最终形成缜密的论证逻辑。
实际工程应用中,分表、分库、分区、分片,都是分治思想的应用。
组合
有分即有合。单一逻辑单元能够做的事情很有限,但是多个逻辑单元组合起来就能完成很强大的功能。就像一个人的力量有限,但如果多个人的力量团结起来,就不可估量了。
函数式编程,即是充分利用短小函数及各种组合方式,构建出强大的功能。
组合是构建强大功能的最重要的方式之一。
复用
没有封装就没有复用。复用是微小的编程能够构建起虚拟大千世界的最重要的秘诀。先构建一个小块,然后在这个小块上构建更大的块,就像建造房子一样。从泥到砖,从砖到墙,从墙到房子四围。
复用是软件工程领域的重要思想。从 LinuxShell、标准库函数,STL,JDK这样的代码级复用,到 Struts, hibernate 的应用框架复用, 以及 设计模式的复用, 解决方案的复用, 复用无处不在。 复用是站在巨人的肩膀上, 能够直接利用专家级的知识和经验,何乐而不为?
新的更优的解决方案往往是现有可复用方案的组合创新。
缓存
缓存是性能加速的重要法宝。在计算机体系里,缓存无处不在。
- CPU 三级缓存
- 存储器层次体系结构
- 内存缓存/分布式缓存
- 数据库缓存
- 浏览器缓存
- CDN缓存
- 代理缓存
- 域名系统
- 计算结果缓存
- 预编译缓存
解耦
解耦是软件应对多变需求和软件复杂性的极为重要的思想。如果缺乏解耦,软件就会缠绕成一团,无法解开,难以修改,很容易出错。
编码
上述思想都有点”虚“,但编码却是实实在在的实践和最基本的思想。
要写程序,必定要将现实中的实体和活动编码成 01 串,进行处理,最后再解码还原到现实世界。编码是指将现实中的万事万物编码成01串的操作。神乎其技兮!
精通编码,就能洞穿程序世界里的一切,如镜透明。
自动化
自动化使得人从繁琐易错的事情中解脱出来, 从事更具有创造性的工作, 使人与计算机并行协作, 在计算机干活的时候人休息一会也不耽搁事情。善于发现可自动化的流程、规律、事务, 尽可能使用程序自动化和简化手工活, 减少或消除繁琐易错的手工操作。
高效可靠的自动化几乎是程序(员)存在的最主要的使命和价值。
预处理
预处理,是指先对待处理数据做一波前期的处理,转换结构,然后再对转换后的结构进行操作,起到加速或方便处理的作用。
比如要查询一个无序列表里的元素,可以先转成一个映射,再进行查询。这样多次查询的效率就会很高。
比如 Rete 算法,即是将一系列规则或模式,转换成一个拓扑图结构,然后再进行匹配。这样就比用原始结构的匹配更容易效率更高。
预处理有一定的前期开销,但能够为后续处理提供很大的便利或效率价值。
时空权衡
时间换空间,或者空间换时间。
空间换时间,是提升性能的常用手段。比如缓存,就是用冗余的空间来换取高效的处理。
时间换空间,是解决内存不足的常用手段。比如要处理大量数据,不会一次性将所有数据都加载到内存里,而是分批次加载和处理,这样小内存也能解决大问题,代价是处理时间会更长一些(因为需要更多次的加载)。
统筹规划
统筹规划思想,注重在全局视角下解决问题。
找到关键路径和节点,解决这些路径和节点的问题,往往会加速整体流程的效率。
不在次要路径和节点上耗时耗力。
第二梯队思想
第二梯队思想,虽然不如第一梯队思想那么通用广泛,却也是编程领域的重要思想,有着举足轻重的地位。
递归
一个规模为N的问题的解可以由规模为S(S<=N)的同样性质的问题的解来构造。
举个简单的例子,1+2+3+4+5 = 1 + (2+3+4+5) = 1 + (2 + (3+4+5)) = 1 + (2 + (3 + (4+5))) = 1 + (2 + (3 + (4+(5))))。 N 个数的和等于第 N 个数与 N-1 个数的和。
T(N) = T(N-n) + G(n) 或 T(n) = mT(N/m) + G(s). 递归技术是一种非常有效的程序设计技术。计算机中的绝大多数数据结构都有递归特性。列表、字符串、二叉树、JSON等。
索引
通过标识常用项,从而能够快速定位常用数据,提升查找效率。
索引是海量数据查询的重要技术基础,而海量数据查询是现代互联网应用的基石。
迭代
迭代是使用固定的计算规则集合不断用新值取代旧值趋向真实值的控制结构。比如牛顿迭代法求N的平方根 X(k+1) = (X(k) + N/X(k))/2 (k>=0) 就是一个迭代过程。可以指定初始值和终结迭代过程的迭代次数。迭代的重要指标是收敛性和收敛速度。
遍历
遍历是从结构中的某个初始节点出发,使用某种控制算法直至访问结构中的所有节点。遍历有深度遍历和广度遍历。深度遍历是一直往一个方向走,直到无路可走,然后回退到上一个可选择路径的节点,选择另一个没有遍历的路径。依次直至所有节点都访问完毕。深度遍历上述的数结构,
中断
当你正投入工作状态的时候,领导发话了:开会开会! 于是你不得不放下手头心爱的事情,跑去听一段@#¥@#%@¥%@%#¥%#的讲话,讲话回来后再以莫名的心绪重新干活。当然,人是有记忆的,当你去开会前,实际上已经记忆了当时做的事情的一些重要细节和进程,这样在回来时就能从这些细节和进程逐渐恢复到当时状态继续干活,就像讲话似乎没发生过一样。这就是中断:做某件事,更高优先级事情插入,保存现场,完成更高优先级的事情,恢复现场,继续做原来的事情。
当发生某种事件时,产生一个信号中断,传递给相应的程序去处理。
中断提供了有效应对系统中各种事件、使得系统能够正常运转的重要机制。
模板
通过替换静态模板中的动态变量, 从而生成动态文本的思想和技术。
如果多个输出都具有相似的格式或流程,就可以定义模板,通过模板与具体数据的结合,来实现具体的输出。
比如结婚请柬就是典型大的模板应用。请柬上的大部分内容是相同的,只有被邀请的人是不一样的,这部分是空出待填充的。
模式
模式,俗称套路,是已有经验的提炼和总结。适当借助模式,能够高效处理问题,避免重复踩坑。
善于发现模式、使用模式。
代理
代理,顾名思义,就是代替某物做某事。
代理通常作为”中间人“,起着缓存、保护、负载均衡等作用。
第三梯队思想
第三梯队思想,程序执行流程的灵活变种。
并发
并发是同一个执行单元在一段时间内交替做多件事情。
比如一个人一边烧水,一遍看电视,一遍督促孩子做作业。看上去就像是在同一时间段内同时做了多件事情。由于某一项需要等待,往往在多项任务之间不断切换(保留现场和恢复现场)。切换会耗费一定的时间。
并发是利用多核的性能提升手段。事实上,并发也是世界运行的本质。这个世界正是由不计其数的人并发地去进行自己的活动所构建起来的。
并行
并行是多个互相独立的执行单元在同一段时间相互独立地做不同事情。
比如一个公司里,产品在理需求,研发在做技术调研,两个人都是朝同一目标前进,但做着不同的事情。
并行是并发的一种更严格的形式。
批量
涉及网络传输时,单个单个去获取数据和处理数据往往会比较慢,因为传输的次数增多,需要反复建立和销毁连接,会耗费不必要的连接建立时间和传输时间,尤其是连接建立成本、传输成本与处理成本相当甚至更大的情形。
比如传输一次需要 10s, 处理一个需要 5s。如果单个处理,那么处理 10 个需要 (10+5)*10 = 150s;而如果一次批量获取10个,然后批量处理,则只需要 10 + 5*10 = 60s。 中间 90s 的传输成本都是不必要的。因此大量数据的获取和处理,往往是批量方式处理的。
比如运货,通常是一整箱一整箱地运送,而不是一个个的运送。
批量是提升大量数据处理的效率的常用手段。
异步
异步最初是为了解决响应的体验问题。在提交后台任务执行的同时,给前端先返回一个消息,让用户能够有所感知,而不是无感知的等待。异步,多用于解耦后台耗时任务与前台展示。
多路 IO 复用,是异步方式解决高并发问题的技术基础。
回调
回调有点类似于埋点。指定做完一件事之后,要去处理一段特定逻辑,而这段特定逻辑可能是不同的。这段特定逻辑就称为回调逻辑。
回调能够让函数的功能更加灵活,是函数式编程的重要特征之一。回调的函数参数通常是函数指针。
延迟
亦称惰性加载。仅在必要时才去加载、访问和计算。
通常用于创建需要耗费资源较大的对象,或者将访问和计算延迟到必要的时候才去进行,以减少不必要的消耗。
比如说,举办一次会议,仅在会前一天或几个小时,才有必要在会场去”铺设“饮用矿泉水。因为如果提前去铺设,万一会议取消了,或者转换场地了,那么这些努力(连带时间和精力)就都白费了。
定时
定时是在指定的时间周期性地做一件事。
软件系统中充满了各种定时任务。比如闹钟就是一个定时任务。
订阅与推送
与定时的指定时间不同,订阅与推送,是在所关注的事情发生变化时,发送消息通知,以便做相应处理。
比如订阅了一个公众号,公众号在发布文章时,就会给你推送一个消息。