UML及软件工程方法概览

429 阅读9分钟

前言

本文是鄙人学习 UML 及与其密切相关的软件过程之总结,有很多不深入,不透彻及个人揣测之处。请读者参考为主,不要全信其中的观点。

UML的来历

UML来自于80年代末的面向对象运动及90年代的软件过程方法学的研究。在面向对象分析(OOA)中,大家需要一种编程语言独立的表示方法,来让大家都能理解一个抽象模型。有另外一波人则是在研究软件工程如何分析需求,交付让客户满意的产品。开始时大家都是闭门造车,各有各的别别窍,最终这波人聚集到一起,请OMG组织来评评理。经过很多努力后形成了UML标准。

从起源来看,你就能理解UML为什么不仅有类图,还有用例图,活动图,部署图这些乱七八糟的图。另外,UML只是一个表示方法,一种图示语言。它和某种软件开发过程无关,最常见的误解就是把它和RUP一块讲。

下面的两节,如果你有不同意见,请记住这是《重构》作者,极限编程创始人,Spring框架依赖注入的总结者,微服务的布道师,一代宗师Martin Fowler的观点哦!不管你服不服,我反正是服了。🤔

UML类图与代码的映射关系

首先要明白一点,UML类图,尤其是那些依赖、关联、组合、聚合等关系,和代码没有必然的对应关系——不同的编程语言可以有不同的实现方式。UML类图的作者,早期很可能是某些组织中的一种职位:OO系统分析员。他们的工作是分析现实世界的用户需求,把它们抽象为对象世界的类。说他们不会编程都有可能哦!把UML和编程语言分开,会让你在学习时避免极大的困惑。

再次强调:在UML和代码之间,并不存在严格的对应,只有一种相似。

  1. 原则一:UML图是为了突出重点,方便在开会时讨论,随手在白板上书画,不要在其中放入太多细节。
  2. 属性(property)有两种表示形式:类框内的字段,或者关联。而它们之间取舍的原则是对应的数据类型是否值得强调。比如简单的类型用框内字段,复杂的类型用关联。所以看到关联的那条实线,你脑海中可以想象,这就是类的一个字段。
  3. 依赖这个虚线箭头也是为了强调。两个类之间有依赖的原因太多了,而上面的属性就是一种依赖。当你需要强调依赖关系的时候才画这条线,而不是把线拉的到处都是。
  4. 聚合其实是没有特殊意义的,它就是属性,是一个历史遗留的符号。不要使用聚合。而组合则暗示了两件事情:部分只属于一个主体实例;部分和主体的生命周期相同。

好了,知道类框图那个基本图形,以及上面这几点,就差不多了。马丁福勒说道,不要试图使用你知道的所有图示法;少量使用至今的图要比被人遗忘的复杂模型更好。

同样,少量能记得住的可用知识要比看一次忘一次的教条更好。

UML的哪些图比较重要,哪些图可有可无

下一节要提到的RUP有一个著名的(4+1)视图模型,参见文章:架构蓝图--软件架构 "4+1" 视图模型

4+1

每个视图只能反映事物的某个切面,自然视图越多越好。然而现实的中小项目不会有这么详细的架构和文档。下面说说我结合书本和经验对UML各个视图的理解:

  1. 静态视图:
    • 类图:最常见图形,有用,但不要用的花里胡哨
    • 包图:不太常见,对巨大代码量有用,主要用反映包之间的依赖
  2. 动态视图:
    • 活动图:常见,有用。但不必拘泥于UML格式,更宽泛的格式可以让业务也能看懂
    • 时序图:常见,有用。同样不要画的花里胡哨(Martin建议返回的箭头别画)
    • 协作图:时序图的马甲,不常见
    • 状态图:不太常见,但对特定领域(例如订单)有用
  3. 过程类视图:
    • 用例图:不建议使用,还不如直接用文字列清楚。尤其是<<include>>等版型,非要用的话请只用<<include>>
    • 组件图:用于架构驱动和组件驱动。在如今微服务场景下,可能类似服务拓扑图,有用
    • 部署图:较常见,有用

RUP

RUP的全称是Rational统一过程。Rational就是个公司(由前面那几个造轮子的人合伙创建),它卖Rose这款产品(大概是集成了Visio,Project这些功能,能画图,甚至还能生成代码),后来被IBM收购了,成为后者几大产品线之一。

RUP的核心主要是用例驱动和迭代式开发。 迭代式开发自然是最初的瀑布式开发(瀑布方法应该是来源于其它工程行业,比如建筑)的改革:它要解决瀑布方法把风险都积累到最后一个月的问题,因此强调要把开发过程分为多个构建周期,每次都要交付一部分功能。 用例驱动则确保了最终的交付物满足客户的预期。结合迭代式开发,可以保证最重要的功能优先被交付。

当然RUP的内涵远大于此,以下是它最经典的开发模型图。

RUP

RUP确实是它被创造的九十年代的最优实践。当时还没有什么互联网,主要的软件系统就是大型组织内部的大型系统,RUP是一个适合这种系统的方法。但这个过程本身是非常庞大笨重的,在适应中小型项目时会让人抓狂。

敏捷

如同JavaEE厚重的Enterprise Bean催生了轻量级的Spring,自然也有人对RUP看不下去了。于是,一帮人宣布不要搞的那么复杂,要以人为本,Show me the code,这帮人聚集起来搞了个宣言,就是敏捷宣言

个体与交互 重于 过程和工具
可用的软件 重于 完备的文档
客户协作 重于 合同谈判
响应变化 重于 遵循计划

在敏捷这面大旗下,涌现了一批脍炙人口的方法和概念。极限编程、TDD、SCRUM、Sprint等等。现在的程序员谈的都是user story,Jira一统了天下。

关于用例(Use Case)和功能(Feature)以及User Story的关系和区别,马丁福勒解释如下:

  • 用例描述Actors怎样使用系统。
  • 功能则用来划分系统,从而可以在多轮迭代中分别实现。
  • 往往是用例先行,用来描述用户需求。功能则是用例的细化。
  • User Story是极限编程对Feature的别称。

马丁福勒推荐Cockburn的《Writing Effective Use Cases》。这本书我没有去读,倒是作者的名字成功地引起了我的注意。🤭

我个人的理解,应该是类似RUP中业务用例和系统用例的关系。而《大象——Thinking In UML》3.3.5节也总结了这个问题,大意是用例是使用者视角,而功能是软件系统视角——我们应该首先强调使用者视角。

回归敏捷

敏捷方法先在小型项目上大行其道,逐渐地大型公司开始拥抱敏捷,尤其是微软这样的公司带头之后,软件公司再不搞敏捷就落伍了。

不过,2018年中旬一位当年参与宣言的大佬Ron Jeffries发文,你们大家是不是忘了敏捷的核心价值观了?应该远离“虚假敏捷”或“黑暗敏捷”,更接近敏捷宣言的价值观和原则。贴两段原话吧:

当公司开始采用敏捷时,通常意味着他们正在努力改进工作方式。借助各种不同风格的指导和培训,他们可以提高问题的可见度,有助于高层管理人员和整个公司做出更明智的决策。也就是说对企业是有利的。 但往往会给开发人员造成更多干扰,减少工作时间,压力增大,并被要求“走得更快”。这对开发人员来说是不利的,最终也会对企业造成不利影响,因为做得不好的“敏捷”会导致更多的缺陷和更慢的进展。通常,优秀的开发人员会离开这样的企业,导致企业效率比采用“敏捷”之前还要低下。

为公司或企业工作通常意味着有些事情是由上层决定的,然后在整个组织中实施和推广。在使用 SAFe、LeSS 和其他方法进行大规模 Scrum 实施时通常会发生这样的情况。然后,大多数人将被要求实施这些决策,他们可能没有经过适当的培训和指导,也不理解背后的真实意图。

上面两段话让人颇有同感,相信很有人有类似的经历。我的一位前领导就说过,估算Story Point的敏捷方法比较压榨程序员。

我顺便问了一下老婆SAFe是咋回事,她们公司今年开始引入SAFe。讨论下来,SAFe似乎是一个Scrum of Scrum,并且其核心是所有的工程师参与决策,且所有的工程师能顶替所有模块(全栈😂)。流程确实较多,推进有很多难以施行的点,但是以人为本这点倒是暗合敏捷宣言的精髓啊!

最后以Ron Jefferies的建议结尾吧:

  • 每一两周交付一次可运行、经过测试的集成软件。提升你的技能,直到可以每天开发出一个完全可操作的软件版本,一天内进行多次。
  • 保持软件设计的简洁。随着软件的演化,其设计将趋于复杂和笨拙。要始终有意识地抵制和扭转这种趋势,并以连续细小的步骤进行重构,尽可能保持进度的稳定和一致。
  • 使用当前的软件增量作为与产品负责人和管理人员进行对话的基础,与他们讨论接下来将会发生什么,以及他们想要你做些什么。

参考资料