Design principles of software development
• • ☕️ 1 min read
- 你可以从本文了解到
- 软件设计是什么
- 061 从需求到设计的转换并不容易
- 062 将设计追溯至需求
- 063 评估备选方案
- 064 没有文档的设计不是设计
- 065 封装
- 066 不要重复造轮子
- 067 保持简单
- 068 避免大量的特殊案例
- 069 缩小智力距离
- 070 保持设计在智力可控范围内
- 071 保持概念一致
- 072 概念错误比语法错误更严重
- 073 使用耦合和内聚
- 074 为变化而设计
- 075 为维护而设计
- 076 为防备错误而设计
- 077 在软件中植入通用性
- 078 在软件中植入灵活性
- 079 使用高效的算法
- 080 模块规格说明只提供用户需要的所有信息
- 081 设计是多维的
- 082 优秀的设计出自优秀的设计师
- 083 了解应用场景
- 084 你可以做到低成本的重用
- 085 “错进,错出”是不对的
- 086 可靠性可以通过冗余来实现
你可以从本文了解到
本文是对《软件设计的 201 个原则》的第4章——软件开发的设计原则的学习。
软件设计是什么
设计包括以下活动:
- (1) 定义满足需求的软件架构(architecture);
- (2) 为架构中的各个软件组件指定算法。
架构包括:
- 软件中所有模块的定义;
- 它们之间如何提供接口;
- 它们之间如何组装;
- 组件的生命周期。
设计的最终产出是设计规格说明(Design Specification)。
061 从需求到设计的转换并不容易
设计的目的是实现从需求规格到设计规格、从外部视角到内部最优的转换。
设计的第一步,是综合形成一个理想的软件架构。
不能直接将需求规格说明中的"架构"作为软件架构,原因:
在需求分析阶段,完全没有考虑选择最优设计。在这种情况下,不能接受将需求阶段的设计作为最终设计。
在需求分析阶段,列出各种可选设计,并进行分析及选优。
在确定需求基线、做出自己开发或购买的决策和进行开发成本估算之前,组织无法负担的起去做彻底的设计(通常占开发总成本的 30% 到 40%)。
该方法假设某种软件架构对所有软件都是最理想的。这显然是不可能的。
062 将设计追溯至需求
设计软件时,设计者必须知道,哪些需求能被每个组件满足。
在不同阶段都需要能对组件和需求进行追溯定位。
- 当[选择软件架构]时,重要的是所有需求都能被覆盖。
- 软件[部署]后,当检测到故障时,维护人员需要快速分离出那些最有可能包含故障原因的软件组件。
- 在[维护]期间,当一个软件组件被修复时,维护人员需要知道哪些需求可能会受到不利的影响。
可以通过创建一个大的二维表格来满足,它的行对应所有的软件组件,它的列对应需求规格说明中的每个需求。任何位置的 1 表示此设计组件有助于满足此需求。
063 评估备选方案
在所有工程学科中,一个重要思想是:详细列出多种方法,在这些方法之间权衡分析,并最终采用一种。
064 没有文档的设计不是设计
设计,是在纸或其他媒介上,对恰当的体系结构和算法的选择、抽象和记录。
065 封装
信息隐藏,有助于隔离错误。
066 不要重复造轮子
软件工程师经常一次又一次地重新发明组件。他们很少修补已有的软件组件。
有趣的是,软件业称这种罕见的实践为“重用”,而不是“工程”。
067 保持简单
托尼·霍尔(Tony Hoare) 说过:构建软件设计有两种方法。一种方法是使它简单到明显没有缺陷,另一种方法是使它复杂到没有明显的缺陷。
记住 KISS 原则(Keep it Simple and Stupid)
当你将软件分解成子组件时,记住一个人很难同时理解超过 7 (+- 2)个事物。
068 避免大量的特殊案例
每一个特殊案例都会使你更难调试,并使其他人更难修改、维护和增加功能。如果你发现太多的特殊案例,你可能设计了一个不合适的算法。应重新思考并重新设计算法。
069 缩小智力距离
艾兹格·迪科斯彻(Edsger Dijkstra)将智力距离(Intellectual Distance)定义为,现实问题和对此的计算机解决方案之间的距离。
理查德·费莱(Richard Fairley) 认为,智力距离越小,维护软件就越容易。
软件的结构应该尽可能接近的模仿现实世界的结构。
面向对象设计和杰克逊系统方法(Jackson System)等设计方法,将最小的智力距离作为主要的设计驱动。
070 保持设计在智力可控范围内
如果设计是以能使其创建者和维护者完全理解的方式创建和记录的,那么这个设计就是在智力可控范围内的。
这种设计的一个基本属性是,它是分层构建的和多视图的。
层次结构使读者能够抽象的理解整个系统,并在向更深层次移动时,理解越来越多的细节。
在每个层次上,组件应该仅从外部视角描述(原则 80)。
(在层次结构中的任何级别)任何单个组件都应该展现出简单和优雅。
071 保持概念一致
概念一致是高质量设计的一个特点。
它意味着,使用有限数量的设计“形式”,且使用方式要统一。
设计形式包括:模块如何向调用方通知错误,软件如何向用户通知错误,数据结构如何组织,模块通信机制,文档标准,等等。
当设计完成后,它应该看起来都是一个人做的,尽管它其实是很多参与者的产出。
在设计过程中,经常会有偏离既定形式的诱惑。
对这样的诱惑,有些是可以让步的,比如理由是提升系统的一致性、优雅性、简单性或性能。
有些则不能让步,比如仅仅为了确保某个设计者在设计中留下自己的印记。
概念一致性比自我满足更重要。
072 概念错误比语法错误更严重
在开发的各个阶段,问自己一些关键的问题。来发现概念错误:
在需求阶段,问自己,“这是客户想要的吗?”。
在设计阶段,问自己,“这个架构在压力下可以正常工作吗?”,或者,“这个算法真的适用于各种场景吗?”。
在编码阶段,问自己,“这段代码的执行和我想的一样吗?”,或者,“这段代码是否正确实现了这个算法?”。
在测试阶段,问自己,“执行这段测试能让我确信什么吗?”。
073 使用耦合和内聚
我们要追求的是低耦合和高内聚。
074 为变化而设计
为了适应变化,设计需要做到:
模块化,即应该由独立的部分组成,每一部分可以很容易地升级或替换,而对其他部分造成最小的影响(查看相关原则 65,70,73,80)。
可移植性,即应该很容易修改以适应新的硬件和操作系统。
可塑性,即可以灵活地适应预期外的新需求。
最小智力距离(原则 69)。
在智力可控范围内(原则 70)。
这样它就表现出 概念一致性(原则 71)。
075 为维护而设计
对于软件产品,设计后的最大成本风险是维护。
从对可维护性的影响来说,架构选择比算法或代码更加重要。
076 为防备错误而设计
你的设计决策应该尽可能做到以下优化:
1.不引入错误。
2.引入的错误容易被检测。
3.部署后软件中遗留的错误要么是不危险的,要么在执行时有补偿措施,这样错误不会造成灾难。
077 在软件中植入通用性
当把一个系统拆分成子组件时,要注意其潜在的通用性。
当多个地方都需要一个相似的功能时,只需构建一个通用功能组件,而非多个相似功能组件。
在开发只在一个地方需要的功能时,要尽可能植入通用性,以便日后扩展。
078 在软件中植入灵活性
一个软件组件的灵活性体现在,它很容易被修改,以在不同的场景下执行其功能(或者相似功能)。
079 使用高效的算法
了解算法复杂度理论是成为一名优秀设计者的绝对前提。
“算法分析”理论让我们知道,如何区分本来速度就慢的算法(不管编码如何优秀)和速度快几个数量级的算法。
080 模块规格说明只提供用户需要的所有信息
设计过程中一个关键部分,是系统中每个软件组件的精确定义。
它必须包含用户(这里的“用户”是指,另一个软件组件,或另一个组件的开发者)需要的全部内容,如:用途,名字,调用方法,如何同所在环境通信的细节。
任何用户不需要的内容,都要明确排除在外。
在大部分情况下,应该排除使用的算法和内部数据结构。
081 设计是多维的
一份完整的软件设计至少需要包括:
1.打包方案(Packaging)。通常用层次图的形式给出,用于说明“什么是什么的一部分?”。它通常隐含说明了数据可见性。它还能体现封装性,如对象内包含的数据和方法。
2.依赖层次(Needs Hierarchy)。用于说明“谁需要谁”。以组件网状图的形式表达,其中箭头的指向表明组件间的依赖关系。依赖可能是数据、逻辑或者其它信息。
3.调用关系(Invocation)。用于说明“谁调用谁”。以组件网状图的形式表达,其中箭头的指向表明组件间的调用、中断、消息传递关系。
4.进程组织(Processes)。一批组件被组织在一起,成为异步处理的进程。这是与其它进程同时运行的组件副本。零个、一个或多个副本可能同时存在。另外,还需要说明进程创建、执行、停止或销毁的条件。
082 优秀的设计出自优秀的设计师
无论如何,真正优秀的设计,是真正优秀设计者的智慧结晶。
优秀设计的特征是:简洁(Clean)、简单(Simple)、优雅(Elegant)、快速(Fast)、可维护(Maintainable)、易于实现(Easy to Implement)。
优秀的设计源于灵感和洞察力,而不仅是努力工作或按部就班的设计方法。
对于最好的设计者要重点支持。他们才是未来。
083 了解应用场景
无论需求文档写得多好,架构和算法的最优选择,主要应基于对应用场景特质的理解。
084 你可以做到低成本的重用
问身边的人你是否开发过做这个功能的组件?如果有,拿来用。
085 “错进,错出”是不对的
要考虑异常的输入,并且保持系统正常运行。不要造成“多米诺效应”。
086 可靠性可以通过冗余来实现
在硬件设计中可以通过:1. 并行方式。2. 冷备方式。
这样的设计成本会少量增长。可靠性是指数级提升。
在软件系统中,相同的软件做两份拷贝,并不会增加软件的可靠性。其中一个失败,另一个也会失败。
可行的方案是:根据相同的需求规格说明,设计出两套软件系统,然后并行部署。
这样在软件系统中,设计成本会翻倍。
软件的超高可靠性是非常昂贵的。(原则 4)