之剑 2016.5.6 01:26:31javascript
<!--目录-->css
<div id="category"></div>java
读到两篇文章,写的不错, 综合摘录一下jquery
函数式程序员在洞察问题方面会遵循一个奇特的路线。他们首先会问一些似有禅机的问题。例如,在设计一个交互式程序时,他们会问:什么是交互?在实现 基于元胞自动机的生命游戏时,他们可能又去沉思生命的意义。秉持这种精神,我将要问:什么是编程?在最基本的层面,编程就是告诉计算机去作什么,例如『从 内存地址 x 处获取内容,而后将它与寄存器 EAX 中的内容相加』。可是即便咱们使用汇编语言去编程,咱们向计算机提供的指令也是某种有意义的表达式。假设咱们正在解一个难题(若是它不难,就不必用计算 机了),那么咱们是如何求解问题的?咱们把大问题分解为更小的问题。若是更小的问题仍是仍是很大,咱们再继续进行分解,以此类推。最后,咱们写出求解这些 小问题的代码,而后就出现了编程的本质:我么将这些代码片断复合起来,从而产生大问题的解。若是咱们不能将代码片断整合起来并还原回去,那么问题的分解就 毫无心义。git
这个思惟过程, 并不是是受计算机的限制而产生,它反映的是人类思惟的局限性。咱们的大脑一次只能处理不多的概念。生物学中被广为引用的 一篇论文指出咱们咱们的大脑中只能保存 7± 2 个信息块。咱们对人类短时间记忆的认识可能会有变化,可是能够确定的是它是有限的。底线就是咱们不能处理一大堆乱糟糟的对象或像兰州拉面似的代码。咱们须要 结构化并不是是由于结构化的程序看上去有多么美好,而是咱们的大脑没法有效的处理非结构化的东西。咱们常常说一些代码片断是优雅的或美观的,实际上那只意味 着它们更容易被人类有限的思惟所处理。优雅的代码创造出尺度合理的代码块,它正好与咱们的『心智消化系统』可以吸取的数量相符。程序员
那么,对于程序的复合而言,正确的代码块是怎样的?它们的表面积必需要比它们的体积增加的更为缓慢。我喜欢这个比喻,由于几何对象的表面积是以尺寸 的平方的速度增加的,而体积是以尺寸的立方的速度增加的,所以表面积的增加速度小于体积。代码块的表面积是是咱们复合代码块时所须要的信息。代码块的体积 是咱们为了实现它们所须要的信息。一旦代码块的实现过程结束,咱们就能够忘掉它的实现细节,只关心它与其余代码块的相互影响。在面向对象编程中,类或接口 的声明就是表面。在函数式编程中,函数的声明就是表面。我把事情简化了一些,可是要点就是这些。github
在积极阻碍咱们探视对象的内部方面,范畴论具备非凡的意义。范畴论中的一个对象,像一个星云。对于它,你所知的只是它与其余对象之间的关系,亦即它 与其余对象相链接的箭头。这就是 Internet 搜索引擎对网站进行排名时所用的策略,它只分析输入与输出的连接(除非它受欺骗)。在面向对象编程中,一个理想的对象应该是只暴露它的抽象接口(纯表面, 无体积),其方法则扮演箭头的角色。若是为了理解一个对象如何与其余对象进行复合,当你发现不得不深刻挖掘对象的实现之时,此时你所用的编程范式的本来优 势就荡然无存了。算法
让咱们暂时撇开平台、框架、技术、设计模式、对象思想、敏捷开发论等。 追问程序本质。数据库
布尔代数起源于数学领域,是一个用于集合运算和逻辑运算的公式:〈B,∨,∧,¬ 〉。其中B为一个非空集合,∨,∧为定义在B上的两个二元运算,¬为定义在B上的一个一元运算。编程
经过布尔代数进行集合运算能够获取到不一样集合之间的交集、并集或补集,进行逻辑运算能够对不一样集合进行与、或、非。
在布尔代数上的运算被称为AND(与)、OR(或)和NOT(非)。代数结构要是布尔代数,这些运算的行为就必须和两元素的布尔代数同样(这两个元素是TRUE(真)和FALSE(假))。亦称逻辑代数.布尔(Boole,G.)为研究思惟规律(逻辑学)于1847年提出的数学工具.布尔代数是指代数系统B=〈B,+,·,′〉
它包含集合B连同在其上定义的两个二元运算+,·和一个一元运算′,布尔代数具备下列性质:对B中任意元素a,b,c,有:
1.a+b=b+a, a·b=b·a. 2.a·(b+c)=a·b+a·c, a+(b·c)=(a+b)·(a+c). 3.a+0=a, a·1=a. 4.a+a′=1, a·a′=0.
在 1933 年,美国数学家 Edward Vermilye Huntington (1874-1952) 展现了对布尔代数的以下公理化:
交换律: x + y = y + x。 结合律: (x + y) + z = x + (y + z)。 Huntington等式: n(n(x) + y) + n(n(x) + n(y)) = x。
一元函数符号 n 能够读作'补'。
Herbert Robbins 接着摆出下列问题: Huntington等式可否缩短为下述的等式,而且这个新等式与结合律和交换律一块儿成为布尔代数的基础? 经过一组叫作 Robbins 代数的公理,问题就变成了: 是否全部的 Robbins 代数都是布尔代数?
Robbins 代数的公理化:
交换律: x + y = y + x 结合律: (x + y) + z = x + (y + z) Robbins等式: n(n(x + y') + n(x + n(y))) = x
这个问题自从 1930 年代一直是公开的,并成为 Alfred Tarski 和他的学生最喜爱的问题。
在 1996 年,William McCune 在 Argonne 国家实验室,建造在 Larry Wos、Steve Winker 和 Bob Veroff 的工做之上,确定的回答了这个长期存在的问题: 全部的 Robbins 代数都是布尔代数。这项工做是使用 McCune 的自动推理程序 EQP 完成的。
从本质上来讲, 程序就是一系列有序执行的指令集合。 如何将指令集合组织成可靠可用可信赖的软件(美妙的逻辑之塔), 这是个问题。
程序 = 逻辑 + 控制。 what to do + when to do.
从编程角度来讲, 开发者应对的就是逻辑, 逻辑的表达、组织和维护。 逻辑是事物自此及彼的合乎事物发展规律的序列。指令是逻辑的具体实现形式。
逻辑成立的先决条件是合乎事物发展规律。 程序只能处理数值, 却传入了字符串, 就只能报错而没法继续; 当处理海量数据时, 若内存不足, 就会致使程序崩溃; 若程序存在内存泄露, 随着时间的推移而耗尽内存, 也会致使程序崩溃。 多个线程同时修改一个共享变量, 若不加控制, 就会由于不一样线程执行修改变量的时序的不肯定致使该变量最终值的不肯定。 这些就是程序执行的发展规律。 要编写程序, 一定要先通悉这些规律。
规律的表现形式是:若是条件 (C1, C2, ..., Cn) 是产生结果 (R1, R2, ... , Rn) 的充分必要条件, 那么当 C1, C2, ..., Cn 任一不知足条件时, 都不可能产生结果 (R1, R2, ..., Rn) ; 反之, 若结果 (R1, R2, ..., Rn) 没有出现, 则一定是 C1, C2, ..., Cn 某一条件不知足致使。 错误和异常便是 C1, C2, ..., Cn 任一不知足条件的表现。规律的性质是必然的, 不存在可能之说; 只存在人们探索的是否足够精确。编程开发首先应当懂得程序执行的规律, 而后才是实际的开发; 不然就会被程序的结果折腾得死去活来。
在通悉程序执行规律以后, 程序须要解决以下问题:
要表达什么逻辑
如何表达该逻辑;
如何维护该逻辑。
软件的复杂性表如今如何表达和维护交互复杂的大型逻辑上
暂时先回到软件的起点, 回顾一下这一切是如何发生的。
最初, 人们使用物理的或逻辑的二进制机器指令来编写程序, 尝试着表达思想中的逻辑, 控制硬件计算和显示, 发现是可行的;
接着, 创造了助记符 —— 汇编语言, 比机器指令更容易记忆;
再接着, 创造了编译器、解释器和计算机高级语言, 可以以人类友好天然的方式去编写程序, 在牺牲少许性能的状况下, 得到比汇编语言更强且更容易使用的语句控制能力:条件、分支、循环, 以及更多的语言特性: 指针、结构体、联合体、枚举等, 还创造了函数, 可以将一系列指令封装成一个独立的逻辑块反复使用;
逐渐地,产生了面向过程的编程方法;
后来, 人们发现将数据和逻辑封装成对象, 更接近于现实世界, 且更容易维护大型软件, 又出现了面向对象的编程语言和编程方法学, 增长了新的语言特性: 继承、 多态、 模板、 异常错误。
为了避免必重复开发常见工具和任务, 人们创造和封装了容器及算法、SDK, 垃圾回收器, 甚至是并发库;
为了让计算机语言更有力更有效率地表达各类现实逻辑, 消解软件开发中遇到的冲突, 还在语言中支持了元编程、 高阶函数, 闭包 等有用特性。
为了更高效率地开发可靠的软件和应用程序, 人们逐渐构建了代码编辑器、 IDE、 代码版本管理工具、公共库、应用框架、 可复用组件、系统规范、网络协议、 语言标准等, 针对遇到的问题提出了许多不一样的思路和解决方案, 并总结提炼成特定的技术和设计模式, 还探讨和造成了很多软件开发过程, 用来保证最终发布的软件质量。 尽管编写的这些软件和工具还存在很多 BUG ,可是它们都“奇迹般地存活”, 并共同构建了今天蔚为壮观的软件世界。
此外, 软件还经历了“单机程序 => 多机程序 => 分布式程序” 的过程 , 多机联网程序由于多个子系统的交互变得更加复杂。 这里再也不赘述。
但请注意, 不管软件发展到多么复杂的程度, 总有一群人, 在试图从程序的本质中探究软件开发的基本问题, 他们试图论证和确保程序的正确性、提炼软件的基本属性并进行衡量; 程序的正确性本质是逻辑学来保证的。 没有逻辑学, 程序根本就没法立足, 更不可能有今天的大规模应用。
软件开发工具让咱们更有效率地创造逻辑、 远离语法错误的困扰;
公共库将经常使用的通用逻辑块封装成可反复使用的组件, 避免没必要要的重复劳动;
设计模式体现的是如何可扩展地解决常见的逻辑交互问题;
应用框架解决的是应用的通用逻辑流的控制的问题,让开发者更多地聚焦具体业务逻辑上;
开发技术是在具体的应用情境下按照既定整体思路去探究具体问题解决的方法。
咱们要解决的是更通用的问题: 如何以更不易出错的方式去表达和维护大型逻辑 ?
表达和维护大型逻辑的终极诀窍就是: 将大型逻辑切分为容易消化的一小块一小块, “不急不忙地吃掉”。
在该方法的实践中, 能够充分利用现有的开发工具、公共库、设计模式、应用框架、开发技术。
独立无交互的逻辑一般体现为公共库, 能够解决经常使用或公共的平常任务, 对其余逻辑无任何依赖和交互, 即自足逻辑。
应对独立无交互的大型逻辑的首要方法是分解为若干的容易实现、测试和复用的小块逻辑, 编写和严格测试。
其次是运用成熟的编程模式去表达逻辑, 尽量复用通过严格测试的可靠的库。
独立无交互的大型逻辑经过合理的逻辑块切分、严格的单元测试能够得到充分的测试和可靠度。
快速响应的问题: “用户要求等待时间短” 与 “请求处理耗时长” 之间的矛盾致使的。
解决独立无交互的耗时长的逻辑依然能够采用切分逻辑块、严格的单元测试的作法使之更容易处理;
此外, 有两种设计思路能够考虑: 并发 与 异步。
并发思路是将切分的相互独立的逻辑块分配给不一样的控制线程中执行, 从而下降请求处理时长; 并发方案得到的性能提高取决于串行操做在总操做中的时间占比。
异步思路是“先响应, 后处理, 终通知” 的"先奏后斩"方案。
将一步分离成了三步, 为了让用户首先得到初步的承诺, 再去履行承诺。 这样作能让用户暂时地放心, 却增长了新的问题: 消息中间件组件的开发与部署、异步消息发送与接收、编程模型的变化和适应。若是整个过程运做良好, 将会达到很好的体验,容易为用户接受。若是其中一步发生差错, 就会致使各类问题, 好比数据不一致, 消息堆积、 请求没法被处理。最终用户等待时间并无下降, 反而使体验更加糟糕。 固然, 若是成功率为 95%, 也是“能够接受”的, 这样用户可能会怪本身“运气不太好”, 而不会过多怪责系统的不完善。毕竟没有任何事情可以作到完美的地步。
并发与异步方案的调试难度和排查问题都比同步方案增长很多。 每一种新的设计方案都会有其优势, 同时也会有其缺点。 权衡优缺点, 择善而从之 。值得注意的是, 并发方案是针对服务端实际处理请求逻辑而言, 而异步方案是针对请求处理以前是否当即回复的方式。 并发与顺序、 异步与同步两两组合, 可获得四种方式:
优势是简单、安全、 容易维护和调试;
缺点是性能较低, 响应时间和吞吐量都不高; 若请求处理时长很是短, 采用顺序同步的方案佳;
优势是经过并发提升服务端的处理速度和吞吐量, 但若请求处理耗时较长, 响应时间仍然不高, 影响客户端体验;
若经过并发方案处理请求的时长很是短, 或客户端体验要求不高, 能够采用并发同步的方案;
优势是提升了响应时间和客户端体验, 因为其逻辑处理仍然采用顺序方式, 请求处理时长并未有改善, 所以吞吐量并无改善。 是一种较好的折衷方案;
若请求处理耗时较长, 影响客户端体验, 且请求处理逻辑复杂, 采用并发方案容易出错或难以并发, 可采用顺序异步方案;
优势是提升了响应时间、客户端体验和处理速度、吞吐量。
缺点是容易出错, 且不易调试;
若客户端对响应体验要求较高, 请求处理逻辑简单(好比简单的数据拉取和汇总), 采用并发方式可有效提高处理速度, 能够采用并发异步方案;
软件的复杂性真正体如今逻辑块的持续长久的交互耦合和可扩展上。这是软件开发与维护中极具挑战性的部分。
逻辑块之间的交互耦合一般体如今三种情境:
操做顺序的依赖。 好比资源更新操做必须在指定资源已经建立的状况下进行。
对共享有限资源的并发申请。 好比打印机只有两台, 却有多个应用程序链接上去请求打印文档;
对共享可变状态的并发访问。 好比两个操做同时要更新数据库中的同一条记录;
三种情境的复杂性均是由并发引发的。 假设全部操做都是串行进行的, 逻辑块的交互无非是“你方唱罢我登场”的次序控制, 而资源对单个请求一般是足够的; 一旦采用了并发方案, 就难以控制逻辑块的执行次序和资源分配的具体状况了, 容易致使某资源对单个请求不足的状况, 从而阻塞多个请求的处理甚至死锁。并发提高了应用的性能, 却增长了出错的风险和概率。并发控制是大型逻辑交互的本质性难点。并发控制的难点在于时序的合理控制和有效资源的合理分配。
对于 a 情境, 一般采用添加前置条件来求解, 在操做以前校验相关资源是否知足、实体状态是否合理, 实体之间的关联是否正确; 若前置条件不知足, 则直接返回错误提示, 或者暂时挂起以备后续继续执行;
对于 b 情境, 须要建立一个可靠适用的资源分配算法 和资源分配模块 , 应用程序再也不“自行”去拉取资源, 而是向资源分配模块申请资源, 由资源分配模块根据实际申请的总体状况及申请条件来决定如何分配资源;
对于 c 情境, 须要进行安全的互斥访问, 谨慎地控制。
逻辑块之间的交互耦合应该交给交互解耦模块去完成, 而不是在本身的接口里实现。
也就是说, 只有交互解耦模块知道全部接口之间的交互, 而接口只作本身知道的事情就能够了。不然, 接口 A 与接口 B 必须知道彼此究竟作了什么, 才能正确地作本身的事情。 假设 接口 A 和接口 B 都修改某个资源的状态。 接口 A 在作某项操做执行必须执行 IF (ConditionX) do something ; DoMyOwnThing ; 接 口 B 也要根据 A 的逻辑相应地执行 if (ConditionY) do anotherThing;DoMyOwnThing. 而程序员在维护和修改接口 A 的逻辑时, 不必定知道接口 B 的逻辑与之相关, 因而修改不可避免地破坏了接口 B 的逻辑。 耦合的接口数量越多, 或者耦合接口之间的耦合资源越多, 对后期维护和扩展将是一个难以应对的噩梦。
对于逻辑块之间的交互解耦, 或者通俗地说, 模块解耦.
程序中的逻辑主要是三类:
获取值: 从数据库、网络或对象中获取值。 若是数据库或网络访问足够稳定的话, 能够当作是简单的获取值, 数据库访问和网络访问对获取值是透明的;
检测值: 检测值是否合法, 一般是前置条件校验、 中间状态校验和后置结果校验, 根据检测结果执行“获取值”或“设置值”的逻辑;
设置(拷贝)值: 设置数据库、对象中的值; 或者发送数据和指令给网络。若是数据库或网络访问足够稳定的话, 能够当作是简单的设置值, 数据库访问和网络访问对设置值是透明的;
这三类逻辑能够称为逻辑元。 具体业务逻辑就是基于物理的或逻辑的资源限制, 将逻辑元的组合封装成逻辑块, 有效控制逻辑块的时序交互和资源分配。 时序控制不合理和资源缺少致使错误和异常。两个程序同时更新一个共享变量, 若是时序不控制, 就会致使错误的结果; 网络通讯错误, 是由于网络带宽资源是有限的。
预防错误的方法就是进行防护性编程, 进行容错考虑。 多思考: 若是这一步发生错误, 会致使什么问题? 该如何作才能预防这个错误? 若是难以预防, 该如何描述, 才能在出现错误时更好地定位出这样的错误? 在出现错误时, 如何才能恢复到正常合法的状态 ? 若是没法程序自动恢复, 怎样作才能让手工处理更加简单 ?
要健壮地表达和维护大型逻辑, 首先系统总体架构必须足够稳固可靠, 在开发和维护过程当中持续加固。 假设一栋建筑总体设计有问题, 那么, 不管里面的房间装饰得多么漂亮优雅, 都会随着建筑的坍塌而消亡。 这须要深刻去探究所使用的应用框架, 挖出可能的不可靠风险, 并加以预防和控制。
在已肯定的设计方案和业务逻辑的状况下, 如何编写BUG更少的代码:
简明扼要的注释 + 契约式/防护式编程 + 更短小的逻辑块 + 复用公共库 + 严格测试
编写更少BUG程序的六条准则:
在方法前面编写简明扼要的注释: 方法用途, 接收参数, 返回值, 注意事项, 做者, 时间。
契约式编程: 在方法入口处编写前置条件校验,在方法出口处编写后置结果校验 ;
防护式编程: 编程时严格校验参数和前置条件; 仔细考虑各类错误与异常的定位和处理;
编写和保持短小逻辑块, 易于为人的脑容量一次性处理, 容易测试;
复用通过严格测试的可靠的公共库; 若是库没有通过很好的测试,但有很好的用处, 帮助其添加测试;
对所编写的代码, 若是不是逻辑元, 都要进行严格测试。
关于做者: 陈光剑,江苏东海人, 号行走江湖一剑客,字之剑。程序员,诗人, 做家
<link rel="stylesheet" href="http://yandex.st/highlightjs/6.2/styles/googlecode.min.css">
<script src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script src="http://yandex.st/highlightjs/6.2/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script type="text/javascript">
$(document).ready(function(){
$("h2,h3,h4,h5,h6").each(function(i,item){ var tag = $(item).get(0).localName; $(item).attr("id","wow"+i); $("#category").append('<a class="new'+tag+'" href="#wow'+i+'">'+$(this).text()+'</a></br>'); $(".newh2").css("margin-left",0); $(".newh3").css("margin-left",20); $(".newh4").css("margin-left",40); $(".newh5").css("margin-left",60); $(".newh6").css("margin-left",80); });
});
</script>