一、导言

为什么要写这个系列

“OO都是一个已经被讨论烂的话题了,还有什么可写的!”

不知当你看到文章标题时,是不是有这种疑问,或者鄙夷。不错,OO从诞生到现在经历了不短的岁月,与其相关的理论、技术、原则、实践、模式、语言已经出了一大堆。可是,你真的了解OO的本质吗?真的能挥洒自如的将OO应用于软件开发中吗?真的能发挥OO的能量,从而提高软件质量吗?如果对这三个问题,你不能很干脆的点头说:“是的,当然!”那么也许你可以抽一点时间,往下看一看。

这个系列文章不打算大篇幅重述各种OO理论,也不打算谈各种OO心法。这系列文章着重于通过实践澄清一些对OO的误会,帮助朋友们更好的使用正确的方法将OO应用于实际开发中。同时,在必要的地方简要叙述一下OO相关知识。

所以,这个系列不是关于OO理论的天书或OO参考大全,而是告诉你“你对OO可能存在哪些误会与认识上的偏差”以及“如何走出误会更好的OO应用于实践”。

OO是技术,不是理论

OO,我认为全称应该叫做“面向对象技术”。其实,OO自诞生那天起其全部目的就是应用于软件开发实践中,提高软件开发质量。这也是OO存在的全部意义。所以,搞OO和搞数论、搞理论物理不一样,不能脱离应用。搞OO的人应该算是工程师,而不是科学家。两者最大的区别是:科学家可以不考虑自己研究的成果有没有什么应用价值。而工程师不一样,他们要更“势利”,要时刻关心自己研究出的东西有什么应用价值。所以一切OO的研究要以可应用性为向导,不能天马行空夸夸其谈。

当然,OO需要理论支撑,但是一定要是有现实意义的理论,而不能像数学家那样为了理论而研究理论,更不能将已有理论当做教条机械性使用。

因此,在学习和实践OO的过程中,要时刻注意和应用性联系起来,才能避免走入理论OO和教条OO的歧途。

到底什么是OO

“什么是OO?”对于这个问题,很难一言以蔽之。但正是由于对这个概念的误解和偏差,才使得某些朋友一直不能正确使用OO,不能让OO真正服务于软件开发,到最后开始怀疑OO、鄙视OO甚至唾弃OO。

在所有对OO的偏差性认识中,最普遍的一点就是“金锤”式理论,即“XX就是OO。”例如,“把所有东西看成对象就是OO”,“遵循封装、继承、多态就是OO”,“应用良好的OO原则进行设计就是OO”,“使用UML就是OO”。显然,这种“一锤子敲定”的方式会让人割裂的看问题,从而无法从全局角度正确把握OO。

在这里,我斗胆给OO下一个定义:OO,即面向对象技术,是一种旨在提高软件质量的综合性技术,其贯穿于软件系统的调研、分析、设计、开发、测试、维护、扩展、升级等整个生命周期,它包含一系列概念、思想、理论、目标、原则、实践、模式、工具、语言等要素,这些要素既相互区别又相互联系,同时从宏观和微观两个角度共同协作,指导和引导开发人员开发出高质量软件,并指导与开发有关的一切过程。

从上面可以看出,OO并不是孤立的概念或技术,而是一系列要素的复合体,并贯穿于整个软件开发周期。所以,仅仅从某个时间或控件切面切入而应用OO,这样的OO是不完整的,也不可能发挥出其应有的作用。打个比方:如果使用OO的方法和工具进行分析、设计,但是编码过程不能做到OO,就好比制造了一辆豪华的轿车却找头驴拉着走,是不能提高你出行效率的。反过来,如果你是一个C#或Java高手,但分析设计过程不遵循OO,直到编码时才用C#或Java试图OO,这无异于你听说开车能提高出行速度,于是你苦学驾驶技术,并掌握了高超的驾驶本领,但最终却坐在一头驴子上,于是你开始大喊:驾驶技术是骗人的!根本没法用!是啊,驴子上连方向盘、离合器都没有,空有一身驾驶本领又如何发挥出来呢。

这个系列的文章概要和内容组织

这系列文章的大体写作方式,是通过一个实际案例《XX食品公司连锁店在线定料系统》的调研、分析、设计、开发等一系列过程,帮助大家更好的认清OO如何实践,同时澄清一些误会。这个系统是我曾经参与过的实际案例,为了文章需要,将进行一定程度的修改,但一些很关键的东西都会原汁原味保留下来。在整个过程中,请各位不拘泥于具体技术相关问题,而要一直保持一个较高的视端,一睹OO的全貌。

文章的大概组织方式:

第一部分:需求分析之前的故事

很多人认为就软件开发来说,第一步是需求分析,其实非也。如果想更好实践OO,需求分析之前还有很多工作,如特性调研、降低风险等环节,这一部分我们讲讲需求分析之前的故事。

第二部分:分析步步高

这一部分开始对系统进行真正的分析,让我们来看看OO是如何引导和指导我们分析的。

第三部分:设计的方方面面

设计是一个繁杂的过程,诸多OO原则与模式都会应用于其中,这一部分不会细讲各种原则及模式,而是看看正确应用原则与模式的方式是怎么样的。

第四部分:让所有努力开花结果

这一部分,我们将前面的成果付诸实践。通过这一部分,可以清楚的看到前面做的一切工作都不是飘在云里的空中楼阁,而是开发高质量软件不可缺少的部分。

以上是目前的规划,当然,在整个过程中可能会出现变化,但是大体条理不会打乱。希望本系列文章能给您带来帮助。

二、第一项任务:特性列表

第一份说明

当这个项目开始时,我们得到的关于我们要做的系统的唯一说明是一页Word文档,这是一份简单的不能再简单的说明。文档里只有一行字:我们需要一个系统,使得全国各地的代理加盟商和连锁店能够通过网络订购原料。另外,我们还知道这是一个食品公司,主营面包、麻花、肉夹馍等食品,在全国各地有多家连锁机构。除此之外,我们一无所知。

永远不要和客户谈需求

软件开发的第一步是什么?很多人觉得是需求分析。显然这短短的一句说明无法满足我们的要求,于是很自然的,你希望找客户谈话,详细了解情况,然后做出详细的需求分析。于是,你心里有了这么一个算盘:

和客户谈话 -> 问清所有需求 -> 进行需求分析 -> 生成需求文档

乍看之下,这是很合理的步骤,但是实际上这是不可行的。原因有如下几点。

1.客户不关心系统的所有方面

每个开发人员都希望,客户可以清楚的把自己需要的东西的方方面面清楚无误告诉你,然而,这只是一厢情愿罢了。因为,任何一个客户在需要什么东西的时候,只会大致想想要个什么东西,并不会将所有地方都仔细想清楚。

2.有时客户并不清楚自己到底要什么东西

有时候,客户并不是很清楚自己想要什么。这不是天方夜谭。很多时候,客户仅仅有一个“想要实现某个目的的动机”,而没有“我需要一个什么系统”这么明确的概念。例如,从上文那个“一句话说明”中,可以看出,我们的客户仅仅是有一个动机:希望有一个系统,能使得他们公司的代理商和加盟店在线定料,至于这是一个什么样的系统,他们并没有明确的概念,更不用说这个系统有什么样具体的需求了。

基于以上两点,你是不可能从客户那里问清所有需求的。实际上,就不该和客户谈需求,因为需求从来就不是“客户面”的东西,而是“开发人员面”的东西。需求需要包含方方面面系统需要实现的功能,而客户往往并没有如此精细想过,甚至客户自己对自己想要的东西什么样子都不清晰。面对这种客户,你一本正经往他面前一坐,开发笔记本说:“我们谈谈需求吧”,或说“我们进行需求分析吧”,我想客户会立马崩溃,而你也不可能得到你想要的所有东西。

作为开发人员,不应该一开始就和客户谈需求,而要先谈特性。很多需求并不需要客户告诉你,开发人员应该能通过常识识别出来,就如没有哪一个买汽车的客户会说:我需要一个辆汽车,要有方向盘,还要有四个轮子。他们通常会说:“我要一辆家用车、要省油、舒适,要至少能坐3个人。”这“家用车”、“至少能坐三个人”就是特性。

特性是一些描述,一条特性简要描述了系统的一个客户最关心的核心功能。

最为开发人员,首要任务不是做需求分析,而是帮助用户整理出一份特性列表。这里之所以说“帮助”,是因为很多时候,客户由于自身太关注于某个方面,而漏掉了十分重要的特性,这是你要帮客户想想,并将想到的特性询问客户是否是需要的。如果客户很高兴的说“对!对!”,那么这就是大功一件。所以,在初期阶段,开发人员一定要想客户之所想,急客户之所急,尽快帮客户理清系统有什么特性。

所以,正确的过程应该是:

询问客户系统都有什么功能 -> 写出初期特性列表 -> 想想什么遗漏特性,并询问客户 -> 查漏补缺

生成特性列表

下面回到案例。

虽然客户的说明只有一句话,我们还是整理出一份初期的特性列表,并将其作为我们向客户展示的第一份工作成果。这份特性列表内容如下:

1.可以将各种原料信息发布到系统上

2.加盟商和连锁店可以通过系统在线定料

没错,我们的初期文档只有两项特性。我们把这个给客户看,客户说:“嗯……大约是这个东西吧。”很显然,我们的客户是比较懒的那种,这时,我们有义务引导客户想起更多需要的特性。下面是当时大约对话:

开发人员(简称D),客户(简称C)
      D:你这个加盟商和连锁店是要如何区分呢?
      C:我们公司有一个加盟商和连锁店的记录。
      D:那么你们是准备将各个加盟商的信息全部录入系统吗?
      C:不是,我希望他们能自己注册,就和论坛那种。
      D:那么你要如何确认他们的身份,总不能任何人都可以在这个系统注册吧。
      C:嗯,我们公司有各个加盟商的详细信息,我们希望他们注册时提供足够的信息,我们进行核对。
      (于是我们飞快写下一个特性:加盟商和连锁店通过网络进行注册,总店负责人审核后才可以正式使用系统。)
      D:你们得在后台能发布各种原料的信息吧。
      C:嗯,使得。
      D:这里有没有什么特别的要求。
      C:没有吧……
      D:好的,那么你们准备设立几个人负责管理这个系统。
      C:就一个人吧,就信息部那个。我们就这一个比较懂计算机的。
      D:也就是说不需要有多个人分别管理这个系统?
      C:不需要。
      (写下一个特性:系统需要一个管理员,可以对系统进行管理)
      D:在你们的加盟商进行定料时,你希望他们怎么操作啊。
      C:这个,我没仔细想过……
      (看客户对这个地方比较不清晰,我们打开了一个网上书店的网站,给他演示了一下购物车购物的过程)
      D:你看,你的这个定料过程是不是和这个购物过程很相似,加盟商定料是不是就相当于从总公司购买原料啊。
      C:对对!就要这种效果的!
      (这里要记住,在特性不能直接说清楚时,找相似事物是一个不错的选择。也就是说,说明这个特性像什么,不像什么)
      (我们又加一条特性:使用购物车功能进行网上定料)
      D:付钱这一块怎么弄,需要网上支付吗?
      C:不了,我们一般又财务专门做钱这一块工作。
      D:那买完原料后要不要什么凭证呢?
      C:买完后生成一份定料单吧,打印出来,交给财务,财务清算款项,款到账后通知原料那边发货。
      (又一条特性:定料完成后生成定料单,并可以打印)
      D:那么关于这些交易,你一定希望能查询吧,你希望怎么查询。
      C:哦,这些我们财务那边有专门的财务软件。你这个系统只要能让加盟商定料就行了。

到这里谈话基本结束,我们得到如下一张补充过的特性列表:

1.可以将各种原料信息发布到系统上
      2.加盟商和连锁店可以通过系统在线定料
      3.加盟商和连锁店通过网络进行注册,总店负责人审核后才可以正式使用系统
      4.系统需要一个管理员,可以对系统进行管理
      5.使用购物车功能进行网上定料
      6.定料完成后生成定料单,并可以打印

我们将补充后的特性列表给客户看了看,基本得到了认可。

到了这里,我们第一步就差不多做完了。但是,我们还是不能马上进入需求分析,因为这之前还有很多事情要做。例如,特性整理,风险规避,这都是后面要讨论的话题。

重点总结

1.客户不会想到方方面面。
      2.有时客户并不明确自己想要什么东西,而仅仅是有个动机。
      3.不要和客户谈需求,要谈特性。
      4.开发人员有义务引导和帮助客户挖掘系统的特性。
      5.当客户描述不清某个特性时,可以采用找类似事物的方法,说说这个特性像什么,不像什么。
      6.在软件开发初期,我们需要首先整理出一张特性列表,而不是做需求分析。

三、降低风险

风险无处不在

在上一篇文章中,我们写出了一张特性列表。然后是不是就可以做需求分析了?很遗憾,还不可以,我们仍有许多工作要做。拿到特性列表后第一件事,就是要尽量降低风险。这里先不长篇大论风险如何如何,我们先做,从做的过程中体会降低风险的涵义。

DRY

这里,首先要引入一个OO原则——DRY。

DRY原则,全称Don't Repeat Yourself,指:在系统中,每一个信息或行为片段应该仅仅存在一份,且存在于合适的地方。

对于这个原则,很多朋友将其理解为“不要出现重复的代码”,这是很片面的理解。其实,OO本身并不是仅仅关于如何写出优秀的代码,而是关于如何开发出优秀的软件。所以,很多OO原则并不是仅仅代码层面的原则,而是可以应用于整个开发过程的。下面来着重看看这个原则的深层意义,以及讨论其在降低风险中的应用。

如何利用DRY降低风险

在所有的开发风险中,有一种风险,是由于同一个信息或行为片段分散于系统的不同地方,从而导致重复劳动,并且给修改和维护造成隐患。

下面,分别通过例子来描述两种不良后果。

1.重复劳动

例如,在特性列表中,如果两个特性本身应该是一个特性,但被误认为是两个不同特性。那么所有基于特性的分析和设计都存在重复劳动,即对同一个特性,做了两遍分析和设计,直到发现重复为止。这个代价是巨大的。

2.修改和维护隐患

例如某一代码段完全相似,但是出现在程序的诸多地方。这些相似的代码段一定是完成完全相同的功能,于是有着“同生同灭”的特点,即如果某段代码要修改,一般来说所有这段代码的重复代码都要修改。这就给维护带来很大隐患。

DRY原则正是针对这种风险的一个解决方案。关于DRY在代码中的应用,暂且放下,这里只看它在特性列表整理中的应用。显然,根据DRY原则,要求特性列表中的特性不能具有重复,那么我们再仔细看看我们的特性列表:

1.可以将各种原料信息发布到系统上
      2.加盟商和连锁店可以通过系统在线定料
      3.加盟商和连锁店通过网络进行注册,总店负责人审核后才可以正式使用系统
      4.系统需要一个管理员,可以对系统进行管理
      5.使用购物车功能进行网上定料
      6.定料完成后生成定料单,并可以打印

仔细检查,不难发现2和5其实是在描述一个特性,于是我们将2和5合并成一个新的特性:加盟商和连锁店可以使用购物车功能在线定料。另外,可以看出,“总店负责人”和“管理员”其实是一个概念。整理完后,得到如下新特性列表:

1.可以将各种原料信息发布到系统上
      2.加盟商和连锁店可以使用购物车功能在线定料
      3.加盟商和连锁店通过网络进行注册,管理员审核后才可以正式使用系统
      4.系统需要一个管理员,可以对系统进行管理
      5.定料完成后生成定料单,并可以打印

再仔细检查,已经没有重复特性。这样,我们就应用DRY原则规避了第一种风险:重复劳动的风险。下面给这种风险下一个定义:

重复劳动风险——指由于重复特性的存在,导致对同一特性进行了一遍以上的分析设计过程。最终导致资源浪费,开发效率降低,可能导致无法按时交付。

知之为知之 不知为不知

在使用DRY整理完特性列表后,下一步就是要规避第二种风险:概念模糊。

概念模糊风险——指由于开发人员对于某一领域知识不熟悉,而将某一个概念想当然,继而根据这个想当然的结果进行分析设计。如果其想法错误,则会导致系统无法正常使用或无法完成业务既定目的,从而导致返工或项目失败。

很明显,一般来说开发人员并不是业务专家。所以对于领域业务不熟悉是很正常的,作为一个合格开发人员,应该谨遵孔老夫子“知之为知之,不知为不知”这一教诲,谨慎负责对待不熟悉的概念,而不能从字面臆想。正确的做法是,首先审视特性列表,对于不熟悉或拿不准的概念,去请教领域专家或客户。下面继续以我们的项目为例。

经过对特性列表的分析,我们找出以下疑问或模糊概念:

1.原料(都是什么原料,需要分类么?)

2.加盟商和连锁店(两者有什么区别?定料业务流程一样么?)

带着着两个疑问,我们再次拜访了客户。具体交谈就不说了,最后我们得到了如下结果。

1.原料主要就是些炸粉、调料之类的配方需要保密的烹饪原料,没有太特别的,不需分类。

2.加盟商和连锁店不是一个概念,加盟商直属总公司,连锁店可能直属总公司也可能直属某加盟商。加盟商和直属总公司的连锁店直接向总公司定料,不直属的的连锁店向相应加盟商领取原料。连锁店按原价定料,加盟商按照等级分为5级,每级折扣不同。

经过以上一番了解,我们基本把模糊概念搞清楚了,并且经过这么一了解,我们的特性列表随之改变:

1.可以将各种原料信息发布到系统上
      2.加盟商和连锁店可以使用购物车功能在线定料
      3.加盟商和连锁店通过网络进行注册,管理员审核后才可以正式使用系统
      4.系统需要一个管理员,可以对系统进行管理
      5.定料完成后生成定料单,并可以打印
      6.直属连锁店按原价定料,加盟商按照等级分为5级,每级折扣不同

我们认为加盟商级别和折扣是很重要的特性,于是把它加到了特性列表里。

保证我们能够实现

上面我们已经避免了两种风险。现在再来看一种风险。举个例子,如果一个建筑设计师,设计了一座新型别墅,其中有一条特性是:院子里有一个季节调节装置,可以随意将院子里的季节调节为春夏秋冬中任意一个季节。你们认为这个特性怎么样?很酷的创意对不对?但是如果你是别墅的客户,你会这么想?我想你不会和设计师一起为这个非常酷的特性而激动,你会首先问设计师要怎么实现这个功能。没错,可以调节季节的院子是很棒,可以不要忘了,最终我们的别墅是要建筑在实际中的,而实际总会有诸多约束不能让每一个想法变成现实。

同上。无论我们设计的东西多么天花乱坠,最终都是要编码实现的。所以,我们要保证所有的特性可以在编码层级上有解决方案。即使我们不能马上知道精确的解决方案,但也要大体知道怎么实现。由此引出了第三种风险:

实现能力风险——指由于某项特性不能由具体技术解决带来的风险。这种风险包括两个层面,第一是现有技术完全解决不了。第二是由于开发团队没有解决方案。这种风险可能导致项目延期甚至失败。

要规避这种风险,就要逐一审查我们的特性是否可以在现有技术范围内解决。仔细审查后,第一个层面应该是没有问题,所有这6种特性都可以在现有技术内解决。但是对于特性2,我们存在一定问题。因为当时我们都没有设计购物车的经验,一下子不知购物车是如何实现的。

为了解决这个问题,要做如下工作。这里要注意,我们在这里可能讨论了很多细节,但是我们要避免提前进入细节。所以,这里讨论的所有细节并不是要进行分析或设计,而仅仅是通过头脑风暴的方式大约有个谱。这个“谱”往往和最终的实现有很大不同,但是至少我们知道这个特性通过某种方式可以实现。

在遇到这种风险时,首先要做的是查找相关资料。在查找了一些资料后,我们了解到购物车一般分为Session实现方式和数据库实现方式。这两种方式特点不同,前者支持未登录购物,确不能保存客户购物信息。而后一种又必须登录后才能购物。这里涉及到客户的利益,所以要由客户决定。

于是,我们询问了客户,得到的答复是“未登录时要可以定料,而且也要保持定料信息。”这句话有点模糊,但大体可以知道客户的意思,于是我们改写成如下一段话“未登录的用户可以使用购物车,但不能下定料单,登录后才可下单。登录用户可以保持购物车中信息。”我们拿给客户看,并进行了一定描述,客户认同了这个决策。

这个确定后,我们就要讨论一下具体解决方案了。我在这里再次说明,以下的讨论不是分析或设计,而仅仅是让我们对棘手的问题更清晰一些,从而心里有个谱,避免实现的时候不知如何是好。

在经过一番讨论后,我们都同意的解决方案是“未登录的用户使用Session处理购物车,登录的使用数据库存储购物信息。当用户登录时,如果Session中有将购物信息,将Session中的信息转入数据库。”

这个讨论可以到此为止了。因为我们不需要太精确,我们只要大致心里有个谱就行了。

重点总结

1.特性列表完成后,我们首先应该降低风险,而不是做分析或设计。
      2.重复特性存在风险。可以用DRY原则处理。
      3.存在模糊不清概念的特性存在风险。请仔细询问客户,不要想当然。
      4.不知如何实现的特性存在风险。一定要保证对每条特性的实现方式心里大致有个谱,但不需很精确。

四、通览全局:避免过早陷入细节的泥沼

细节的泥沼

现在我们再次将特性列表贴过来:

1.可以将各种原料信息发布到系统上
      2.加盟商和连锁店可以使用购物车功能在线定料
      3.加盟商和连锁店通过网络进行注册,管理员审核后才可以正式使用系统
      4.系统需要一个管理员,可以对系统进行管理
      5.定料完成后生成定料单,并可以打印
      6.直属连锁店按原价定料,加盟商按照等级分为5级,每级折扣不同

我们已经和这则列表折腾很久了,相信很多朋友已经厌倦了,肯定在想:不要在折腾这该死的特性列表了,赶快开始吧。这点我同意。但是要开始做什么?很多朋友可能会说:开始用例分析吧。说实话,用例是好东西,它让我们清晰认识到系统的工作流程,正式因为过于清晰,所以很容易让我们陷入一个细节的泥沼。

应该说,从“特性列表”直接到“用例分析”不是一个好注意,因为特性列表关注于功能(Function),而用例关注于系统的业务流(Business Flow),我们从功能直接开始分析系统的细节业务流,这个跨越太大,不利于软件质量的保证。特性是相对分散独立的功能描述,而用例是系统细节,很明显,在这之间应该有一个过渡,而这个过渡,就是一个高层次的,从全局角度对系统的一个概观认识。这个概观认识起到承上启下的作用,既将特性列表映射为一个系统的大概模型,又给系统细节的分析奠定了基础。所以,在系统特性基本确定后,我们首先要从全局给出一个系统的概览,避免落入用例分析这样细节的泥沼。

概览系统的有力工具——用例图

既然我们要给出一个全局的系统概览,自然就需要有一种方式将其表达出来。显然,仅仅通过说是不理想的,我们需要一种能用于书面表达的工具,而这个工具,就是我们非常熟悉的UML图之一的用例图。

很多朋友可能有疑问,上面不是说不进行用例分析吗?怎么这里又画用例图了?因为用例和用例图表述的信息层面完全不同,用例更倾向于细节和业务流的描述,而用例图倾向于整体的描述。下面给出两种工具要表述的信息:

用例——表述某个交互操作的动作执行者、用例名称、具体流程过程、例外情况等。

用例图——表述系统有哪些执行者、有哪些用例以及执行者于用例、用例于用例之间的关系。

很明显,用例更关注于某一个操作的具体流程,所以适合在需求分析中使用;而用例图更关注于整个系统的使用和交互情况,因此能给我们一个系统的概览。

下面,我们就使用用例图,从高层次角度看看系统的样子。在这里还要说明的是,随着UML和用例分析技术的发展,提出了业务用例和系统用例的区别,而且用例存在不同的粒度。在进行高层次概览的时候,我们是使用的是一种粒度比较粗的用例图。也许在以后的需求分析中,我们还会使用粒度更细的用例图。

通过简要对特性列表的分析,可以画出一张如下的用例图:

可以看到,这个用例图并没有太多细节的东西,而仅仅描述了三个信息:1.系统有哪些用户 2.系统有哪些操作 3.系统和用户有哪些交互。这些,就是我们要的一个系统概观。

用例图的验证

画完用例图,我们并不是就完事了。因为,要使得用例图真的是全局概观,就一定要保证一点:图中用例覆盖了所有特性。如果不能覆盖所有特性,说明我们的用例图中缺少用例甚至缺少动作执行者。现在我们来验证一下:
      特性1覆盖于“原料管理”。
      特性2覆盖于“在线定料”。
      特性3覆盖于“注册”和“加盟商和连锁店管理”。
      特性4覆盖于“原料管理”和“加盟商和连锁店管理”。
      特性5覆盖于“打印定料单”。
      特性6覆盖于...???

我们忽然发现一点,这里并没有和折扣有关的东西,那么这个特性没有被用例覆盖。我们知道,如果要实现这个特性,系统中一定要有一定的折扣机制,而一定是有管理员设置的。于是我们增加一个“折扣管理”用例。注意,这里我们可以先不陷入细节,仅仅知道系统有这么一个折扣机制,至于具体怎么实现,到分析设计阶段再讨论。于是,修改后的用例图如下:

现在我们可以说:特性6覆盖于“折扣管理”和“在线定料”。

好了,这样我们已经确认,用例图的用例覆盖所有特性。

重点总结

1.不要过早陷入细节
      2.在特性列表到用例分析之间,用该先有一个系统概览,让我们从高层次上审视系统全貌。
      3.用例图是实现这种概览的有效方式。
      4.用例不要粒度过细,要能反应系统粗粒度操作。
      5.最后不要忘了检查图中用例是否覆盖了所有特性。

五、需求分析之前的故事

高质量软件的第一要素

到目前为止,我们做了很多工作,但是我一直在强调这些都还不是需求分析。在很多人心目中,软件开发的第一件事就是先做需求分析。那么我们为什么不这样做呢?这牵扯到一个关键的问题:我们都希望开发高质量的软件,而本系列文章的重点也是如何通过OO实践开发高质量软件,那么什么是高质量软件?

对于这个问题,也许很多人会说,是灵活的、是易于修改和扩展的、是可维护性高的、是用户体验好的、是文档完整的、是代码规范的、是性能处理优秀的……好吧,我承认,这些都是高质量软件必不可少的元素,但是,还有一个更重要的要素,就是:软件必须做客户希望它做的事。你的软件再灵活、编码再规范,客户不关心,客户最关心的是软件是不是完成了他期待的功能,可以做他希望软件做的事。所以,高质量软件的第一要素就是:让软件做客户希望它做的事。

知道了这点,就知道为什么第一步不是做需求分析了,因为需求分析的重点不是“让软件做客户希望它做的事”,而是“将需求分解归纳成开发人员容易进行领域分析和设计的信息片段”。所以,需求分析是开发人员面的东西,而不是客户面的东西。作为开发人员,我们要首先站在客户的角度看问题,而不能总是站在开发人员角度,和客户隔着一条河对话。我们要走过去,去河的另一岸。

回顾我们的工作

现在来总结一下我们目前所做的工作,你会发现,我们所做的全部工作,其目的就是让软件做客户希望它做的事。

我们首先总结出特性列表,然后通过分析和询问降低了风险,同时修改了特性列表,最后从做出一张用例图,使得从全局角度对系统进行一个概览。所有这一切,其实都是开发人员在“努力变成客户”,或说努力让自己站在客户的角度看系统,真正了解客户想让希望做什么。因为,最好的理解需求的方式就是理解客户想让系统做什么。

我们在哪里?看看地图吧

做了这么多工作,是不是有点迷失方向的感觉?似乎我们已经迷失在OO从林中,不知现在身在何处。好的,那我们看看“OO地图”吧,一方面搞清楚我们在什么地方,另一方面看看我们后续有哪些路要走。

以上就是实践中的大致开发流程。一般来说,开发大致分为两个阶段:前一阶段我们要站在用户角度,搞清用户想要系统做什么;后一阶段要回到开发人员角度,进行分析、设计、编码、测试等一系列操作。而我们现在正处在两个阶段的交界处。

一般在迭代阶段提倡使用迭代与增量的方式进行开发。至于这样有什么好处,以及OO如何于迭代增量方式结合这些问题,我们将在下一篇文章中结合我们的案例详细讨论。

重点总结

1.高质量软件的第一要素是:软件做客户希望它做的事。
      2.在开发初期,我们要尽量站在客户角度。
      3.理解需求的最好方法是明白客户希望软件做什么。
      4.开发流程大约分为两个阶段:搞清用户想要系统做什么和迭代开发。

六、迭代式开发与用例驱动

再次明晰开发流程

在上一篇文章“五、需求分析之前的故事”中,我给出了一幅开发流程图:

这幅图,加上前几篇文章的内容,给不少朋友留下诸多困惑。如“特性列表不算需求分析吗?”、“用例图怎么跑到需求分析前面去了?没有需求分析哪来的用例图?”为了解开这些困惑,我们应该先把开发流程各个相关概念给明确了。

在一般开发流程中,直观上可以分为两个部分:业务阶段和系统阶段。明确这一点非常重要。其中业务阶段主要进行的工作是与计算机无关的,而系统阶段才是和计算机相关的东西。上图中“我们在这里”那道竖线所指的地方,就是业务阶段和系统阶段的分界点。

而如果从建模角度分析,整个开发流程分为四个子流程:

a)业务建模流程
      b)系统建模流程
      c)分析设计建模流程
      d)部署实施建模流程
      其中a)属于业务阶段,而b)c)d)均属于系统阶段。

业务建模流程的任务就是做业务分析、降低风险和给出系统总体概览模型。系统建模主要就是需求工程。分析设计建模就是我们熟悉的概要设计和详细设计。而部署实施建模是对系统的具体实施建立模型。

其中,我们一般所说的“需求”,实际是指“系统需求”,是在b)阶段进行的,所以a)阶段的一切工作,都不能算是需求分析。(当然,如果加一个前缀,说是“业务需求分析”,也是可以的,但是一般在软件工程领域,说“需求分析”是特指“系统需求分析”。)

而关于用例图的疑惑,大家应该知道,用例图分为业务用例图和系统用例图,而业务用例图产生于a)流程,这样它们当然会出现在b)流程,也就是需求分析之前。当然,在b)流程中还会出现用例图,这时就是系统用例图了。

明白了以上开发流程,我想很多疑惑就自然解开了。所以,其实我们所谓的“需求分析之前的故事”,就是业务建模流程。

系统阶段的法宝1:迭代与增量

在上文中,我们提到软件过程大体分为业务阶段是系统阶段。在前面几篇文章中,我们已经基本完成业务阶段,下面是进入系统阶段了。不过莫急,在正式进入系统阶段之前,我们有几个重要的、关于系统阶段的话题要聊一下。

如果你问我,在系统阶段最应该被牢记的是什么,我会告诉你,是两个词:迭代&增量。

我们已经知道,系统阶段是在业务阶段的基础上,在计算机领域内进行需求分析、系统分析、概要设计、详细设计、编码、测试、部署实施等一系列环节。但是,如果你想仅施行一遍这个流程,而完成整个系统的话,不好意思,你大致会精神崩溃。

如果这个过程仅实施一遍,其实就成了瀑布模型。小系统也许可以,但是稍大点的系统,将会带来严重的后果。我们知道,“软件开发中永远不变的就是变化”,如果采用瀑布模型,反馈将会非常靠后,一直要到部署实施阶段才能看到成果,如果客户要求更改需求或认为当前系统不是他们想要的,那么修改过程会异常惨烈,代价也是巨大的。

那么,我们应该如何实施系统阶段开发呢?法宝就是迭代和增量。具体做法是:

我们将整个系统分为许多相对独立的“单元”,每次只对一个或少数几个单元实施需求分析、系统分析、概要设计、详细设计、编码、测试、部署实施等一系列过程,一次这样的过程叫做一次迭代,而将一次迭代的成果加入现有成果的过程就叫做增量。

这种开发流程的优势是明显的:我们总是能在相对较短的时间内,完成整个系统功能的一个“子集”,这个子集是可以运行的,可以看到效果,所以如果用户不满意,反馈是及时的,修改代价也较小。通过合理的过程控制,变更代价总可以控制在一个可以接受的范围内。

在实施迭代&增量过程时,要注意一下两点:

1)迭代单元不是环节,而是系统功能的某个子集。如不能说第一次迭代完成需求分析、第二次迭代完成设计……这不叫迭代式开发,这叫里程碑式开发,和迭代有本质区别。

2)每次迭代一定要完整完成需求分析、系统分析、概要设计、详细设计、编码、测试、部署实施这一套环节,产生的成果必须是成品,是可运行、可交付的,是整个系统的一个子集,而不能是一个半成品。

系统阶段的法宝2:用例驱动

相信,大家或多或少听说过“用例驱动”。那么什么是用例驱动呢?如果你理解了上文的迭代&增量过程,就很好理解用例驱动了。上文不是说过,迭代时每次选取一个或少数几个“单元”吗?那么这个“单元”是什么?理论上,什么都可以,只要是对系统合理的划分。但在实践中,人们发现,使用业务用例作为迭代单元是最合适的。为什么呢?
      其一,业务用例大小适中,规模平均。
      其二,业务用例大多相互独立性好,适合进行迭代。
      其三,业务用例能很好反映系统的功能单元。

所以,所谓的用例驱动就是以业务用例作为迭代单元进行迭代开发的流程。

你看,我们前面做了那么多业务分析,其中成果之一就是业务用例图。而这里,业务用例成了我们进行系统迭代开发的单元和驱动者,所以,软件开发过程不是割裂的,而是相互联系的,不会说上一个阶段过去就过去了,和下一阶段毫无联系。一般,上一阶段的产品总会成为下一阶段的材料。

在这里附上一副我自己画的示意图,希望能帮助大家理解本文内容:

本文理论讲得多了点,有点枯燥,但是必须讲,因为明白这些是以后进行系统阶段的基础。从下一篇开始,我们回归示例,进入真正的系统阶段了。下一篇,将开始第一轮迭代的起始阶段:需求分析。

重点总结

1.软件过程大致可分为业务阶段和系统阶段。
      2.业务阶段进行业务建模流程,与计算机领域无关;系统阶段进行系统建模、分析设计建模和部署建模阶段,与计算机相关。
      3.系统阶段的两大法宝:迭代式开发与用例驱动。

七、【第一轮迭代】需求分析与领域分析

在前面,我们花了六篇文章的篇幅去讨论需求分析之前发生的事情,这些内容看起来枯燥或飘渺,但实际是为真正开始系统的分析、设计和实现进行的必要准备。从这篇开始,将正式进入系统的开发阶段。这一篇文章,将讨论第一轮迭代过程中的需求分析和领域分析环节。

选取第一轮迭代要实现的特性

回顾前面章节,我们说到,“迭代与增量”和“用例驱动”是系统开发的两大法宝。另外,指出了如下几个要点:

1)迭代单元不是环节,而是系统功能的某个子集。如不能说第一次迭代完成需求分析、第二次迭代完成设计……这不叫迭代式开发,这叫里程碑式开发,和迭代有本质区别。

2)每次迭代一定要完整完成需求分析、系统分析、概要设计、详细设计、编码、测试、部署实施这一套环节,产生的成果必须是成品,是可运行、可交付的,是整个系统的一个子集,而不能是一个半成品。

所以,说白了,这第一步就是要从前面得到的特性列表或业务用例分析文档中选取一个或几个特性或业务用例进行实现。(因为特性和业务用例有对等映射关系,所以,从特性列表或业务用例分析文档中选取迭代功能点在理论上是等价的。)这里,我们从特性列表选取特性。那么首先把我们前面完成的特性列表再搬出来:

上图就是我们前面得出的特性列表,总共有六个特性。理论上,第一轮选取哪些,总共选取几个,没有明确的规定,但是,在选取特性方面,还是有一定经验可以遵循的,大致有如下原则:

1)就选取个数来说,与迭代周期有关。一般的迭代开发中,一个迭代周期大多选在一周到两周之间,不宜多于两周,所以,每个周期选择的特性要估算能在这个周期内完成。(顺便说一下,在每个周期内,如果发现时间不够,做不完计划,应削减一些特性推入下个周期,切不可延长周期。另外,切不可在周期开始后添加任务。)

2)就选取的特性来说,最好是比较内聚的几个特性。也就是说,每个周期选取的特性,要尽量关联紧密,而和其他周期的特性要尽量耦合度低。

3)就选取顺序来说,因为要求每一个周期都要产生一个可运行的子集系统,所以,最好先选取平台性、基础性、对外依赖弱的特性,再选取功能性、对外依赖强的特性。例如,在这个例子中,如果第一轮迭代选择特性2则不是一个好主意。因为购物车功能要依赖于管理员、加盟商、物料等诸多特性,在后者没有实现之前,很难单独做出一个可运行的购物车子集系统。

针对以上原则进行考虑,笔者最终选择3、4和6作为第一轮迭代的特性。

至于为什么这样选,我就不再分析,请各位结合我上面提到的三个原则自己思考一下。

领域分析

在确定迭代特性后,下一步要进行领域分析。领域分析就是针对特性涉及到的实体及实体间的关系进行一个分析,构造出静态领域模型。这里要注意三点:1、领域模型是静态模型;2、领域模型要反映的是实体及实体见得静态关系(例如包含等,而调用这类动态关系不该在这里出现);3、领域模型是比较高视角的模型,其中的实体和最终程序中的实体类未必对应。

领域分析的方法有很多,下面我使用一种我总结的领域分析方法。流程如下:

step1、提取特性中的名词

我们这个迭代周期内涉及的特性是3、4和6,从中可提取出如下名词:加盟商,连锁店,网络,管理员,系统,直属连锁店,原价,等级,折扣。注意,这些名词就是领域实体的候选者。

step2、筛选名词,确定实体

不是所有候选名词都是领域中的实体,这一步要对候选者进行筛选。这一步非常重要,也相对难度较高,需要结合经验和前面的客户谈论材料、需求记录等,必要时,要咨询客户或业务专家。下面我们进行筛选。

首先,“网络”显然不是领域内的实体,排除。“系统”是对整个产品的总称,也比较明显不是领域实体,排除。排除了两个明显的“干扰项”,下面看几个可疑的家伙,这里,我发现“原价”、“等级”和“折扣”比较可疑。通过对前面材料的回顾分析,基本可以肯定“原价”应为物料的一个属性,难以成为单独的实体,排除。而“折扣”和“等级”是否能成为实体,依赖于后续系统的设计方式,如果将等级单独设计成一个模块,则其应该为一个实体,而若将其设计为加盟商的属性,则不能成为实体。对于这种摸棱两可的候选项,我们姑且保留,并加一个“?”作为后缀。当然,由于前面分析中说明“折扣关联与等级”,所以,仅保留“等级”就可以了,“折扣”可作为其属性。

经过上述筛选,获得下列领域实体:加盟商,连锁店,管理员,直属连锁店,等级? 。

我们不妨先将各个实体画入领域分析图。

step3、确定实体间的关系

有了实体,下面要确定各个实体间的关系。如上图所示,管理员比较容易确定,因为它似乎不与任何实体存在静态关系。这里比较纠结的是加盟商、连锁店、直属连锁店和等级四个实体。关于它们间的关系我们不能臆想,要去查阅前面的记录资料或去咨询客户。在OOA&D实践之路——真实案例解析OO理论与实践(三、降低风险)一文中,我们记录了这样一段文字:

“加盟商和连锁店不是一个概念,加盟商直属总公司,连锁店可能直属总公司也可能直属某加盟商。加盟商和直属总公司的连锁店直接向总公司定料,不直属的的连锁店向相应加盟商领取原料。连锁店按原价定料,加盟商按照等级分为5级,每级折扣不同。”

从以上文字中,我们提炼出如下关系:

1)加盟商和连锁店没有关系。

2)直属连锁店是连锁店的一种特例。而且应该还有种“非直属连锁店”

3)非直属连锁店可能会属于某个加盟商。

4)等级仅属于加盟商。

到这里为止,几个实体间的关系基本清晰了,我们将其表达在领域分析图里:

以上就是第一轮迭代的领域分析图了。其中直属连锁店和非直属连锁店都继承于连锁店,而非直属连锁店必定对应一个加盟商,加盟商可以对应零到多个非直属连锁店,每个加盟商有且只有一个对应的等级。

step4、领域分析的优化

别觉得完成上述步骤就万事大吉了,虽然我们的领域分析模型已经出来了,但它可靠吗?还有优化的余地吗?万事多思考一点,总是没坏处的。就像刚出厂没经过检测的飞机,你敢坐吗?所以,这里我们要对已经成型的领域分析模型进行一个检测。检测的方法很简单,就是回顾前述工作,逐个点进行验证和确认。当我回顾了特性及初步业务需求后,发现如下两点问题:

第一、管理员是否是领域中的实体?要知道,领域中的实体,一定是系统边界内的实体。那这里就要分情况讨论了,如果系统只需要一个管理员,那么系统中就没有对管理员的管理,那么管理员只是系统外部的用户,就不能存在于领域实体中了;如果有多个管理员,并且管理员管理单独成为一个模块,则管理员应放在这里。于是我联系了客户,在确认系统只需要一个管理员后,我毫不犹豫将管理员从领域模型中抹去了。

第二、我觉得领域模型抽象层次还有所欠缺。加盟商和连锁店性质很类似,都有注册、都要被审核,其实它们应该继承自某个更高抽象,这里我称其为会员。于是经过思考和修正,得到最终的领域模型如下:

这里要注意,领域模型只表示出实体和实体间的静态关系就好了。至于各个实体有哪些属性,有哪些交互,那是后面设计阶段的工作。

基于用例的需求分析——编写用例图及用例规约

传统的软件工程中,通常使用需求规格说明书进行系统需求分析。而我更喜欢使用用例分析技术。在用例分析阶段,我们要输出两份文档:用例图和用例规约。用例图用于概观上描述需求,用例规约用于对用例的流程进行详细描述,直接作为后续设计、编码及测试的依据。

下面进行系统需求分析。

step1、提取特性中的动词

上面我们曾使用提取名词法找实体,那么要找用例,就要分析特性中的动词了。废话不多说,我们先提取出特性3、4和6中的动词:注册,审核,使用,管理,定料。因为用例分析中要确定每个用例的Actor,所以,在提取完动词后,要对每个动词赋予其主语,作为其Actor,于是,最终得到的提取列表如下:注册【未注册加盟商和连锁店】,审核【管理员】,使用【注册后的加盟商和连锁店】,管理【管理员】,定料【注册后的加盟商和连锁店】

step2、动词筛选

同样的,不是每个动词都是合法的用例,这里也要对候选动词进行筛选。具体到这里,“使用”是个很泛的名词,不应该成为用例,而“定料”虽是用例,但于这轮迭代的内聚性不高,建议放入后续迭代中。最后得到用例:注册【未注册加盟商和连锁店】,审核【管理员】,管理【管理员】 

step3、用例分析与优化

这里的用例有进一步分析的余地,首先,“管理”这个用例粒度太大,很难知道设计和开发,于是应进行分解;其次,“审核”应属于管理的一部分。基于以上两点,将管理分解为“删除会员”、“指定加盟商等级”和“审核会员”,另外,“未注册的加盟商和连锁店”,跟据领域分析,可写作“未注册会员”。另外,这里有几个隐含的用例,就是关于等级的管理,这几个用例从动词分析中是提取不出来了,这取决于特性描述的局限性。我们通过经验和个人的分析,得到关于等级的管理用例。于是,最终用例敲定如下(这里用例命名规则为 uc迭代周期编号.用例编号):

uc1.1 - 注册 actor:未注册会员

uc1.2 - 删除会员 actor:管理员

uc1.3 - 指定加盟商等级 actor:管理员

 uc1.4 - 审核会员 actor:管理员

uc1.5 - 添加等级 actor:管理员

uc1.6 - 删除等级 actor:管理员

uc1.7 - 等级信息维护 actor:管理员

step4、给出用例图

跟据上述分析,给出用例图如下:

step4、编写用例规约

需求分析的最后一步,就是为每个用例编写相应的用例规约。用例规约是一种规范化文档,它面向设计开发人员,详细描述了各个用例内部的基本信息、执行流程及例外流程等,必要时可附上相应的活动图。因为用例规约是设计和开发的重要参考文档,所以在编写过程中务必要做到详尽和准确。

下面仅给出“注册”这个用例的用例规约作为示例和参考。

必要时,可在下端附上活动图:

总结

在每个迭代周期的初始阶段,当选择完需要迭代的特性后,就可以开始领域分析和需求分析了。本文是先进行领域分析。其实两者并无确定的先后顺序,往往是相辅相成,交叉进行。下一篇文章将进入第一迭代周期的设计阶段。

作者:T2噬菌体 出处:http://leoo2sk.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

真实案例解析OO理论与实践相关推荐

  1. OOAD实践之路——真实案例解析OO理论与实践(二、第一项任务:特性列表)

    查看本系列全部文章: <OOA&D实践之路--真实案例解析OO理论与实践>索引贴 第一份说明       当这个项目开始时,我们得到的关于我们要做的系统的唯一说明是一页Word文档 ...

  2. OOAD实践之路——真实案例解析OO理论与实践(五、需求分析之前的故事)

    查看本系列全部文章: <OOA&D实践之路--真实案例解析OO理论与实践>索引贴 高质量软件的第一要素       到目前为止,我们做了很多工作,但是我一直在强调这些都还不是需求分 ...

  3. 【系统架构设计师】软考高级职称,来自订阅者真实反馈,从理论、实践、技巧让你掌握论文写作秘诀

    [系统架构设计师]软考高级职称,来自订阅者真实反馈,从理论.实践.技巧让你掌握论文写作秘诀. 目录 对于系统架构设计师的论文写作几点建议 论文摘要 论文正文 论文结尾 关于[系统架构设计师]备战,我想 ...

  4. C/C++ 数据结构设计与应用(四):C++数据压缩与传输:从理论到实践的全景解析

    C++数据压缩与传输:从理论到实践的全景解析 一.数据压缩的策略与方法 (Strategies and Methods of Data Compression) 1.1 数据压缩的基本概念与原理 (B ...

  5. 麦麦养老:养老照护服务关键痛点及智慧养老解决路径实践案例解析

    主讲人丨郭 飞 开篇: 7月24日,AgeClub成功在上海举办<2020中国老年行业创新发展系列论坛>,聚焦于后疫情时期中国老年行业的最新机会与挑战应对.会上,我们邀请了麦麦养老副总裁兼 ...

  6. 跨平台C++ Qt数据库管理系统设计与实战:从理论到实践的全面解析

    跨平台C++ Qt数据库管理系统设计与实战:从理论到实践的全面解析 一.引言(Introduction) 1.1 数据库管理系统的重要性(Importance of Database Manageme ...

  7. 视频教程-数据分析快速实践:企业真实案例精讲-Python

    数据分析快速实践:企业真实案例精讲 本科:武汉大学 软件工程 博士:美国佐治亚大学 地理信息系统 杨威 ¥69.00 立即订阅 扫码下载「CSDN程序员学院APP」,1000+技术好课免费看 APP订 ...

  8. Microsoft NLayerApp案例理论与实践 - 项目简“.NET研究”介与环境搭建

    项目简介 Microsoft – Spain团队有一个很不错的面向领域多层分布式项目案例:Microsoft – Domain Oriented N-Layered .NET 4.0 App Samp ...

  9. 实践出真知之Spring Cloud之基于Eureka、Ribbon、Feign的真实案例

    转载自  实践出真知之Spring Cloud之基于Eureka.Ribbon.Feign的真实案例 Eureka是Spring Cloud Eureka的简称,是Netflix提供的组件之一.通过E ...

最新文章

  1. springboot配置Redis哨兵主从服务 以及 Redis 集群
  2. JavaScript open() 函数
  3. Log4net核心组成
  4. 大数据建模、分析、挖掘技术应用研修班的通知
  5. 挤拥城市游戏android,拥挤城市crowdcity
  6. TypeScript 安装与使用
  7. 【clickhouse】clickhouse 表引擎 之 SummIngMergeTree
  8. 实战 RocketMQ 流量削峰,怎么能错过这一篇!
  9. NameError: name 'reload' is not defined等python版本问题解决方案
  10. vue实现密码的表单验证~(旧密码,新密码,重复新密码)
  11. IDEA配置java开发环境
  12. SCDM——多进(出)口流道的抽取
  13. 休谟问题和金岳霖的回答
  14. 词典GoldenDict
  15. C++Primer 第10章lambda表达式
  16. 中国智能农业行业市场供需与战略研究报告
  17. Flutter-如何计算文字宽高
  18. Unsupervised Hyperspectral Mixed Noise Removal Via Spatial-Spectral Constrained Deep Image Prior
  19. 音乐音频 | openSMILE提取音频需要掌握的知识
  20. 周鸿袆教你打造十页完美商业计划书的十条法则

热门文章

  1. win10---血战上海滩
  2. 使用sketchfab下载artstation资源导入到UE流程
  3. windows C 盘扩容
  4. 初学MSP430F5529定时器
  5. iPhone X Face ID判断
  6. UNIT07 BREs EREs PREs
  7. 第十二届noc网络机器人赛项成绩_第十六届中小学NOC活动物流机器人赛项全国决赛获奖结果.pdf...
  8. 数据结构(十六)——左高树(含合并过程详细图解)
  9. python列表嵌套元组拆分_Python进阶之元组拆包及嵌套元组拆包
  10. 免费网盘如何选择@2020年