连载39:软件体系设计新方向:数学抽象、设计模式、系统架构与方案设计(简化版)(袁晓河著)

1. 简单性

 

由于对简单的理解会很多,具有最少构成要素的结构,符合简单性观念。在众多可能中选择一个最方便的方式,也符合简单性观念。根据奥康的剃刀原则“如无必要,勿增实体”即简单有效的原则。然而简单性是一个相对的概念,是在不同的时空、不同的视角下存在的一种可被成本最低的理解。

但是在系统构架中,具有简单的设计方案,往往具有最少的约束,从而带来最为直接的处理方式,由于简单,所以设计开发都显得容易掌控,其稳定性和可靠性会大大的增强,同时由于简单,所以一旦存在需要扩展,其扩展的约束也是非常少,所以能够形成新的扩展。当然,如果我们能够通过统一划归的手段,那么最终的系统也是从复杂趋向简单,统一性是根本的,而简单性则是期望获得的一个结果。统一化以后,容易产生简单的处理,例如我们的线性化处理,最后呈现的整齐划一的简单原则。

然而简单也是容易让人迷惑,很多时候需要从一个整体去考察其是否简单,而不是仅仅从局部来考察。但是在很多的系统设计中,由于所有的整体都是一个一个局部构成的,所以在我们关注局部的情况下,我们容易制定一个尽量简单的策略,然而,当我们将一个个局部整合在一起的时候,却发现我们最终的系统不是一个简单的策略,那是否说我们在局部设计的过程中就不要思考简单原则,当然,我也可以负责任告诉你,最后你的系统肯定不是一个简单有效的系统。这就形成了一个困境。我们应该在何时使用简单的策略呢?我们依据的标准是什么呢?

带着这样的困惑和悖论,我们继续来分析一下我们平时的“化繁为简”的方法,无论是分析还是综合的过程中我们是否无时不刻在使用简单化的方法和手段呢?虽然,目前还无法从理论上论证这个就是一个“完美的”处理方案,但是在巨多的事例面前,我们是否也明白在整个设计过程中,是否应该时刻保持“简单”的处理原则,即使可能我们已经偏离了我们的目标,但是,从一开始就是沿着“复杂”的方向进行,那么我们注定是设计出一个无比复杂的系统。

因此我们需要清醒的认识到,一个系统具有的简洁美,不是因为其内容是简单的,而是其表达形式是简单的,其体系结构是简单的,我们掌握起来所支付的成本应该是最低的,我们变化处理的成本也是最低的。所以我们说,设计一个系统不是创造一个“实体”,而是要求通过我们的设计构建起一个实体的“映射体”,这个“映射体”是一个具有简单结构通过简单方式构建在一起能够完整表达这个实体的外部特征的结构体。让外在的简单性和内在的真实性密切相互关联,达到矛盾的对立和统一。

对称性可能是简单性的一个非常重要的表现特征,为什么呢?因为从直接的效果上来看,我们掌握一个对称性所需要的“知识量”,只需要“一半”就能够掌握,比如当我们知道一个图形满足对于某个直线对称,那么我们可以用已知的的这些信息,完全勾画出另外的“一半”图形,虽然对称的方式有多种多样的,但是,对称表示元素的构型在自身变换群下的不变性。这样的不变性就是我们简单的体现。

因此,很多时候,我们的设计在能够具有对称方式展现的情况下,都应该考虑向作对称的一面进行设计,就算我们需要增加一些更多的属性来填补,其实针对整个系统来说,对称带来的简单性的价值远远超过增添属性带来的成本。例如我们具有如下的一个状态设计的处理(如图7-4所示):

image.png 

7-4

 

 

此时为了描述这样的状态,我们不得不使用如下的方式:

 

switch(哪一个状态)

{

case State1:

{

if(condition_1)   

}

case State2:

{

if(condition_1)  

  if(condition_2)  

}

  

}

 

如此复杂,我们的外在表达形式就非常复杂,那么我们可以“补全”这些条件,形成一个对称的局面呢!

 

image.png 

7-5

 

此时,我们引入了一个condition_NoExit条件以后(如图7-5所示),那么我们完全可以使用一个状态变迁的表来表示这些状态和条件的处理方式(此时,是不是感觉我们的处理方式立刻变得高大上了^_^),由此我们的设计就变得更加简单,更容易扩展,而且所需要掌握的成本同样会更少,所以增加并不一定就是代表了复杂,有时候让之形成“对称”的效果以后,能够给我们带来意想不到的好处。

 

2.1 单一的误解

 

单一职责原理(SRP)解释为:一个类只能因为一个原因而改变。

这个原则其实是比较坑爹的一个原则,没有明确原因到底是什么层级,是哪个范围的定义,而且如果按照上面的原则写出来的代码,可能会让你更看不懂这样的代码,因为所有类都只有一个变化的原因,所以最后其类恐怕都变化成command设计模式一样都只具有一个execute操作,虽然这将这个类变得非常简单,但是这个类之外的类与类之间的关系就会变成一个蜘蛛网了。就有某些好事者写过这样的代码,把阅读的人都绕晕了,但是别人还振振有词的表示这个符合最基本的单一职责原则呢!

如果我们从更抽象的意义来看看这个设计原则,这个原则是没有问题的,只是这样的解释是不合理的。我们不需要这样畸形的将之应用到某个具体的类中来描述,而是依照

通过使用单一原则来阐述非对称加密中密钥只能作为加密或解密这样的单一方式,而不是对称加密中,既具有加密的密钥又具有解密的密钥功能,来说明,采用这些单一的加解密密钥分离的好处,可以具有组合效应的基本要求,由此可通过机构非对称密钥对,个人密钥对一些列CA机制来达到组合,其应用的强大威力。

人类在不成熟时期,老是希望趋于复杂,但随着认识的深入,简单才是人类迈入成熟的表现。

Linux中一切设备资源都是文件,网卡、硬盘、内存、进程等等都是文件,都让其具有简单统一的读写接口。

但就我的理解,单一原则应该是如何进行秩序化,将它们外在的行为表现的一致,这应该才是单一原则的最核心的问题,我们从表驱动来看,通过秩序化以后,其通过查表就能更容易写入(举例表驱动的例子),比如在java中为什么有一个CObject,就是为了化为一致的处理(具体分析CObject的成员变量),才容易更好的规范和组合。又例如组合设计模式(将组合设计模式解释一下),就是通过规范秩序以后,才能够更好的将这些行为组合在一起。这些都体现了单一化、秩序化的思想。所以,不是因为我们管理的数量多,而是我们管理是一致的,可通过简单的操作就能够完成。故,兵法上说,指挥千军万马就如同指挥一个人。

然而,我们在软件设计中的单一原则的认识还比较肤浅,因为对于单一原则来说,其实表示的对象的状态和定义是单一的还是其具有的操作是单一的呢?在软件设计的思维角度中其表示的是状态和定义是单一的,但是我们在实际的过程中却大大的限制了其自身的可扩展和灵活变化。

这里我们以Javainteger类来看,对于整数这样的一个对象,其接口满足我们刚才讲到的单一原则,因为其表达的是对整数的设置、获取、基本的四则运算、序列化、向string转换运算等,但是这个职责单一的类无法包括其新的特性,例如,对于整数来说,它是一个数学意义上的“环”,这些特性是整数的核心特性,其实包含的是内部的一个禀性,但是我们的软件设计过程中,无法预先定义这样的接口,我们要求的单一原则,不会对将来可能存在的操作进行接口定义,其实我们也无法在现有的设计语言的技术上更好的对接口进行完整的确定,因为这个对象的特性会随着需求在变化,而我们目前的软件设计还只是使用形式决定内容的层次上(注:在哲学和科学中都是内容决定形式)。是通过外在的接口来定义一个对象,而不是通过其概念定义到内部特征,再到外延这样的顺序来进行,所以,我们永远都在更改我们的接口,永远都无法达到单一原则的要求。所以,我们面向对象的思路是否还存在一些致命的弱点呢?

小心为“名”所困,一个类只有一个因素让其变化,即类具有单一原则,然而目前的情况是凡是能通过技术手段将类划分成多个类就划分成多个类,于是在一个小工程里面就出现成千上万个类,更有情况是一个类只有一个成员变量,一个Set一个Get操作,类无独立的逻辑意义,成了美其名曰解除耦合,同时类与类之间的关系非常复杂,任何一处修改都要去查查类之间的关系,非常复杂,同时其扩展也是非常复杂的,不知道应该在哪个类中进行继承和组合。类的个数也是剧增,很难记住如此庞大数量的类名。而且这些类名也大多无实际意义的名称。于是系统就进入了无人愿意维护的地步。这在设计中就是一种过度设计。不仅无法理解,而且也无情的浪费资源。

那么一个类到底有多大呢?我想任何大师都无法准确回答这个问题。因为类需要表征其逻辑意义。这些逻辑意义可能对应一个物理意义,有可能对应部分的物理意义。但无论如何,只要认为其有独立的逻辑意义,同时在认识能力的范围内,那么其类就达到目标。另外,在需求变化的过程中,其变化以后只要改变其逻辑意义,那么就需要划分成两个或多个类,所以变化的因素很重要,当变化冲击了起类的逻辑意义,其重构就要开展。

在一个系统中,不是将类划分的越小,就越能够表示解除了耦合。在类设计中同时也要考虑到其内聚性,高内聚目前是很多设计中缺失。只有高内聚才能保证其逻辑意义独立。类之间交互才能减少。让类达到一个平衡。

尽信书则不如无书,类的粒度只有从设计的角度去思考才能设计出优秀的系统,走向极端只能使事业失败的风险变成必然。

 

2.2 简单化设计原则

 

Unix设计原则:

ü 清晰原则:使用简单接口拼合简单部件。编程的本质就是要控制复杂度,多数问题只会局限于一个局部,不会影响全局。unix向TCP/IP协议栈实现socket链路层接口管理框架。

ü 接口单一:一个接口包含一个功能。例如Rx/Tx/IOCtrl

ü 组合原则:设计时候考虑组合拼接。把复杂问题分解为一条程序处理链条。程序之间可以通信、前者的输出是后者的输入。通信方式尽量采用简单文本格式,人可以方便阅读。组合程序之间不进行内部状态依赖,互相独立,对处理链上游和下游程序不做假设可以替换。例如Unix管道技术,通过管道组合可以完成复杂的工作IS|WC

ü 分离原则:策略与机制分离,接口同引擎分离。策略是程序的外部表现,体现用户界面。机制是内部实现,体现核心业务逻辑,相对稳定。实现时把策略和机制分离,策略作为前端、后端是机制,二者通过IPC进行通信,同样的机制可以变换多种界面,满足不同的需求,同时机制保持稳定。例:Xwindow设计/unix多种shell机制,GUI界面程序设计/INVX架构,VRPV8  SMP分层模型:DOM/D2M/DAM,一种D2M适应多种工具和众多APP

ü 透明原则:设计和实现都要可见。设计简单,包括流程/数据结构/接口,在代码级别容易理解,运行时刻通过输出信息或者查看运行状态,容易监控程序是否正确执行。作为设计的一部分,考虑支持状态查看和输出重要信息

ü (New)表示原则:把知识叠入数据,简化和统一处理逻辑。

ü 简单统一原则:Unix中一切设备资源都是文件,网卡、硬盘、内存、进程等等都是文件,都让其具有简单统一的读写接口。

首先,让我们来从层次层面进行分析简单化为什么如此重要,同时也从一个更加全面并且深入的角度分析简单化设计原则,“简单化”其实并不“简单”。

在一个具有层次化的系统之中,底层的架构原则往往是简单的,也就是构建起最为基本的架构思想是“简单”的,例如我们使用“置换”这一公理化的方式来表达软件设计基础的体系架构,这些架构是简单的,并且可以更好的论证,随着系统的层次越向上,其“简单化”就逐渐被复杂所取代,越向上其将具有过多的“特征”,更为复杂的因素来决定。

虽然,最底部基层的“简单”构建,但是需要注意,简单化是最具有区域限制的,这一点过去一直被忽略,为什么简单化具有区域限制呢?因为一旦大范围的领域,其更多的具有更为复杂的特征制约。举一个不太恰当的例子:AK47是一款简单化体现极致的士兵单人×××,但是,其无法做到跨大区域的完全应用,其在大领域内还需要更为复杂的海陆空的坚船利炮来决定,而在我们的系统设计过程中,为了局部的更优化更具体更特殊的处理,我们会应用更为简单有效的解决方案,但是,一旦其领域扩大,则往往警惕我们需要使用更为复杂的上层设计来适应大范围的要求。

另外在纵向方面,抽象这一层面上,我们也可以忽略其他的因素,聚焦于某一固定的特性上,那么此时这一固有的特性也体现了“简单”化的设计,此可称呼为“抽象简单化”,这样的简单原则则是有必要的前提条件,是在某一定的假设情况下具有的简单化。而我们的很多接口往往就体现这样的简单化要求。如果我们体现的抽象简单化体现的越是周到,则表示我们的接口设计则更为优秀,让接口更加有效的独立,也让系统变化过程中,接口才能够达到更好的适应性。但是这里需要注意,抽象简单化是有前提的,不是任意的,因此需要关注与其前提的适应场景,另外抽象简单化也是具有区域限制,是在某一特殊区域内具有适应性,所以接口也会根据区域的变化而发生变化。

数据建模,使用数据之间的关联关系来体现业务逻辑或者领域知识,使得业务处理逻辑代码简单一致稳定。

仅仅改变数据模型就可以支持新业务,逻辑处理代码不用修改。

通过使用单一原则来阐述非对称加密中密钥只能作为加密或解密这样的单一方式,而不是对称加密中,既具有加密的密钥又具有解密的密钥功能,来说明,采用这些单一的加解密密钥分离的好处,可以具有组合效应的基本要求,由此可通过机构非对称密钥对,个人密钥对一些列CA机制来达到组合,其应用的强大威力。

限定PW两端是相同类型的业务(对称的)这样简化了PW的设计。

对于一些状态信令,PE必须参与或监控,有时还可能需要透传、转换、记录这些控制,状态信息。

一个隧道上可多条伪线(PW)进行复用。

组网中的方案和学问、技巧、困难、目标、需求和能力提升点:

传送速率越来越快,瞬间接入越来越多,高清VoD,在线授课、云服务等等。

电信网络扁平化,无线网络异构化,降成本、统一架构、轻松运维、面向未来的可扩展性和灵活性。

快速部署、即插即用、快速故障定位、配置简单、Qos保障、灵活接入,能够组大网,集中控制,集中配置,集中管理。

业务性能管理。实时或周期性业务监控,高精度、业务报文直接度量,时钟同步和管理、可视化、图形化人机管理界面、快速故障定位、端到端IPSec安全方案、提供虚拟化的软件平台。

存在多条路径,虽然是动态查找到的,但是其无法做到最优,可以通过mpls建立一条最快速有效,同时最安全的路径,这条路径上其设备可以得到保障,是最优的路径选择,而且也是动态生成,只是生成一条最优路径,而不是每个包都去找最优的路径,于是其Qos和安全能够得到保证。而且其控制和管理也变得简单,而且对原有网络不需要做大的更换。

MPLS转发,其控制和转发功能相剥离,控制平面负责路由器信息的建立,标签信息的建立,转发平面负责IP转发和标签映射,并且负责标签转发。下面是MPLS的转发过程在所有的LSR(标签转发路由器)路由器上启用路由协议,在LSR中建立路由表项。

利用LDP(标签分发协议)利用IP路由表建立LSP(标签转发通道)LER(标签边缘路由器)收到IP报文后,分析报文头,对应到FEC(转发等价类)然后给报文加上标签,根据标签转发表中的LSP信息,将报文送到相应的接口,LSR收到带有标签的报文,只欧诺个解析最外层标签头,然后根据标签头查找自己的LSP。之后替换掉标签头,送到相应的接口,其余的LSP处理过程相同,当倒数第二跳的LSR收到带有标签的报文,查找标签转发表,然后判断对应的出口标签是否为隐式标签或者空标签,然后弹出标签,发送IP报文到最后的LSRLER收到倒数第二跳发来的报文后,执行三层路由功能,根据报文的目的地址进行转发。

提前分库、分表,迁移到多个硬件设备,运维工作量会更少,比使用hash算法更好处理

No SQL也是这样的思想,不以结构化来绑定其自己的策略。

为什么TDM业务通过IP网络中需要建立一个隧道呢?这与TDM业务有关,因为其是同步网络,有先后顺序,没有重传机制,所以建立隧道与业务特性无关。

网络中业务平面与控制平面解耦,也是单一原则的体现。通过使用简化的设计方案能够将不必要的耦合降低最小,而MPLS为什么能够做到更为复杂的多协议的接入呢?其实其内涵就是简化机制、分离解耦。而这思想其实是借鉴了TDM的优秀思想,也是充分利用了简单的设计原则,反而让其具有支撑更为复杂的业务处理和完善的管理能力。

人类在不成熟时期,老是希望趋于复杂,但随着认识的深入,简单才是人类迈入成熟的表现。从复杂中抽象出简单,其实这是一个困难的过程,一旦抽离简单化成功,则我们的认识就会突破到新的境界。

其实简单原则的设计,稳定性强,但缺点在于无法复用,例如美国的航天飞机和中国的航天飞机就是这两个极端,由于为了达到其高度的可中用性,美国的航天飞机创作复杂,那个华丽的外型代表了美国的先进科技,但是航天飞机的可靠性就很低了,爆了好几个,而且是每发射一次都提心吊胆,其航天飞船则方便得多,始终回来以后就可以保留到博物馆。但是并不是设计的越复杂其可复用性就越好,例如AK47,就三大块,但是其设计简便灵活,非常实用,而且不卡壳,结构简单,安装快捷,所以简单就是美,不同的组件之间不要过多的依赖,在需要经久耐用的地方就要固化设计,在需要可灵活应对变化的地方就要设计到可灵活装拆,其组件间可单独创建和销毁。


猜你喜欢

转载自blog.51cto.com/13832308/2135102