(这一节在第一版的 《构建之法》中没有, 是《构建之法》电子书(多看版), 和纸版书第二版中新增加的内容,纸版书第二版预计2015年6月出版)
11.1 分析和设计方法
我们写软件就是要解决用户的需求,我们需要表达和传递下面这些信息:
在“需求分析”阶段,我们要搞清楚
在问题领域中的现实世界里,都有哪些实体,如何抽象出我们真正关心的属性,实体之间的关系是什么,在这个基础上,用户的需求是什么,软件如何解决用户的需求。
在“设计与实现阶段”,我们要搞清楚
软件是怎么解决这些需求的?
在“测试”和“发布”阶段,我们要搞清楚
软件真的解决了这些需求了么?
软件团队的所有相关人员都需要处理、了解这些信息,如果在处理的过程中有误解和遗失,就会导致开发过程中的问题,以至最终产品不能满足用户的需求,就像 8.3 节提到的“秋千图”那样。那么这些信息怎么表达才能更准确、更能有效地交流呢?
我们先看看两个初中水平的题目:
- 今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?
- 程序员果冻觉得写程序赚钱不多,他想捞外快。于是他参加了王屋村的搬砖大队,大队规定搬砖到目的地,没有破损则给运费每块砖四分钱,如果有任何破损或丢失则倒扣一毛五分钱。果冻搬到一半的时候觉得还是坐着写程序好,最后他搬了一千块砖,共得三十五块两毛五分钱。问果冻搬的砖头没有破损的有多少块?
这两个问题看似不容易,并且毫不相干,但是本书的读者应该都能毫不费力地用类似的方法来解答。我想最常见的思路是:
这个问题实质上是二元一次方程求解:
鸡兔同笼:
x + y = 35; 4*x + 2*y = 94.
果冻搬砖:
x + y = 1000; 4*x – 15*y = 3525
我们还可以用二维坐标系图示的方法来得出直观的解法:
鸡兔同笼的图示解法
看典型解题者的解题过程,有下面的步骤:
1) 理解,抽象:理解问题,过滤掉非核心信息,抽象出关键信息和它们之间的关系。(雉就是野鸡,我没看见过活的野鸡,在这个问题中它等同于家鸡,鸡长啥样?一只鸡有几个头,几只脚,兔子长啥样?... 鸡头、兔头、鸡脚、兔脚要满足一些关系)
2) 找到合适的数学模型:啊,这就是二元一次方程求解。
3) 根据模型和解法,按部就班地解决问题:这要依赖于对数学原理(交换律,等价性…)和基本操作的掌握。
分析和设计有许多方法:
- 以文字为主的文档, 如Word、PowerPoint 文档。正如我们在需求分析和场景设计中看到的那样。
- 用图形为主构造的模型, 如 Mind Map (思维导图),ERD, DFD, UML 的各种图,甚至包括Flow Chart 流程图
- 用数学语言的描述,如 Vienna Development Method
- 用类自然语言+代码构造的描述,如 Literate Programming
- 源代码加注释也能描述
11.2 图形建模和分析方法
我们要给事物建造出一个“模型”,描述事物、事物的属性、事物之间的关系(静态的)以及各个事物之间的信息传递(动态的)。
下面简要介绍各种方法,详细的介绍和具体的应用可以参照专门的教科书和文献。
11.2.1 表达实体和实体之间的关系(Entity Relationship Diagram, Entity Relationship Model,Mind Map)
思维导图(Mind Map)
“一图胜千言”, 人们经常用图形来帮助他们了解概念,强化记忆。思维导图是其中的一个例子。思维导图没有严格的语法定义,一般来说是从图形的正中开始写下一个概念,然后按照绘图者所关心的属性扩展,几乎每个人都能马上开始画图。这个看似简单的工具其实很适合团队一起讨论和理解核心概念 – 例如,我们的主要用户有什么特点、什么需求。
图 xxxx 思维导图的例子
思维导图形式灵活,适用于很多鼓励探索、发散思维的场合(如头脑风暴会议),但是它的图形元素缺乏严格的语法和语义。
实体关系图 (Entity Relationship Diagram)
如果我们着重于表达现实世界中的实体和它们之间的关系,那么实体关系图ERD是最自然的表达方式。下面是实体关系图的一个例子,表达了大多数读者都比较熟悉的 “图书馆借书” 的场景:
在我们分析实体之间的关系时,这就是一个理解和抽象的过程,例如,我们可以通过自然语言的帮助把各种元素归类到它们在ERD里适当的类型中。 这样的做法也用在了10.3 节 “功能驱动的设计” 中。 请看下表:
表:xxxx 英语语言中不同的词性和ERD的元素的分类
英语语言中的不同词性 |
ER D中的类型 |
普通名词 (表示一类事物,如银行、客户、书籍等) |
实体类型 |
专有名词 (表示一个特定的人或事物) |
实体 |
及物动词 (客户取出存款) |
关系的类型 |
不及物动词 (利息可以升高或降低) |
属性的类型 |
形容词 (这种活期账户是无利息的) |
实体的属性 |
当我们要表示实体之间的静态关系时,ERD 是一个合适的工具。
用例图 (Use Case Diagram,UCD )
上一章提到的用例(Use Case)也有图形化的表示。用例图主要有下列的元素:
- 参与者(Actor): 表示参与系统运作的外部因素,例如用户,管理员,外部模块,设备,来自外部的信号等。 通常是一个简笔画的小人。
- 系统:通常用一个方框来表示系统的边界。有时也可以忽略。
- 用例(Use Case):表示系统和参与者交互的一次场景。它是一组动作的集成,而不是一个单独的内部元素。
- 信息传递线:用带箭头的线用来表示参与者和系统通过相互发送信号或消息进行交互的关联关系。
用例图的元素简单,绘图简明,它的主要目的是尽快让团队成员和利益相关者(特别是对技术不熟悉的)理解系统的需求。
11.2.2 表达数据的流动 (Data Flow Diagram)
当我们要关注数据在不同的实体之间依赖一定的规则流动的时候, DFD 是一个合适的工具。还是用大学图书馆管理系统为例,它有什么数据流过呢?
从下图来看,流过的数据还真不少。我们简要地列出几个例子:
1) 和管理机构相关的数据流
管理机构可以发出指令,“改变读者借书数量的上限”,这样的信息会导致图书馆的处理规则发生变化,并且会导致相关信息出现在“公开显示设备”– 例如网页,或者电子公告板上。
管理机构可以查询一定时间内图书借阅情况的明细或统计信息,这些信息或者返回到管理机构(例如,借书欠款最多的读者),或者出现在“公开显示设备”上 (例如,本月热门人文类书籍前十名)。
2) 和读者相关的数据流
读者可以查询、预定、借出书籍。
3) 和新书入库相关的数据流
新书入库的时候,书的各种属性会被录入到系统内的“图书数据库”,同时内部管理系统能触发流程,让预定某书的读者知道,他关心的书已经到货。
4) 和时间相关的数据流(图上没有表示)
时间也是信息,当某个时间点到达的时候, 系统内部的逻辑会触发一系列动作,导致信息的处理和流动,例如每天晚上11点开始统计第二天图书到期的读者,并给这些读者推送催还消息。
每一个数据的操作还可以进一步细化,形成一个新的、更低层次的DFD。这些数据流能引导设计者全面设计系统的信息处理流程。DFD 还能帮助系统得到安全设计,设计者可以分析能影响本系统的信息都从哪里来,外部数据和内部数据的边界在哪里?如果我们盲目相信信息源发出的数据,是否会造成严重后果?敏感数据都流到哪里去了? 如果数据的目的地没有合适的保护,是否会造成敏感数据的泄露[XZ1] ?等等。
11.2.3 表达控制流 (Flow Chart, Finite State Machine)
我们在计算机理论基础课上都学过有限状态自动机(Finite State Machine, FSM), 在程序设计语言基础课上都学过基本的流程图,这里不再赘述。
11.2.4 统一的表达方式(Unified Modeling Language, UML)
这些图形建模方法各有特点,它们使用了不同的几何图形、标注规则、专有词汇和颜色。人们自然会想,能否有一个统一的表达方式? UML 就是这样的回答。 UML在20世纪90年代伴随面向对象的方法发展,在工业界的应用和反馈中成熟起来,2004年发布的UML2.0是一个相对稳定的版本。
表xxxx 各种图示建模方法的大致特点
各种分析建模方法 |
从结构化数据的角度看 |
从面向对象的角度看 |
从控制的角度看 |
强调静态 |
ERD |
Class Diagram |
|
强调动态,交互 |
DFD,UCD,Activity Diagram |
Sequence Diagram |
FSM,Flow Chart, UML State Machine |
对于这些图形化的辅助工具的价值,不同的人有不同的看法。
在《Coders at Work》这本书里面, Java 架构师,畅销书 《Effective Java》的作者Joshua Bloch 对于“你用过UML 设计工具么?” 的回答是:
“没有。 能把设计画成图,让别人理解当然很好。 但是说实话我真的记不起来哪些模块应该是圆形,哪些是方形。”
谷歌研究院的院长 Peter Norvig被问及同样问题的时候说:
“我从来不喜欢UML类型的工具,如果你不能通过计算机语言表达 (UML 要表达的东西), 那就是这种语言的弱点。“
像任何新技术一样,以UML 为代表的图形化分析方法的确解决了不少实际问题,但是也引发了一些误解、误用、狂热和“银弹”的信仰。UML 的设计者和推动者之一Grady Booch 说到:
在UML 出现之前和之后,软件项目成功的关键依然是 – 智慧地使用技术、遵从一个好的软件开发过程、有经验的开发者和适当的技能组合[XZ2] 。
11.3 其他设计方法
在计算机软件发展的过程中,科学家和工程师们还尝试了很多其他方法, 它们在不同程度上解决了一些局部问题,从不同的方面推动了相关领域的发展。
形式化的方法 (Formal Method)
很多软件需求(例如计算机语言的编译器)可以抽象为对符号的运算和变换,很多软件的某些核心功能需要严密地验证,保证没有问题。一些科学家一直在努力,希望用无歧义的、形式化的语言描述我们要解决的问题,然后用严密的数学推理和变换一步一步把软件实现出来,或者证明我们的实现的确完整和正确地解决了问题。在这个领域一个比较成熟和经过实践考验的方法是 Vienna Development Method (VDM)。
文学化编程 (Literate Programming)
程序员在写程序的时候,要理解在文档中的需求,同时还要在程序里写相关的注释,这些不同目的的“写作”各有价值,但是一旦需求或程序发生变化,这些不同的文档很难保持同步。 更不用说程序员最常见的毛病 “我以后会加上注释的…”Donald Knuth 在20世纪70年代末开始尝试并提倡 Literate Programming 的思想并在自己的软件项目中身体力行。这一方法和常见的 “写程序,时不时加上一些注释” 相反, 它是 “写文档,时不时有些代码”。 它使用了宏(Macro)来进行抽象和信息隐藏。 通过工具的支持,它的源代码可以提取出让计算机编译执行的部分(叫 Tangle),以及文档(叫 Weave)。
有兴趣的同学还可以探索更多的设计方法。
把这些不同方法列举在这里,一方面是要说明,有多种设计软件的方式,它们在不同的历史阶段,在各自适合的范围内能有效地发挥作用。 如果我们能建立一个正确的抽象模型,这个模型可以适合很多不同的具体应用, 例如一个基于关系数据库增删改查的抽象模型可以适用于图书馆管理系统,学籍管理系统,简单的物流仓库管理系统等。 另一方面,软件团队可以写很多文字,画很多图,写很多公式,还可以口若悬河地互相倾诉,但是最后在电脑上运行的,是代码。
回到前面提到的“鸡兔同笼”问题,人们还想出了另一解法:
假设笼子里所有的兔子都坐在地上,举起它们的前腿,这样,三十五个动物都有两只腿和地面接触。那么,这个笼子里原来的“下有九十四足”就变成了“下有七十足”。兔子一共举起了二十四只前腿,每只兔子都有两只前腿,那么笼子里就有十二只兔子,那么三十五只动物的另外二十三只就是鸡。
我们怎么用数学公式或图形来表达这一方法呢?这一方法如何能推广到“果冻搬砖”的问题中呢?
[XZ1]加入注释: 参见: Coders at Work: Reflections on the Craft of Programming. 作者:Peter Seibel, ISBN 978-1430219484
[XZ2]注解:参见 文章 Death by UML Fever
作者 ALEX E. BELL, 和Grady Booch 在文章后的评论。 http://queue.acm.org/detail.cfm?id=984495
[XZ1]新注解:请参考网上关于安全设计,威胁模式分析(Threat Modeling)的文章,例如: https://msdn.microsoft.com/en-us/magazine/cc163519.aspx