程序设计的武功心法
软件 (Software)
Soft - 软的、可以轻易改变的
Ware - 产品
第一种就是让程序能动,但第二种要如何实现 ?
轻易改变是一个抽象的概念,具体的描述可以理解为 :
如何让建构产品的「程序码」更容易的、阅读、维护与扩充。
实现需求的武功心法
由五种原则的字母 组成 SOLID (坚硬的) 单字的排序 :
如果你在网路上搜寻过「软件 设计原则」还会发现有六大、七大或九大原则:
它们通常已经包含这五项。
维基百科
存在的目的是为了建置清晰、可读与可延伸的开发指南,
并且还可以应用在 测试驱动开发、敏捷开发,以及自适应软件开发的基本原则。
我认为这五项,实际上就是很多软件设计方法论的根源点。
只要完全遵守,即便没有任何的架构,也能够将程序写得井然有序、条条有理。
看到这个名字,直觉想到的是「一个函式只做一件事」或「一个类别只做一种事」。
这样的理解不太精确,因为这个是「重构的原则」并不是 SRP。
一个模组应该有一个且只有一个理由会使其改变
更容易理解的描述 :
一个模组应该只对唯一的一个角色负责
合并起来重新描述
一个原始档案的类别只会对系统中特定角色的使用者负责,
只有当这个特定群体的需求改变,程序码才会改变。
从这一段描述可以知道 :
单一职责原则,实际上是一种「分类」的方法,依据的是「不同角色的使用者」(变化)。
一个简单的理解
程序之所以会修改,通常是使用者需求的改变。
假设 : 一个类别只做一种事,但这个事刚好被两个部门的角色使用到
当其中一个部门提出新的需求,调整时就会影响到另外一个
虽然该部门提升业务能力,但另外一个部门却得为他们的收益付出代价。
(被影响的一方,基本上都无法接受。)
更好的情况
建置时分别独立,各自对自己的使用者负责。
以开发者的角度来看:
虽然会导致程序码重复,但复制代码的成本绝对会比多个角色调整、验证
与验证遗漏,导致错误的影响付出较少的代价。
这个原则出现的原因是由於「Conway 定律」的积极推论
Conway 定律说的是 :
软件产品的架构与专案团队的组织结构是互相影响的
Conway 定律,积极推论 :
软件系统的最佳结构 深深受到使用它的组织社会结构所影响
也就是说 :
组织架构通常也是软件架构的最佳参照。
除非组织的部门真的有共用到某些资源,否则我们开发的程序,就不应该将不同角色的程序模组重复使用。
DDD 领域驱动设计的分层结构,可以很好的实现
在各个层级与区块中都有一个单一负责的对象,因此符合单一职责原则。
一个软件制品应该对於扩展是开放的 但对於修改是封闭的
话句话说 : 如果今天在建一栋楼
一楼已经完成,追加新的设计,只能从二楼开始。
不应为了某种需求 在一楼的墙壁钻孔、打洞。
万一刚好是某个重要结构,可能导致倾斜或倒塌,这样肯定得不偿失。
软件设计的架构
与其说要遵循这个原则,不如说是要「设计」成符合这个原则的系统。
同样以建楼为例 :
一楼再搭建时,就设想还会添加哪些设备
开头提到过的软件第二种价值 :「让电脑的行为可以轻易改变」。
或具体说法建构,更容易阅读、维护与扩充的产品程序码。
「开放 - 封闭原则」 就是一个实际的【指导方针】
理想状态,在扩展新功能时,修改旧程序的数量,无限趋近於零。
原则的描述,它是一个「大原则」只指引方向。
具体的作法 :
将「重要」不可轻易改变的模组,保护起来避免外部修改。
将「动态」需要时常变更的模组,保留空间提供後续调整。
DDD 领域驱动设计的架构
领域层通常就是业务的核心逻辑是组织创造收益的根本原因,不可能经常更动。
(会变更的情况 通常都是组织经过重大调整。)
所以功能扩充会在「领域层」添加新的「业务区块」,然後才在「容易变动的应用层与使用者介面层」,调整服务的项目清单。
「里氏」是美国计算机科学家 - Barbarra Liskov
她於 1988 年,写下定义子型态的方式 :
这里需要如下的替换性质 :
若对於型态 S 每个物件 o1 都存在一个型态为 T 的物件 o2,
使得在所有针对 T 编写的程序 P 中,用 o1 替代 o2 後 ,程序 P 的行为功能不变,则 S 是 T 的子型态。
这一段描述,使用许多变数。
为了更好的理解,可以先关注「子型态」与「替换」这两个关键词。
例如 :
应用程序不需要依赖两个子型态类别的任何一种,两种子型态的授权又都可以替换成授权介面的物件。
该范例符合里氏替换原则
用个人或企业授权的物件,替代授权介面的物件功能不变
首先,父类别-抽象介面,存在的目的是什麽 ?
维基百科,里氏替换原则的相关连结: 「契约式设计」
契约式设计
要求软件设计者必须为软件组件定义正式的、精确的并且可验证的介面。
也就是说:
介面存在的目的,就像是一种契约,用来验证实作提供的东西到底府不符合需求。
不验证没有契约,但拿到实际且正确的东西,当然没有问题。 (替代)
但假设 : 已经签好契约
厂商却发给我另外一种东西,还强迫必须接受
对应到程序码 : 负责的模组必须加上各种判断,来辨别这个东西到底是什麽
在系统中,额外机制就是混乱因子。
将会导致整体架构逐渐失序,使得系统难以维护与扩充更新。
不应强迫客户端依赖它不使用的方法
原则的由来
三个使用者,同时使用一个模组,但各自都只有使用其中一个方法。
对於任一使用者来说,模组中另外两个方法是他不需要的。
所以在使用者与模组之间,又各自新增一个介面 :
该介面只定义使用者会使用的方法,并且隔离彼此。(名称由来)
背後的罗辑
就是不强迫客户端依赖它不使用的方法。(客户端不一定是真实的使用者,有可能是上层模组与下层模组的使用关系。)
这个原则,做了两件事情:
第一个好处
维护客户端的工程师,可以清楚的知道模组需要的是什麽服务。
(大模组与我之间,关联并没有那麽的直接)
第二个好处
客户端对大模组的依赖解除
解除大模组依赖的好处 ?
大模组的存在是不太正常,但却又自然而然。
因为程序从一开始创建,并不会立马就想到未来会有多少功能。
在原本的模组,拓展新功能会是个省时省力的方法。
一次两次的叠加没啥问题,但当发现已经有点臃肿时,已经无法舍弃。
如何修复 ?
工程师看见这个问题,回头修改会牵连太广、成本太大,而且也会违反「开放 - 封闭原则」。
更好的做法是为未来做准备
如果有个全新且更精准的模组替代,对於客户端与依赖的小接口,基本上什麽都不用做。
因为是新模组依赖於我的小接口,而不是我客户端模组还要改动程序码去依赖新模组。
高层次的模组不应该依赖於低层次的模组,两者都应该依赖於抽象介面。
传统的应用程序架构是高层次透过低层次实现功能
例如 :
将分析报告保存在系统中,是分析的模组透过资料库的模组使用储存的功能。
依据原则
两者的依赖关系要调整成分析报告使用抽象介面的保存方法,然後资料库在依据抽象介面的定义,实作资料库的储存功能。
高层次为什麽是高层次 ?
因为它是企业「创造收益」的核心规则,即便没有系统,使用纸笔作业也依然成立。
因此,不能随意变动应该被保护起来
如果,依赖於低层次模组
代表低层次模组变动会回头影响到高层次模组。严重一点出现异常,更可能导致高层次模组无法作业。
稳妥起见 : 解开依赖关系
即便低层次模组发生问题,最多也只是无法储存,不会导致高层次模组业务停摆。
为什麽依赖於抽象介面?
为了可以拓展新的功能
就像前面提到过的抽象介面是契约精神的展现,我要的东西规格已经定义清楚。
但如果有更好的方法,就是额外实作新的功能,将原本旧的模组替换掉即可。
上述的 SOLID 设计原则的描述顺序,应该多少会感到有些混乱。
这是因为 SOLID 只是单字字母的排序,并不是重要性或者因果推演的排序。
我认为可以分类成三个部分:
第一个部分 : 开放 - 封闭原则
它是一个大方向原则,总体目标就是将系统设计成「容易拓展新的,并且少量修改旧的」。
第二个部分 : 单一职责原则
讲的是「分类」的方法,可以运用在「开放 - 封闭原则」,「封闭」的部分,
透过需求根源点 - 「角色的分类」,将可能会修改的部分集中。
第三个部分 : 依赖反转原则、里氏替换原则、介面隔离原则
讲的是介面的使用方式,可以运用在「开放 - 封闭原则」,「开放」的部分。
依赖反转原则 :
告诉你使用抽象介面,可以在保证核心正常运作的情况下,还能够拓展新功能。
里氏替换原则 :
介面与类别 - 一对多关系
它可以帮助系统,在拓展功能时,保证子型态模组的可靠性。
介面隔离原则 :
介面与类别 - 多对一关系
它可以帮助系统,无法分割类别时,保证拓展功能的纯粹性,使得模组具有高内聚与可读性。
我在搜寻相关资讯时,总觉得五项原则,就像是 :
一千个读者心中,有一千个哈姆雷特。
不是原则吗 ?
怎麽讲的都不太一样 !?
如果上述有错或者跟你想的有出入,都可以留言讨论。
前言 因为小弟有一些专案需求需要使用到 iBeacon,因此就有深入去了解 iBeacon 套件用法...
本系列文章同步发布於笔者网站 上篇介绍了 Open vSwitch with Provider Ne...
Setup 和 Teardown 在单元测试的艺术提到:进行单元测试时,很重要的一点是确保之前测试过...
早期运动Day10 - 对於压力,我们都需要平衡报导 今天在运动时,听着《自控力:和压力做朋友》 里...
升上高中也有专题研究的学分。为了找到适合的题目,我和同个专研的同学一起到师大资工(和科学班合作的校系...