软件设计是什么:
就是讨论要用什么技术实现功能?
就是要考虑选择哪些框架和中间件?
设计就是设计模式?
设计就是Controller、Service加Model?
……
一百个程序员,就有一百种理解。若按照这些方式去了解“软件设计”,软件设计的知识不仅散乱,且像陷入沼泽:
刚学会Java,听说Go成了新的主流,还没等下决心转语言 ,Rust现在又被吹起来
终于知道MQ能干啥了,准备从众学习Kafka,这时又看到一篇公众号说Pulsar比Kafka更好
总算理解观察者模式,却有人告诉你JDK中早就提供了原生支持,但更好的做法是Guava EventBus
好不容易弄清MVC,却发现后端现在的主要工作是写RESTful服务,Controller还没有用,就该改名Resource
……
软件设计应关注长期变化,能应对需求规模的膨胀。然而这些在不断变化的东西可能还没你的软件生命周期长,又怎能支撑长久的变化?那软件设计到底是什么?
1 核心模型
软件开发是解决由需求带来的各种问题,解决结果是可运行的交付物。如在线购物的需求,使用电商平台方案解决。软件设计就是在需求和解决方案之间架设的一个桥梁。
区别于解决简单的问题,软件开发往往是一项长期工作,是个多人运动。这就需要建立起统一的结构,以便所有人都能有共同理解。就像建筑中的图纸,懂建筑的人看了后,就会产生一个统一认知。
而在软件开发过程中,这种统一结构就是模型,软件设计就是要构建一套模型。
模型包括:
描述业务的各种实体
完成业务功能的各种组件
写代码中常用的服务(Service)、调度器(Scheduler)等概念就是一个个模型。模型是一个软件的骨架,也是一个软件之所以是这个软件的核心。一个电商平台,它不用关系型DB,还可用NoSQL,但若无产品信息,没有订单,它就肯定不是电商平台。
模型粒度可大可小。若把模型理解为一个个类,就是小模型。也可把整个系统当作一个整体,就是大模型。“高内聚、低耦合”就是对模型的要求。
这种模型有效隐藏细节,理解更容易,可继续扩展。如程序设计语言,就是提供编程模型,让我们写程序不用再面对各种硬件差异,还能够在此基础上继续提供新功能。各种框架和技术,也是提供了一个个模型,它们大幅降低了开发门槛。
所以整个计算机世界就是在这样一个又一个模型的叠加中,一点点构建。模型是分层的,就像乐高,由一个个小块构建出一个个大部件,再用这些部件组成成品。
与一些人理解的Controller、Service分层有差异。这才是在计算机行业中普遍存在的分层。网络模型就是典型分层模型。按TCP/IP分层,网络层要构建在网络接口层上,应用层要依赖传输层。
即便是在一个软件内部,模型也可分层。可以先从最核心的模型开始构建,有了这个核心模型后,可通过组合这些基础的模型,构建出上一层模型。
如交易系统设计。分析主要交易动作后,提出一个交易原语概念,包括资产冻结、解冻、出金、入金等动作。然后,把原先的交易动作变成了原语的组合。如下单是资产冻结,成交是不同账户的出金和入金,撤单则是资产解冻:
由:
交易原语保证每个业务的准确性
交易动作保证整个操作的事务性
这就是模型的分层。所以,模型是一个软件核心;模型粒度可大可小;好模型“高内聚、低耦合”;模型可分层,由底层的模型提供接口,构建出上层的模型。
仅是把软件设计理解成构建模型还不够。模型设计也不能任意妄为,需要有一定约束,即软件设计要构建的另一部分:规范。
2 约束的规范
限定什么样的需求应该以怎样方式完成。如:
业务处理相关代码,体现在领域模型
网络连接相关代码,写在网关
与外部系统集成的代码,要有防腐层
……
每个项目都有自己的规范,但问题也常驻。如
2.1 缺乏显式、统一规范
规范是维系软件长期的演化。没有显式规范,项目维系只能依靠团队个人发挥,新人往往创造不同寻常新写法,项目朝着失控发展。某项目,多种不同的做法并存:
数据库访问,有用MyBatis的,有用JDBC,也有Hibernate
外部接口设计,有用REST风格的,有用URL表示各种动作
文件组织,有的按照业务功能划分(比如,产品、订单等),有的按照代码结构划分(比如,Resource、Service等);
……
没有统一规范,每个项目上的新人都会骂街前人代码。然后,新人自己另起炉灶,又加了新东西。其实前任们的混乱一般也就是这么开始的。存在一个显式、统一的规范,项目就能按一个统一方向行进。即使后续设计要演化、规范要调整,统一规范也比散兵游勇代码更可控。
2.2 不符合软件设计原则
有次网关OOM。这个网关日常内存消耗达150G,一次流量暴增它就扛不住。后来经过优化,把内存消耗降到8G。单看数字,20倍优化,nb啊,具体咋整?
最核心内容就是构建防腐层,将请求过来的JSON转换成普通内存对象。而原来做法是把JSON解析器解析出来的对象到处用,因为这些对象上附加很多额外信息,导致占用大量内存。
只是因为旧的规范不符合软件设计原则而导致的错误:外部请求的对象需要在防腐层转换为内部对象。
2.3 防腐层
模型的一个规范:我接触防腐层的概念是从DDD的限界上下文开始。Eric用细胞膜的概念来解释“限界”的概念,细胞膜只让细胞需要的物质进入细胞,代码之间业务也存在这样一个界限,同一个对象的业务含义在不同的上下文中不一样。网上买书为例:
购买页面,关注点在这本书的名称,作者,以及分类,库存等信息
提交订单后,这本书就成为了订单上下文中的一个订单item,我们会关注这个item 的数量以及购买他的人是谁,以及书的配送地址等;
订单提交给仓库后,仓库会关心这本书还有没有库存,以及打包状态,分拣,物流等状态
防腐层是在限界上下文之间映射(说白了就是交互)的方式,体现在代码上就是一个对象的转换,这个转换的意义在于隔离变化,防止因为对象在一个上下文中的变化扩散到其他的上下文中。
2.4 关于规范
规范也是团队文化中很重要的一部分,以持续集成为例子,它的执行严格依赖于团队的开发纪律文化,以为了所谓赶进度而单元测试覆盖很低或者直接不写;采用分支策略方开发,一星期都合并不了主干,类似的人到处倒是,也就因为这一点,很多团队都在持续集成这个环节上掉队了。所以开发规范真的很重要,时刻谨记:混乱始于没有规范。
3 模型与规范
二者相辅相成。一个项目最初建立起的模型,往往要符合一定规范,而规范的制定也有赖于模型。
就像讨论户型,可按照各种方式组合不同空间(模型),却不会把厨房与卫生间放在一起(规范)。
软件设计既包含构建出一套模型,也包括制定出相应的规范。
特定技术、框架和中间件,只是支撑我们模型的实现
设计模式、Controller、Service、Model这些东西也只是一个特定的实现结果,是某些特定场景下的模型
4 总结
软件设计应该包括:
模型,是一个软件的骨架,是一个软件之所以是这个软件的核心。模型的粒度可大可小。我们所说的“高内聚、低耦合”指的就是对模型的要求,一个好的模型可以有效地隐藏细节,让开发者易于理解。模型是分层的,可以不断地叠加,基于一个基础的模型去构建上一层的模型,计算机世界就是这样一点点构建出来的。
规范,就是限定了什么样的需求应该以怎样的方式去完成。它对于维系软件长期演化至关重要。关于规范,常见的两种问题是:一个项目缺乏显式的、统一的规范;规范不符合软件设计原则。
模型与规范,二者相辅相成,一个项目最初建立起的模型,往往是要符合一定规范的,而规范的制定也有赖于模型。
软件设计,应该包括模型和规范。