本文主要内容来自 SpriCoder的博客,更换了更清晰的图片并对原文的疏漏做了补充和修正。
什么是软件设计
软件设计重要
- 软件设计是指关于软件对象的设计,是一种设计活动。软件设计既指软件对象实现的规格说明,又指这个规格说明产生的过程。
- 软件设计活动以需求开发的制品(需求规格说明和分析模型)为基础,构建软件设计方案描述和原型,为后期的构造活动提供规划或蓝图。
- 软件设计兼具工程性和艺术性,由于软件系统的可变性,软件设计具有演化性,也因为软件设计的过程实际上就是一系列决策发生的过程,软件设计具有决策性。
软件设计的核心思想
- 软件设计方法的核心问题:控制系统复杂度
- 分解与抽象是软件设计的核心思想,两者都是由层次性的,彼此之间可以嵌套使用。
- 分解:横向上将系统分割为几个相对简单的子系统与子系统之间的关系
- 抽象:在纵向上聚焦个子系统的接口(这里的接口与实现相对),可以分离接口和实现,使得人们更好的关注软件系统本质,降低复杂度。
软件设计思想的发展
- 从程序设计到软件设计,再到大规模软件设计
- 在大规模软件设计的时候,一个产品目标可能要做很多的功能
软件设计的核心思想
为什么要设计
- 事物的复杂性 VS 思维的有限性
- \(7\pm 2\) 规则:在一个屏幕上,同时出现多位数字或字母,瞬间出现和消失,超过 \(7\pm 2\) 的话,可能比原来记住的更好
- 关注点分离与层次性
为什么软件是复杂的?
软件的复杂性是一个本质的东西,而不是一个偶然出现的
- 解决的问题域时很复杂的
- 管理的开发过程是复杂的
- 软件的灵活性导致的复杂性
- 离散系统行为的符号化的问题
设计的复杂度
- 事物复杂度 VS 载体复杂度:对外表现与内部结构
- 设计复杂度 = 事物复杂度 + 载体与事物的适配复杂度
如何去控制复杂度?
- 软件设计的核心思想 —— 分解与抽象
理解软件设计
什么是设计
- Wiki:设计是为制作每个对象或系统奠定基础的计划。
- [Brooks 2010]认为上述定义的精髓在于计划、思维和后续执行。
- [Ralph 2009]将设计定义为:
- 设计(名词):一个对象的规格说明。它由人创造,有明确的目标,适用于特殊的环境,由一些基础类型构件组成,满足一个需求集合,受一定的限制条件约束。
- 设计(动词):在一个环境中创建对象的规格说明
- 设计经常需要一个设计师考虑一个对象或过程的审美、功能以及其他方面,这通常需要进行相当的研究、思考、建模、交互调整和重新设计。
工程设计还是艺术设计
- 软件设计应该偏向工程还是设计?
- [Faste2001]认为软件设计要:时刻保持以用户为中心,为其建造有用的软件产品;将设计知识科学化、系统化,并能够通过职业教育产生合格的软件设计师; 能够进行设计决策与折中,解决设计过程中出现的不确定性、信息不充分、要求冲突等复杂情况。
- 为什么要折衷?因为问题是不确定的,是有冲突的
- Vitruvius(《De Architectura》,公元前 22 年)认为好的建筑架构要满足“效用(Useful)、 坚固 (Solid)美感(Beautiful)”三个方面的要求。
- [Brooks2010]认为重要的美感因素包括:简洁、结构清晰和一致。
- [Smith1996]认为在软件设计(尤其是人机交互设计)中艺术始终都处于中心地位,比工程性更加重要,为此设计师需要学会:发散性思维和创新;与用户共情,体会他们的内心感受;进行相关因素的评价和平衡,例如可靠性与时尚(Fashion)、简洁性与可修改性等;构思与想象,设计软件产品的可视化外观(Visualization)。
- Design = Engineering + Art 设计等于工程加艺术
- 苹果手机与安卓手机
- 苹果手机是设计师设计出来的
- 安卓手机是工程师设计出来的
- 现在安卓手机和苹果手机之间的区别也在逐渐减小
Rational or Pragmatic? 理性主义 or 经验主义?
理性主义代表
- 理性主义更看重设计的工程性,希望以科学化知识为基础,利用模型语言、建模方法、工具支持,将软件设计过程组织成系统、规律的模型建立过程 [McPhee1996]。
- 在考虑到人的因素时,理想主义认为人是优秀的,虽然会犯错,但是可以通过教育不断完善自己 [Brooks2010]。
- 设计方法学的目标就是不断克服人的弱点,持续完善软件设计过程中的不足, 最终达到完美[McPhee1996]。
- 形式化软件工程的支持者是典型的理想主义:通过数学证明来证明软件工程
经验主义代表
- 经验主义者则在重视工程性的同时,也强调艺术性,要求给软件设计过程框架添加一些灵活性以应对设计中人的因素。
- [Parnas1986]曾指出没有过程指导和完全依赖个人的软件设计活动是不能接受的,因为不能保证质量和工程性。但是[Parnas1986]也指出一些人的因素决定了完全理性的设计过程是不存在的:
- 用户并不知道他们到底想要怎样的需求;
- 即使用户知道需要什么,仍然有些事情需要反复和迭代才能发现或理解;
- 人类的认知能力有限;
- 需求的变更无法避免;
- 人类总是会犯错的;
- 人们会固守一些旧有的设计理念;比如诺基亚
- 不合适复用。
- 所以,[Parnas1986]认为软件设计需要使用一些方法弥补人的缺陷,以建立一个尽可能好(不坏就行)的软件设计过程。文档化、原型、尽早验证、迭代式开发等都被实践证明能够有效弥补人类的缺陷[Parnas1986, Brooks 2010]
软件设计的演化性
- 需求是外部表现,需求和内部结构是有鸿沟的。
- 非功能需求(质量需求)
- 始终进行迭代。
设计的决策
- [Freeman1980]认为:软件设计是一种问题求解和决策的过程;问题空间是用户的需求和项目约束,解空间是软件设计方案;从问题空间到解空间的转换是一个跳跃性的过程,需要发挥设计师的创造性,设计师跳跃性地建立解决方案的过程被称为决策。
- 软件设计的问题求解与决策比普通数学问题的求解与决策要困难的多,因为软件设计面对的问题通常都是不规则的(Ill-Structured)[Simon1978],包含有很多的不确定性和信息不充分情景,所以进行设计决策时并不能保证决策的正确性,往往需要在很长时间之后才能通过验证发现之前决策的正确与否。
决策的约束性
- 约束满足与决策
- 约束:
- 需求;环境;资源;技术 …
- 最初的需求确定了对设计情况的最基本的约束和要求。
- 通常,在设计工作本身中最终会发现更多的约束。
- 约束既适用于设计的工件,也适用于设计活动中涉及的流程和参与者
- 约束满足
决策的多样性
- 决策的选择性
- 多个同样好的方案,选择一个
- 对于方案进行筛选,找到一个合适的
- 充分理解足够好的概念
- 设计问题的解决方案空间非常大,其庞大的规模消除了穷举搜索,这是一种可能的问题解决技术
- “设计”的特征在于各种设计备选方案之间的一系列决策
决策的演化性
- 遵循路线:问题决策验证下一个问题
- 决策的顺序影响:上一个决策会设计到下一步行为。
- 前一个决策会影响后一个,而且不可预见
- 每个不同的观点都可能以不同且不可预测的方式影响设计的进度。
- 决策的不可逆性,设计决策一定要慎重!
- 无法完全消除一个前期错误的决策
- 决策要一致!
- 决策的概念完整性也十分重要!
- 决策一定要慎重,不能过于随意。
- 从服务、包装、售卖都会给你提供一种一致的体验。
- 魅族的人机交互
软件设计的分层重要
- 一种是分为软件体系结构和软件详细设计。
- 另一种是分为高层设计、中层设计和低层设计。
- 高层设计:基于反映软件高层抽象的构件设计,描述系统的高层结构、关注点和设计决策。
- 中层设计:更加关注组成构件的模块的设计、导入/导出、过程之间调用关系或者类之间的写作
- 低层设计:深入模块和类的内部,关注具体的数据结构、算法、类型、语句和控制结构等。
程序设计的建立
- 1950s
- 第一代语言机器语言,第二代语言汇编语言
- 语句为最小单位
- 1960s
- 第三代语言
- 类型与函数:第一次复杂系统分割
- 1970s
- 类型与函数的成熟:形式化方法
- 数据结构 + 算法 = 程序
低层设计
- 将基本的语言单位(类型与语句),组织起来,建立高质量的数据结构+算法
- 常见设计场景:
- 数组的使用,链表的使用,内存的使用,遍历算法,递归算法…
- 一次问相对比较大的内存,然后我们自己在进行的内存的分配。
- 经典场景:
- 堆栈,队列,树,排序算法,查找算法…
- 数据结构与算法审美:
- 简洁、结构清晰,坚固(可靠、高效、易读)
- 数据结构与算法课程
- <计算机程序设计艺术> 第四本计算机程序设计艺术>
低层设计的本质
- 屏蔽程序中复杂数据结构与算法的实现细节!
- 对于复杂数据结构与算法的实现细节隐藏。
低层设计:代码设计
- 对一个方法/函数的内部代码进行设计
- 又被称为软件构造,通常由程序员独立完成
- 依赖于语言提供的机制(程序设计课程)
- 面向对象和结构化
- 指针或地址
模块划分
- 1970s:函数的成熟与模块的出现
- 模块划分:将系统分成简单片段:片段有名字,可以被反复使用
- 名字和使用方法称为模块的抽象与接口
- 模块内部的程序片段为精化与实现
中层设计的开始
- 模块划分隐藏一些程序片段(数据结构+算法)的细节,暴露接口于外界
- 以模块作为概念
模块化的目标:完全独立性
完全独立有助于理解、使用与复用、开发、修改
模块化的问题与困难
- 程序片段之间不可能是完全独立的
- 方法:实现尽可能的独立
- 模块化
- 信息隐藏
- 抽象数据类型
- 封装
- …
中层设计总结——设计目标
- 最终审美目标:简洁性、结构清晰、一致性、质量(可修改、易开发(易理解、易测试、易调试)、易复用)
- 直接评价标准:模块化;信息隐藏;OO 原则
中低层设计的问题
- 《Programming-in-the-Small VS Programming-in-the-Large》
- 过于依赖细节
- 连接与依赖
- 接口与实现
- 忽略的关键因素:无法有效抽象部件的整体特性
- 总体结构
- 质量属性
- 大型软件开发的一个根本不同是它更关注如何将大批独立模块组织形成一个“系统”,也就是说更重视系统的总体组织
- 12306 的宕机问题,我们也不能承受过亿的 PV,使用硬件不划算,但是我们可以快速的恢复就很好
- 服务器之间的负载均衡,部分静态数据,缓存和反向代理
高层设计:体系结构
- 部件承载了系统主要的计算与状态
- 连接件承载部件之间的交互
- 部件与连接件都是抽象的类型定义(就像类定义),它们的实例(就像类的对象实例)组织构成软件系统的整体结构,配置将它们的实例连接起来
- 连接件是一个与部件平等的单位
- 消息队列、方法调用都可以被我们理解为连接件
- 和中低层设计完全的不同
敏捷视点
- 只有高层设计良好,底层设计才可能良好
- 只有写完并测试代码之后,才能算是完成了设计!
- 源代码可能是主要的设计文档,但它通常不是唯一一个必须的。
软件设计过程、方法和模型、描述
软件设计过程的主要活动
- 分析设计出发点
- 建立候选方案
- 生成最终方案评价
- 设计需求:对于设计我们要做的事情
设计的方法
- 软件设计的方法可以分为以下几种[SWEBOK 2004]:
- 结构化设计方法
- 面向对象设计
- 数据为中心设计:有一些项目是做数据仓库等的,数据爬取操作和读取。
- 基于构件的设计:调用构件和库
- 形式化方法设计
结构化设计方法
- 是一个经典的软件设计方法,采取自下向上和逐步求精的思想,按照功能对系统进行分解
面向对象设计方法
- 源自于数据抽象和职责驱动,利用封装、继承、多态等方法,提高软件的可扩展性和可复用性。
以数据为中心设计
- 开始于系统操纵的数据结构而不是它所表现的功能。
基于构件的设计
- 重点在于构件的提供、开发和继承,以提高系统的可复用性。
形式化方法设计
- 通过数学方法来对复杂系统进行建模。
静态模型 vs 动态模型
- 描述软件设计的模型,通常可以分为两类:
静态模型
- 静态模型是通过快照的方式对系统中时间不变的属性进行描述。通常描述的是状态,而不是行为。
- 比如:一个数字的列表是按大小排序好的。
动态模型
- 动态模型通常描述的是系统行为和状态转移。
- 比如:排序的过程中如何进行排序。
结构化设计模型和面向对象设计模型
- 在结构化设计中
- 静态模型:实体关系图
- 动态模型:数据流图和结构图(Structure Chart)
- 在面向对象设计中
- 静态模型:类图、对象图、构件图、部署图;
- 动态模型:交互图(顺序图和通信图)、状态图、活动图等
软件设计描述规范
- 早在 1998 年,IEEE 组织就提出了 IEEE 1016-1998 号标准[IEEE1016-1998] 以规范软件设计的文档化描述。
- 在 2009 年 3 月,IEEE-SA 标准委员会修订了 1016 号标准[IEEE1016-2009]。 IEEE 的标准并没有限定设计描述文档的具体结构,而是通过解释一系列概念说明了软件设计描述文档应该遵循的思路和基本内容。
软件设计描述模型
- 设计视图是应该包含设计图的
设计视角
利益相关者
- 设计视角必须符合在需求(Requiement)利益相关者(Stakeholder)设计关注点 (Design Concern)。
- 设计对不同的利益相关者来讲,有着不同的意义[Pfleeger 2009]:
- 顾客想要确认现有的软件设计方案能够保证其所期望的系统的功能和行为可以实现。
- 架构师想要知道软件单元、计算平台和系统环境足够实现需求中要求的非功能性需求。
- 设计人员想要了解系统的整体设计,以确保系统的每个设计决策和系统功能都能实现。
- 开发人员想要知道所开发的单元的精确描述,以及与其它单元的关系。
- 测试人员想要确认怎样测试设计的所有方案。
- 维护人员系统想要在修复问题和添加新特性时,能够保持体系结构的完整性和设计的一致性。
- 因为每个利益相关者目的不一样,对设计就会产生不同的设计关注点。而我们的设计得全面考量各个利益相关者的设计关注点,给出相应的设计视角下的设计视图。
设计理由
设计描述文档模版
设计文档书写要点
- 充分利用标准的文档模版,根据项目特点进行适当的裁剪。
- 可以利用体系结构风格的图,让读者更容易把握高层抽象。
- 利用完整的接口规格说明定义模块与模块之间的交互。
- 要从多视角出发,让读者感受一个立体的软件系统。
- 在设计文档中应体现对于变更的灵活性。