作者
码砖杂役
本文为码砖杂役投稿,已获授权
上世纪60年代爆发的软件危机催生了软件工程,人们寄希望于借助工程化的手段管理、设计、构建和维护软件,自此,聪明绝顶的工程师便在追求更美好软件的漫漫长路上艰苦求索。
开发语言经历了汇编、C、C++、Java、Erlang、Python;编程范式涵盖了面向过程(POP)、面向对象(OOP)、泛型(GP)、函数式(FP);软件架构从单机到分布式到云原生,包括巨石,库组件模块服务,分层,微服务,MVC/ServiceMesh/Serverless等;而软件工程思想和方法论则包括以生命周期管理为核心注重工序的瀑布模型(WaterfallModel),以需求进化为核心注重迭代渐进的敏捷开发(AgileDevelopment),以边界划分和控制为核心注重领域建模的领域驱动设计(DDD:DomainDrivenDesign)。
世界是缤纷复杂的,要把真实世界映射到虚拟软件注定不会是一件容易的事,软件开发是权衡抉择的艺术,譬如快速交付和安全生产常常背道而驰,开发效率和运行效率总是难以一致。所以在软件发展的历史长河中,人们发明一种方法解决一个问题,而几乎总是会引入另一个问题,软件工程师不得不面对混沌不堪的世界。
面向过程(C)认为一切皆过程,现实世界都可以封装为一个个过程,通过过程串联和编排模拟世界。但随着软件被大规模用于解决复杂的商业问题,这种范式被证明缺乏足够的抽象,虽然函数可视为最小粒度的模块化技术,但依然无法掩盖其模块化能力的不足,过程和被操作数据分离也导致软件偏离高内聚低耦合的方向。
为了解决上述问题,面向对象范式(C+++/Java)被设计为通过对象建模世界,对象把属性和方法封装在一起,通过公开接口与外界交互,为软件设计提供一种逻辑层面的模块化手段;而且,对象与现实世界的事物很容易映射。对象通过组合表征更复杂的概念,通过接口泛化表达更抽象的概念。
泛型的动机则更加简单,需要一种语言机制,为解决跨越数据类型而提供标准容器的障碍,C++通过模板这种语言机制提供了参数化类型的能力,编译期的类型检查和类实例化既保障类型安全又提升执行效率,但也增加编译时间和损害可读性,特别是模板元编程等新玩法的引入则让事情更加复杂。
UML诞生于瀑布模型大行其道的时代,是独立于具体程序设计语言的面向对象建模工具,UML把面向对象开发分解为分析(OOA)、设计(OOD)和编码(OOP)三个阶段,该流程注重分析设计而轻视编码实现。
由于设计和实现被划分成2个相互钳制的阶段,所以开发过程会存在两个模型,即一个显化于UML图纸中的设计模型,一个隐匿于软件源码中的实现模型,两个阶段两个模型必然导致设计和编码的割裂,设计和实施交予不同人实施看似有利于分工协作,实则增加了沟通成本,降低了交付效率。
学院派一度追求依照架构师的UML设计图就能自动生成代码,这看起来很美,而实际上这种目标只在受限的情况下才能被满足;而在日益复杂的商业软件开发中,工程派感受到时间更多被消耗在开发和维护上,最终的交付件只能是源码而非图纸,所以开发人员只能转向设计原则(SOLID)和设计模式(GOF)寻求慰藉,设计人员则头顶“架构师”的美名天马行空。
瀑布模型的过程难以逆转,且只有到项目后期才能看到结果,针对瀑布的缺陷,敏捷开发试图从改善软件开发端到端的沟通方式入手,极限编程(XP)是一种实施敏捷开发的轻量级软件工程方法学,尝试用一种螺旋式的方式演进,极限编程正视软件活动的复杂性,承认需求在起步阶段无法固化下来,主张开发人员应该优先将精力投入到代码中,透过引入基本价值、原则、方法等概念来灵活应对需求变更。
敏捷开发在互联网的蓬勃发展中大放异彩,究其根本是因为互联网应用的需求是动态变化的,难以严格遵照沉重的传统瀑布模型流程。
重原型实现的敏捷方法甚至被曲解为完全不需要设计和文档的开发方式,敏捷的优势同时又成为它的弊端,忽视文档的重要性,在人员流动大的情况下无疑会加大维护的困难,且它在面对领域知识复杂的组织和应用时,敏捷开发也饱受质疑。
随着WebService的应用井喷,以Java为代表的新兴语言攻城拔地,Controller-Service-Dao或者SOA设计摇身变化成行业的标准解,Service层扮演着上帝类,所有的逻辑都往里塞,充斥getter/setterDAO的贫血模式弥漫着恶臭味,基于数据驱动的开发模式陷入了泥潭,领域驱动设计进入了人们的视野。
年,EricEvans提倡的领域驱动设计(DDD)视“领域内核”为企业最重要资产,主张通过通用语言(UbiquitousLanguage)消除表达的不准确性,领域驱动设计继承并发展了敏捷开发。DDD把领域和设计归为软件设计的核心,让业务人员和开发人员得到同样的重视,建议合力捕捉充血的领域模型。DDD战略设计从宏观上确定限界上下文,而战术设计在实现层面给出一些最佳实践。
传统模式秉持以数据(库)为中心的理念,而领域驱动设计则转向以领域模型为中心,这是根本性的设计转变。DDD通过四重边界划分问题空间和解空间,确定核心、通用、支撑三类子领域,在界限上下文内部,通过分层(基础层-领域层-应用层-展现层)实现内外隔离,应用层形成了一种保护层,有效地隔离了业务复杂度与技术复杂度。将领域层作为整个系统稳定而内聚的核心,是领域驱动设计的关键特征。
回顾软件工程发展过程中涌现的各种主义,每一个流行的思潮都有一套自圆其说的理论,都声称完美解决了某些问题,但又无一例外的陷入另一个框架陷阱,但又都无一能够终结软件工程的无序设计。我们无法跳出自娱自乐般无休止重构循环的怪圈,我们依然置身于充满各种技术债的困境,所以,跳出各种框架模式的精神枷锁,回过头来,我们不妨重新审视设计的本质,我们不妨想一想应对软件复杂性的根本原则。
可将解决大规模复杂软件问题的方法,简单归为几点:抽象,分解,隔离。
抽象就是归类,归类是为了复用。抽象的意义是通过表现找到事物背后的本质,抽象的目的是为了减轻认知负担,避免重复思考和劳动,精简问题空间,让人