重燃程序语言软件开发
你还在用程序词汇的词汇,写着面向全国操作过程的标识符吗?
一 序言
在西欧巴洛克末期,一名了不起的物理学家物理学家-第谷,在彼时明晰提出了地心说,反驳了以火星为银河系服务中心的行星价值观,由于价值观极为全面性,直至数十年后第谷开普勒等人历经中后期研究,才逐渐普遍认可并逐渐形成了彼时第谷价值观的纯洁性。
,在计算机系统科学应用领域也拉开序幕着反之亦然的故事情节。数十年前 Kristen Nygaard发明者了Simula词汇,这也是那时被尊重的当今世界上第二个明晰同时实现程序词汇程式结构设计的词汇,他明晰提出了如前所述类的程式结构设计艺术风格,确认了"天地万物皆第一类"而此程序词汇方法论的"毁灭者价值观",但在彼时反之亦然未受普遍认可。Peter Norvig 在 Design Patterns in Dynamic Programming 为此不予了反驳,并论述他们无须什么程序词汇。数十年后 Robert C.Martin、Bertrand Meyer、Martin Fowler等人,再度佐证并结晶了程序词汇的结构设计经营理念。程式结构设计价值观的重构也不是操之过急,但在而此个世纪末获得了急速的发展。
二 程式结构设计价值观的重构
从六十年代六十年代冯·诺圣马蒂缔造首台计算机系统开始,始终至那时只有短短的70年时间,从因节计算机系统词汇FORTRAN,到那时他们常见的C++,JAVA,PYTHON等,计算机系统词汇的重构速率远超他们所采用的任何人两门自然词汇。从最先的面向全国电脑,再到面向全国操作过程,到进化为那时他们所采用的程序词汇。维持不变的是程式结构设计的核心经营理念,变动的是程式结构设计的价值观。
1 面向全国电脑
计算机系统是01的当今世界,最先的程序就是通过这种01电脑码来控制计算机系统的,比如0000代表读取,0001代表保存等。方法论上这才是当今世界上最快的词汇,无需翻译直接运行。但弊端也很明显,那就是几乎无法维护。运行5毫秒,程式结构设计3小时。由于电脑码无法维护,人们在此基础上发明者了汇编词汇,READ代表0000,SAVE代表0001,这样更易理解和维护。虽然汇编在电脑码上更可视更直观,但本质上还是两门面向全国电脑的词汇,依然还是存在很高的程式结构设计成本。
2 面向全国操作过程
面向全国操作过程是一种以事件为服务中心的程式结构设计价值观,相比于面向全国电脑的程式结构设计方式,是一种巨大的进步。他们不用再关注电脑指令,而是聚焦于具体的问题。它将一件事情拆分成若干个执行的步骤,然后通过函数同时实现每一个环节,最终串联起来完成软件开发。
流程化的结构设计让编码更加清晰,相比于电脑码或汇编,开发效率获得了极大改善,包括那时仍然有很多场景更适合面向全国操作过程来完成。但计算机系统科学最大的成本在于维护,由于面向全国操作过程更多聚焦于问题的解决而非应用领域的结构设计,标识符的重用性与扩展性弊端逐渐彰显出来,随着业务逻辑越来越复杂,软件的复杂性也变得越来越不可控。
3 程序词汇
程序词汇以分类的方式进行思考和解决问题,程序词汇的核心是抽象思维。通过抽象提取共性,通过封装收敛逻辑,通过多态同时实现扩展。程序词汇的价值观本质是将数据与行为做结合,数据与行为的载体称之为第一类,而第一类要负责的是定义职责的边界。面向全国操作过程简单快捷,在处理简单的业务系统时,程序词汇的效果其实并不如面向全国操作过程。但在复杂系统的结构设计上,通用性的业务流程,个性化的差异点,原子化的功能组件等等,更适合程序词汇的程式结构设计模式。
但程序词汇也不是银弹,甚至有些场景用比不用还糟,一切的根源就是抽象。根据 MECE法则 将一个事物进行分类,if else 是计算机系统科学最严谨的分类。他们在结构设计抽象进行分类时,不一定能抓住最合适的切入点,错误的抽象比没有抽象复杂度更高。里氏替换原则的创始人Barbara Liskov 谈抽象的力量 The Power of Abstraction。
三 面向全国应用领域结构设计
1 真在程序词汇吗
// 捡入客户到销售私海publicString pick(String salesId, String customerId){// 校验是否销售角色Operatoroperator= dao.find("db_operator", salesId);if("SALES".equals(operator.getRole())){return"operator not sales";
}// 校验销售库容是否已满int hold = dao.find("sales_hold", salesId);
List customers = dao.find("db_sales_customer", salesId);if(customers.size() >= hold){return"hold is full";
}// 校验是否客户可捡入Opportunity opp = dao.find("db_opportunity", customerId);if(opp.getOwnerId() !=null){return"can not pick others customer";
}// 捡入客户opp.setOwnerId(salesId);
dao.save(opp);return"success";
}
这是一段CRM应用领域销售捡入客户的业务标识符。这是他们熟悉的Java-程序词汇词汇,但这是一段程序词汇标识符吗?完全面向全国事件,没有封装没有抽象,难以复用不易扩展。相信在他们标识符库,这样的标识符不在少数。为什么?因为它将成本放到了未来。他们将此称之为披着程序词汇的外衣,干着面向全国操作过程的勾当。
在系统结构设计的早期,业务规则不复杂,逻辑复用与扩展体现得也并不强烈,而面向全国操作过程的标识符在支撑这些相对简单的业务场景是非常容易的。但计算机系统科学最大的成本在于维护,当系统足够复杂时,当初那些写起来最easy的标识符,将来就是维护起来最hard的债务。
2 应用领域驱动结构设计
还有一种方式他们也可以这么来写,新增商机模型,通过商机来关联客户与销售之间的关系。而商机的归属也分为公海、私海等具体归属场景。商机除了有必要的数据外,还应该收拢一些业务行为,捡入、开放、分发等。通过应用领域建模,利用程序词汇的特性,确认边界、抽象封装、行为收拢,对业务分而治之。
当他们业务上说商机分发到私海,而他们标识符则是opportunity.pickTo(privateSea)。这是应用领域驱动所带来的改变,面向全国应用领域结构设计,程序词汇程式结构设计,应用领域模型的抽象就是对现实当今世界的描述。但这并非操之过急的操作过程,当你只触碰到大象的身板时,你认为这是一扇门,当你触碰到大象的耳朵时,你认为是一片芭蕉。只有他们不断抽象不断重构,他们才能愈发接近业务的真实模型。
Use the model as the backbone of a language, Recognize that a change in the language is a change to the model.Then refactor the code, renaming classes, methods, and modules to conform to the new model--- Eric Evans 《Domain-Driven Design Reference》译:采用模型作为词汇的支柱,意识到言语的改变就是对模型的改变,然后重构标识符,重命名类,方法和模块以符合新模型。
3 软件的复杂度
这是Martin Flowler在 Patterns of Enterprise Application Architecture 这本书中所提的关于复杂度的观点,他将软件开发分为数据驱动与应用领域驱动。很多时候开发的方式大家倾向于,拿到需求后看表怎么结构设计,然后看标识符怎么写,这其实也是面向全国操作过程的一个表现。在软件初期,这样的方式复杂度是很低的,没有复用没有扩展,一人吃饱全家不饿。但随着业务的发展系统的重构,复杂度会陡增。
而一开始通过应用领域建模方式,以程序词汇思维进行软件开发,复杂度的上升可以获得很好的控制。先思考他们应用领域模型的结构设计,这是他们业务系统的核心,再逐渐外延,到接口到缓存到数据库。但应用领域的边界,模型的抽象,从刚开始成本是高于数据驱动的。
The goal of software architecture is to minimize the human resources required to build and maintain the required system.--- Robert C. Martin 《Clean Architecture》译:软件架构的毁灭者目标是,用最小的人力成本来满足构建和维护该系统的需求
如果刚开始他们直接以数据驱动面向全国操作过程的流程式标识符,可以很轻松的解决问题,并且之后也不会面向全国更复杂的场景与业务,那这套模式就是最适合这套系统的架构结构设计。如果他们的系统会随着业务的发展逐渐复杂,每一次的发布都会提升下一次发布的成本,那么他们应该考虑投入必要的成本来面向全国应用领域驱动结构设计。
四 抽象的品质
抽象永远是计算机系统科学应用领域最难的命题,因为它没有规则,没有标准,甚至没有对错,只分好坏,只分是否适合。反之亦然一份淘宝商品模型的应用领域抽象,可以算是业界标杆了,但它并非适合你的系统。那他们该如何驾驭抽象呢?UML的创始人Grady booch在 Object Oriented Analysis and Design with Applications 一书中,提到了评判一种抽象的品质可以通过如下5个指标进行测量:耦合性、内聚性、充分性、完整性与基础性。
1 耦合性
一个模块与另一个模块之间建立起来的关联强度的测量称之为耦合性。一个模块与其他模块高度相关,那它就难以独立得被理解、变动或修改。TCL词汇发明者者John Ousterhout教授也有反之亦然的观点。他们应该尽可能减少模块间的耦合依赖,从而降低复杂度。
Complexity is caused by two things: dependencies and obscurity.--- John Ousterhout 《A Philosophy of Software Design》译:复杂性是由两件事引起的:依赖性和模糊性。
但这并不意味着他们就不需要耦合。软件开发是朝着扩展性与复用性发展的,继承天然就是强耦合,但它为他们提供了软件系统的复用能力。如同摩擦力一般,起初以为它阻碍了他们前进的步伐,实则没有摩擦力,他们寸步难行。
2 内聚性
内聚性与耦合性都是结构化结构设计中的概念,内聚性测量的是单个模块里,各个元素的的联系程度。高内聚低耦合,是写在教科书里的观点,但他们也并非何时何地都应该盲目追求高内聚。
内聚性分为偶然性内聚与功能性内聚。金鱼与消防栓,他们一样可以因为它们都不会吹口哨,将他们抽象在一起,但很明显他们不该这么干,这就是偶然性内聚。最希望出现的内聚是功能性内聚,即一个类或模式的各元素一同工作,提供某种清晰界定的行为。比如我将消防栓、灭火器、探测仪等内聚在一起,他们是都属于消防设施,这是功能性内聚。
3 充分性
充分性指一个类或模块需要应该记录某个抽象足够多的特征,否则组件将变得不用。比如Set集合类,如果他们只有remove、get却没有add,那这个类一定没法用了,因为它没有形成一个闭环 。不过这种情况相对出现较少,只要当他们真正去采用,完成它的一系列流程操作后,缺失的一些内容是比较容易发现并解决的。
4 完整性
完整性指类或模块需要记录某个抽象全部有意义的特征。完整性与充分性相对,充分性是模块的最小内涵,完整性则是模块的最大外延。他们走完一个流程,可以清晰得知道他们缺哪些,可以让他们马上补齐抽象的充分性,但可能在另一个场景这些特征就又不够了,他们需要考虑模块还需要具备哪些特征或者他应该还补齐哪些能力。
5 基础性
充分性、完整性与基础性可以说是3个相互辅助相互制约的原则。基础性指抽象底层表现形式最有效的基础性操作(似乎用自己在解释自己)。比如Set中的add操作,是一个基础性操作,在已经存在add的情况下,他们是否需要一次性添加2个元素的add2操作?很明显他们不需要,因为他们可以通过调用2次add来完成,所以add2并不符合基础性。
但他们试想另一个场景,如果要判断一个元素是否在Set集合中,他们是否需要增加一个contains方法。Set已经有foreach、get等操作了,按照基础性方法论,他们也可以把所有的元素遍历一遍,然后看该元素是否包含其中。但基础性有一个关键词叫有效,虽然他们可以通过一些基础操作进行组合,但它会消耗大量资源或者复杂度,那它也可以作为基础操作的一个候选者。
五 软件开发原则
抽象的品质可以指导他们抽象与建模,但总归还是不够具象,在此基础上一些更落地更易执行的结构设计原则涌现出来,最著名的当属程序词汇的五大结构设计原则 S.O.L.I.D。
1 开闭原则OCP
Software entities should be open for extension,but closed for modification-- Bertrand Meyer 《Object Oriented Software Construction》译:软件实体应当对扩展开放,对修改关闭。
开闭原则是Bertrand Meyer 1988年在 Object Oriented Software Construction 书中所提到一个观点,软件实体应该对扩展开放对修改关闭。他们来看一个关于开闭原则的例子,需要传进来的用户列表,分类型进行二次排序,他们标识符可以这样写。
publicList sort(List users, Enumtype){if(type== AGE){// 按年龄排序users = resortListByAge(users);
}elseif(type== NAME){// 按名称首字母排序users = resortListByName(users);
}elseif(type== NAME){// 按客户健康分排序users = resortListByHealth(users);
}returnusers;
}
上述标识符就是一个明显违背开闭原则的例子,当他们需要新增一种类似时,需要修改主流程。由于这些方法都定义在私有函数中,他们哪怕对现有逻辑做调整,他们也需要修改到这份标识符文件。
还有一种做法,可以同时实现对扩展开放对修改关闭,JDK的排序其实已经为他们定义了这样的标准。他们将不同的排序方式进行抽象,每种逻辑单独同时实现,单个调整逻辑不影响其他内容,新增排序方式也无需对已有模块进行调整。
2 依赖倒置DIP
High level modules shouldnot depend upon low level modules.Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions--- Robert C.Martin C++ Report 1996译:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。
Robert C.Martin是《Clean Code》《Code Architecture》两本经典书籍的作者,1996年他在C++ Report中发表了一篇名为 The Dependency Inversion Principle 的文章。他认为模块间的依赖应该是有序的,高层不应该依赖低层,低层应该依赖高层,抽象不应该依赖细节,细节应该依赖抽象。
怎么理解Robert C.Martin的而此观点。他们看这张图,他们的手可以握住这个杯子,是他们依赖杯子吗?有人说他们需要调杯子提供的hold服务,他们才能握住它,所以是他们依赖杯子。但他们再思考一下,棍子他们是不是也可以握,水壶他们也可以握,但猫狗却不行,为什么?因为他们的杯子是按照他们的手型进行结构设计的,他们定义了一个可握持的holdable接口,杯子依赖他们的需求进行结构设计。所以是杯子依赖他们,而非他们依赖杯子。
依赖倒置原则并非一个新缔造的方法论,他们生活的很多地方都有在运用。比如一家公司需要设立法人,如果这家公司出了问题,监管局就会找公司法人。并非监管局依赖公司提供的法人职位,它可以找到人,而是公司依赖监管局的要求,才设立法人职位。这也是依赖倒置的一种表现。
3 其他结构设计原则
这里没有一一将 S.O.L.I.D 一一列举完,大家想了解的可以自行查阅。除了SOLID之外,还有一些其他的结构设计原则,反之亦然也非常优秀。
PLOA最小惊讶原则
If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature-- Michael F. Cowlishaw译:如果必要的特征具有较高的惊人因素,则可能需要重新结构设计该特征。
PLOA最小惊讶原则是斯坦福大家计算机系统教授 Michael F. Cowlishaw 明晰提出的。不管你的标识符有多好,如果大部分人都为此感到吃惊,或许他们应该重新结构设计它。JDK中就存在一例违反PLOA原则的案例,他们来看下面这段标识符。
在分享会上,我故意将这行注释遮盖起来,大家都猜不到 newFormatter.getClass() 这句标识符写在这里的作用。如果要检查空指针,完全可以用Objects工具类提供的方法,同时实现完全一样,但标识符表现出来的含义就千差万别了。
KISS简单原则
Keep it Simple and Stupid-- Robert S. Kaplan译:保持愚蠢,保持简单
KISS原则是 Robert S. Kaplan 明晰提出的一个方法论,Kaplan并非是一个软件学家,他是平衡积分卡Balanced Scorecard创始人,而他所明晰提出的这个方法论对软件行业依然适用。把事情变复杂很简单,把事情变简单很复杂。他们需要尽量让复杂的问题简明化、简单化。
六 写在最后
软件开发的最大目标,就是降低复杂性,天地万物不为我所有,但天地万物皆为我用。引用JDK集合框架创办人Josh Bloch 的一句话来结束。学习程式结构设计艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则。
You should not slavishly follow these rules, but violate them only occasionally and with good reason. Learning the art of programming, like most other disciplines, consists of first learning the rules and then learning when to break them.--- Josh Bloch 《Effective Java》译:你不该盲目的遵从这些规则,应该只在偶尔情况下,有充分理由后才去打破这些规则学习程式结构设计艺术首先要学会基本的规则,然后才能知道什么时候可以打破这些规则
参阅书籍
1、《Object Oriented Analysis and Design with Applications》https://niexiaolong.github.io/Object%20Oriented%20Analysis%20and%20Design%20with%20Applications.pdf2、《Clean Architecture》https://detail.tmall.com/item.htm?id=6543927642493、《A Philosophy of Software Design》https://www.amazon.com/-/zh/dp/173210221X/ref=sr_1_1?qid=1636246895
作者 | 聂晓龙
原文链接:https://developer.aliyun.com/article/808762?utm_content=g_1000309141
本文为阿里云原创内容,未经允许不得转载。
上一篇:详解开发人员亲善的应用软件