zoukankan      html  css  js  c++  java
  • 多元化时代敏捷软件开发的崛起与传统软件工程的延续

     

    多元化时代敏捷软件开发的崛起与传统软件工程的延续

    1.传统软件开发模式

      1.1瀑布模型

        1.1.1概念

          瀑布模型,顾名思义,软件开发的过程如同瀑布飞流一般,自上而下,逐级下落。瀑布模型的核心思想是将问题按照工序进行简化,将软件结构的设计与软件功能的实现分隔开来,同时运用结构化的分析与设计方法,将软件的物理基础与逻辑实现也分隔开来。瀑布模型将软件开发周期按照顺序线性地分为了六个基本活动阶段,分别为制定计划阶段、需求分析阶段、软件设计阶段、程序编写阶段、软件测试阶段以及运行维护阶段。这六个阶段线性排列,相互衔接,具有严格的执行顺序。瀑布模型的提出很大程度上体现了流程化的设计思想,便于进行分工与协作。

        1.1.2优点

          瀑布模型正是由于其严格的阶段化流程,因而在流程设计上具有十分明显的优势。该模型使得项目本身就可具备了天然的检查点,阶段的划分为软件开发者提供了定时检查的可能,是较强的标准化措施。而对于软件开发者来说,每一阶段的完成既象征着下一阶段的开始,也意味着上一阶段的完结,因而能够让开发人员集中更多的精力于后续的开发,而不再考虑之前的设计,大大提高了开发的效率,也提高了项目本身的标准化程度。

        1.1.3缺陷

          由于瀑布模型基于了严格的阶段设计,而不同的阶段的执行顺序也是线性的,虽然对于软件开发人员来说规定好了开发的路线,也为不同的阶段划定了严格的界限。然而也正是这种界限,使得项目开发的各个阶段之间缺乏必要的交流与探讨,也没有相关的情况反馈,因而对项目的完整性和交互性没有很好地约束。另外,顺序化的执行方式意味着我们只能在项目生命周期的后期才能看到软件开发的初步结果,使得留给后续测试反馈等工作的时间过少,某些意义上拖长了整个产品的生命周期。除此之外,为了严格按照既定时间完成各阶段的内容,常常要求我们用一些强制性的措施来跟踪项目的发展过程,从而保证项目进度的正常,这就意味着整个项目开发的过程过于死板,难以变通,不具备良好的反应能力,更不适应用户的需求变化。

      1.2迭代模型

        1.2.1概念

          迭代开发是一个大规模的迭代过程,它将整个项目分解成了若干个小项目,而每一个小项目在一定程度上也可以再继续划分成更小的项目,而对于每一个这样的划分出来的小项目,我们都可以基于瀑布模型对其进行完整的一轮开发,在很短的周期内产生一个可发布的产品,而这个产品则是最终产品的一个子集。迭代开发由若干个这样的过程组成,类似于小型瀑布开发项目的集合。迭代开发适用于早期需求不断变化的项目,并且要求分析设计人员对项目所设计的领域具有相当高的熟悉程度。总而言之,对于那些风险度较高,用户参与程度较高,同时要用到面向对象建模的项目,只要软件开发团队中具有高素质的管理者,开发人员之间协作模式良好,那么迭代开发模型将是很好地开发方式。

        1.2.2优点

          由于将整个大项目变成了若干个小项目的迭代和,因而迭代模型很大程度上降低了项目开发过程中在一个增量上的开支风险,倘若开发人员在某一迭代上出现了问题,也只会在此个迭代上进行风险的评估,对于其他迭代的影响不大。另外该模式降低了产品无法按照既定进度进驻市场的风险,因为在迭代的早期我们就可以预估出开发过程中面临的风险,从而提早作出规划,避免了开发过程中遇见风险后的手忙脚乱导致的一系列难以预料的问题。迭代风险通过不断的迭代加快了开发人员的开发效率,因为在迭代的过程中会有一个不动点,开发人员只要把握准这个不动点,就可以确定问题的焦点所在,从而进行高效率的开发,加快项目工作的进度。而相对于单纯的瀑布模型,迭代模型对于用户的需求更改则显得更为适应,在这种模型下,用户的需求在开发前期并不十分明确,并且常常随着开发过程的进行在不断变化,迭代的过程也正是对新需求的适应过程,完美的解决了用户需求的更改与产品开发之间的冲突问题。

        1.2.3缺陷

          迭代开发由于要进行不断的迭代来完善产品,以适应用户的需求变化,因而如果用户的需求相对较为固定,如果仍然采用边写边重构的方式进行开发,无疑会增加很多无谓的重复工作。加之在迭代前期对产品的形态缺乏最终概念的时候,如果对于迭代的方向把握不准确则很容易偏离最初的意图,造成不必要的损失,因而迭代开发模式更加要求开发人员的设计水平以及全局观念,更需要一个较强的架构师进行架构管理,这也是迭代开发的一个难以普及的难点。

      1.3其他模型

        1.3.1快速原型模型

          快速原型模型是在开发真实系统之前先构造一个系统原型,并在该原型的基础上进一步地逐渐进行整个系统的全面开发工作。开发原型的过程事实上也是与用户进行交互的过程,获取用户对原型的评价,从而在后续的开发过程中根据评价有针对性的对系统作出调整和修改,使其一步一步满足用户的需求。该模型克服了瀑布模型的缺点,减少了由于软件需求的不明确而带来的开发风险,但与此同时快速建立的系统模型结构以及重复多次的连续修改会使得产品的质量相对较低,并且限制了开发人员开发项目的创新性。

        1.3.2螺旋模型

          螺旋模型以前面几个模型为基础,基于一个快速形成的模型,以进化的开发方式为中心,定义了四个项目阶段,并且在每个阶段周期都采用迭代开发的方法,使得软件开发路径沿着螺旋线迭代前进,从而带来了层次的不断递进。螺旋开发强调了风险的分析,它要求每个开发人员都要了解每一个层次可能出现的风险,并且及时对风险进行分析,采取相应的措施,减少风险带来的损害。螺旋模型很好的解决了开发高风险系统的软件开发过程的实现。

    2.敏捷软件开发模式

      2.1何为敏捷开发

          所谓敏捷开发,自然是相对于“非敏捷开发”而言的。而相比“非敏捷开发”,敏捷开发最重要的就是程序员团队与业务专家之间的紧密合作、团队成员之间的面对面的沟通交流以及频繁的软件版本的交付。而这样的软件开发方式极大地适应了当前社会情形下用户需求不断更改的特点,敏捷开发可以对用户的新要求迅速作出反应,适合小规模团队开发软件使用,更凸显了每个“人”在软件开发周期中的作用。

      2.2敏捷开发的特点

          在敏捷联盟的官方网站上,我们可以看到这样的四句宣言:个人与沟通胜过过程与工具;可工作的软件胜过面面俱到的文档;客户协作胜过合同谈判;相应变化胜过遵循计划。这四句话完美的概括了敏捷开发的四个特点,体现了使开发团队快速工作并具有应变能力的价值观和原则。敏捷开发看重团体中每个“人”的作用,强调每个开发人员相互之间的沟通与协作,开发过程和开发工具在“人”的面前就要稍低一等。而这个“人”也不仅仅指的是开发人员,还包括了需要服务的客户,核心思想就是以本设计团体为中心,和外界各类人员进行配合。敏捷开发重视的是实际产品的开发与交付,一个能实际工作的软件远远要好于求全责备的各种文档。另外敏捷开发注重交互,尤其是与用户之间的交互,开发人员根据用户的需求的变化,不断调整软件的功能为用户提供不同版本的可运行的软件。

      2.3开发原则

          敏捷开发事实上是基于用户需求的一种开发方法,因而开发的深度应当随用户的需求的不断展开而逐步深入。因而在项目开发的初期,敏捷开发不提倡过度的需求分析,保证对需求的变化的响应是动态且及时的。另外敏捷开发的项目在设计上应当分为数个迭代周期,在每个周期内都应当产出可交付的软件产品,并且将用户的反馈作为下一次迭代开发的基础。总结来说,可交付的可执行软件是评判敏捷开发项目进展以及成果的最主要的依据,而整个开发过程又是自反馈的,这种迭代式的自反馈使得开发的过程也是不断改进,不断完善的过程。

      2.4开发流程

          敏捷开发大体上将项目管理开发的生命周期分为了三个阶段,分别为项目规划阶段、项目启动阶段以及迭代开发与发布阶段。项目规划阶段类似于传统开发过程中的可行性分析和需求分析阶段,在这一阶段内开发人员需要确定软件开发的计划以及对客户的需求进行初步的了解分析。项目启动阶段是一个过渡阶段,用于软硬件环境的准备、开发场地布置、资源准备等等。在必要的情况下还可以对前一阶段的需求分析进行更详尽的处理。迭代开发与发布阶段是整个流程的核心部分,项目组会根据目标系统的发布版本,将这一阶段分成多个迭代重复的过程,并且每一次的过程都是一次目标系统的增量在开发环境中实现,并从开发环境到生产环境进行迁移。这里需要强调的是在这个阶段里最重要的过程既不是开发过程,也不是测试过程,而是常常被传统开发过程所忽视的发布过程。因为在敏捷开发模型中,产品的第一次发布会在较早的时间产生,而这个版本的发布会对客户的投资以及市场的反馈还有后续的项目走向调整产生重要的意义。

      2.5优点

          敏捷开发采用了简单的计划策略,因而开发的周期较短,适合于中小规模项目的开发。并且在敏捷开发的整个开发过程中,我们都采用了迭代增量开发,不断反馈修正,不断测试的方法,既有力的保障了软件的质量。最重要的一点是敏捷开发很好地适应了用户瞬息万变的需求,能够做到及时响应,及时调整,为用户能够提供高质量的软件。

    3.敏捷开发模型与传统软件开发模型的对比

      3.1与瀑布开发模型的对比

         图 3 - 1

     

          瀑布模型将软件开发周期分为六个基本活动,并且规定了其自上而下的衔接顺序,虽然这样的开发方式易于使用并且方便实用,但是很难表达出用户的需求,不适应于需求的变化。

          图 3 - 2

     

          敏捷开发以人为核心,将软件项目切分成多个子项目,各个子项目都经过测试,具备集成和可运行的特征,即把大项目分解成为多个相互联系但也可以独立运行的小项目,在这个过程中,软件一直处于可运行的状态。

      3.2与迭代开发模型的对比

          敏捷开发与迭代开发都强调在较短的开发周期内提交软件,但是基于Scrum的迭代开发却会在一个较长的迭代周期频率下不断交付。在迭代开发的过程中我们不允许有不断改变的需求,否则迭代的方向会不停的改变,使得偏离预想的路线,因而在这样的一些场景下就会使得迭代变得十分困难。与此同时,迭代开发在项目的估算方面难度很大,导致对项目的安排缺乏宏观性,不容易作出相关的一些承诺。相比较而言敏捷开发则是适应型的开发方法,更有利于处理变化的需求,而其具备的小团队的特点则有利于开发人员之间的沟通交流以及用户代表与开发团队之间的交流,这种交互是很关键的反馈模式。“人与交互”带来的是知识的迅速传播以及思维共享。

      3.3与螺旋开发模型的对比

          与螺旋开发模型相比,敏捷开发的方法强调更多的是适应性而非预见性。螺旋开发的本质是将瀑布模型和快速原型模型结合起来,并且对其他模型所忽视的风险加以分析,因而适用于大型的系统。螺旋模型的特点在于我们不需要在开始的时候就完全定义清楚客户的需求以及软件开发的基线,我们只需要定义清楚最主要的功能,然后不断的迭代,从客户的意见与反馈修正迭代方向,从而完善软件的功能。我们可以这样认为,螺旋开发模型很大程度上是由风险驱动的,我们不可能在不对每个阶段进行风险评估的情况下进行循环迭代,因而在螺旋开发的模型中,风险评估往往是很重要的一部分。而对于敏捷开发来说,在整个开发周期内,很多情况的发生都具有不可预见性,因而敏捷开发强调的不是风险的预估而是对风险的适应,如何快速集中地适应发生的变化,才是敏捷开发最需要研究的问题。

      3.4与CMMI模型的对比

          CMMI标准是目前对软件组织能力进行评价时使用的最为广泛的模型,它能够规范软件开发的过程,并且产生和维护大量的文档,所以被称为“重载方法”,与此相对,敏捷开发因为其较高的软件开发效率而被称作“轻载方法”。虽然敏捷开发能够提高软件开发的效率,克服了传统软件工程中认识和实践上的弱点,但是其本身也存在很多不足,比如在工程上和管理过程上缺乏一致性和充分性的考虑,并且常常只使用于中小型软件的开发,对于大中型的软件支持不足。因而在某种意义上来说,CMMI与敏捷开发既是对立的,也是是互补的。CMMI是一个非常好的框架,它强调了机构性的过程管理,但是如果没有很好的理解和正确的实施,常常会造成不必要的浪费和损失。敏捷开发强调的是可实现功能的软件,追求开发的效率,尽量缩短开发周期,并且开发是面向非结构性的,具有应变的时间。

      3.5宏观对比

          总结而言,敏捷开发一部分程度上是基于传统的开发方法而改进的,但它能够吸收其他方法的优点,而尽量减少其缺点,正所谓融百家之所长。然而在继承的基础上,敏捷开发也很好地做到了改进。

          比如在分工方面,传统方法阶段的划分分明,一般来说不同阶段的工作由不同的人来完成,每个人都有标准的分工。但是对于某些需要全体成员积极参与的项目比如课程设计或者毕业设计这样的项目,采用传统的方法意味着告诉前面已完成工作的开发人员不必要再关注后续部分的工作,这样既降低了所有人的项目参与度,也不利于互相之间的交流学习。敏捷开发很好地解决了这个问题,团队的每个成员从与客户的交互到代码的编写,都需要亲身体验,这就保证了各个成员的参与度。

          又比如在用户需求方面,传统方法常常要求用户提供明确的需求,并且开发人员也只能在正确的需求条件下才能完成正确的开发。但是我们都知道一方面是因为计算机的发展速度之快,以致于固定的需求已经不再是软件的基本要求,而应当是跟随市场的反馈而追求不断的变化与创新,随着时间的推移而产生巨大的变化,另一方面是顾客与开发人员之间的沟通可能存在不到位的情况,致使理解出现了偏差,导致对需求的明确度不高。所以传统开发模型对于变化的需求不能很好地适应,这一点远远比不上敏捷开发极强的适应性。

          还比如多数传统的开发方法是由文档驱动的,在开发的每一个阶段都需要相应的文档进行总结分析,并指出后续的开发要点,因而如果前面的阶段没有完成或是没有及时编写文档,开发人员是无法继续进行下一阶段的工作的。敏捷开发则不同,它是由功能驱动的,因而重点在于每一阶段的测试工作的完成,这很有利于小规模团体之间的交流协作与提升开发兴趣。

          再比如说传统开发方法中对于顾客的定位是观察者,而不是参与者,这就决定了顾客与开发团队之间的利益博弈的关系,用户的参与度不高,常常导致开发团队交付的软件与用户预想的结果有较大的差异。在敏捷开发模型中,顾客与团队是相辅相成的,顾客可以直接参与软件的开发过程并及时提出相应的修改意见,一旦有了交互的过程,就意味着开发出来的软件具有极高的适用性,能够促进客户与团队之间关系的良性循环。

          最后我们看看软件模块的集成。传统开发方法的的集成工作常常会堆在开发的末期进行处理,这就使得在集成的过程中如果出现问题的话很难有足够的时间进行调试和修改,而敏捷开发在每个阶段都会有一次模块的集成,每一次的集成对于软件的改动相对较小,因而发现问题可以及时定位纠正。事实上,敏捷开发的测试往往是自动化的,所以这也给开发人员减轻了很多压力与负担。另外,传统软件开发的周期比较长,获得可执行软件的时间也比较晚,而敏捷开发的周期较短,并且很早就能获得第一版可执行软件,留给用户进行反馈的时间也比较充分。

          “取其精华,去其糟粕”,这是敏捷开发方法之于传统软件开发方法之间最重要的区别与联系。

    4.新软件形势下的敏捷开发

      4.1实现敏捷开发在实际项目中的应用

        4.1.1国内软件开发环境

          (1)中小型企业较多,大中型企业较少,很多企业开发的项目达不到所预期的效果;

          (2)多数公司对于软件开发的测试过程不够重视,不足以应对敏捷开发所要求的测试技能以及需求的变化;

          (3)无论是什么开发方式都要求团队之间的配合度和和谐性,但是很多团队目前的默契度不足以支持敏捷开发的各个阶段;

          (4)目前很少有企业领导对敏捷开发方式有足够高的重视度,如果不能自上而下的实行,底层开发团队往往有心无力;

          (5)中小型公司缺乏相关的专业人才,很难普及相应的模式;

          (6)公司常常注重的是个人能力,相信的是少数人的能力,以此来保证软件的开发,但是这种信任则是冒着很大的风险;也有的公司更加看重项目的流程,对项目本身的效率造成了很大的影响,难以产生高的效益。

        4.1.2敏捷开发与企业架构的兼容性

          我们知道敏捷开发常常适用于中小项目的开发,并且开发团队以3-5人为宜,而这显然是与传统企业的架构是格格不入的。但是面对着客户需求由“单一化”到“动态化”的新型软件格局的普遍化,传统软件工程的开发方法受到了相当严重的打击。我们知道传统的软件开发模型大多都是线性流程,这种流程带来的最大的问题就是架构单一,难于变通和创新。因而敏捷开发的思想在当前的环境下就显得格外重要。那么敏捷开发如何实际应用于传统软件企业当中呢?事实上敏捷开发与企业架构是可兼容的,但是我们需要为之付出不小的努力。从目标来看,企业架构以及敏捷开发的目的都是向顾客交付功能与需求对齐的高质量软件,并且不断响应过程中需求的变更,然而两者的实现方式截然不同。事实上从某种程度上来讲,对于一项工程,无论是哪种开发方式的缺失都有可能带来一些微小的问题。比如对于一个文档处理系统来说,仅仅使用敏捷开发方式虽然效率很高,但是很难协调处理好企业架构的需要,类似跨越需求,接口或是操作性的问题。或者比如说一个使用瀑布模型开发的系统,很完美地处理好了企业架构的问题,但是却不能在较早的时间向客户展示系统的价值,也几乎不可能通过迭代解决可能遇到的风险。所以均衡这两者是软件开发最为平衡的模式。我们可以这样实现二者的均衡:对于一个敏捷开发团队,它可以属于一个企业架构组,团队中的成员有必要与组内成员互相交流,互相联系。敏捷开发在保证自身工作完整性的条件下兼容组织架构的运行,使得两者相互包容,和谐相处。

        4.1.3敏捷开发与CMMI模型的共存

          在这里我们需要强调两个概念,融合与共存。

          什么是融合?一杯牛奶和一杯咖啡,各取半杯混成一杯,这就叫融合。但是融合成的这一杯牛奶咖啡真的好喝么?也许合适的比例下得到的产物味道很好,但是这样能够给予我们多大的好处呢?对于敏捷开发和CMMI来说,融合并不是一个好的选择,因为一旦融合就意味着必然有一者会消失被另一个吞并,或是两者都消失,转化成新的东西。从理论上来讲,CMMI与敏捷开发两个行业的差别很大,所解决的问题,所要面临的问题以及自身的客观规律也都相差很大,而CMMI与敏捷开发在行业中的定位仅仅只是方法,因而如果不能使得行业本身或是其自身规律融合,仅仅只有方法的融合,这是不协调的。目前的状况是我们很难找到不同问题之间的融合点,因而这两个方法之间的融合也成了无稽之谈。

          不能融合并不代表不能共存。什么是共存?老虎狮子各自在自己的领域中活动,必要时相互协作,这是共存。CMMI与敏捷开发的关系就如同家里的桌椅之间的关系,桌子和椅子单独使用总是不能够尽善尽美,但是也没有什么融合的必要,而如果桌椅摆放在一起就能协调运转,完成相应的功能。

          所以在两者可以共存的前提下,我们需要考虑的是面对什么项目应当选取哪种开发方式,在哪些阶段使用哪种开发方式,两者能否结合使用这些问题。我们可以通过一些切入点来思考这些问题:

          (1)    明确软件整体的发展方向,认准软件发展形势,在提高自身竞争力的前提下选择合适的开发模式;

          (2)    根据企业自身的规模以及发展情况适当选取模型,拿小型公司来说,CMMI对于企业划分成五个等级,小公司自身处于CMMI所规定的等级中的较低级,在这样一种情况下贸然采用敏捷开发是有相当大的风险的。而对于中等规模的公司来说,在项目过程达到要求的情况下可以适当选择敏捷开发以提升开发的效率。对于大公司来讲,大公司讲究的是企业的基础和文化,并且拥有优秀的开发和测试人员,因而最好不要轻易的改变自身的项目开发模型,但是可以现在小型项目上进行试验,以此来积累经验减小风险。

          (3)    对于特定的项目开发,我们如果既能符合CMMI的规范,又能采用敏捷开发提高效率,那当然是再好不过了,这样我们便可以获得真正意义上的开发的可重复性以及成本风险的可预测性等等好处。

          (4)    采用敏捷开发一定要考虑到自身的利益,以及未来发展的期望,目光长远才能稳定发展,不能急功近利,只看眼前。

      4.2敏捷开发在项目开发中的要点

          近些年来,越来越多的开发团队开始采用敏捷开发的开发方式对软件进行开发,并且取得了较好的效果。而敏捷开发模型其实也是一把双刃剑,合理利用可以极大的提升开发的效率,一旦没有把握开发中的要点,有可能带来资源的浪费以及成本的损失。所以对于敏捷开发人员来说,需要在开发周期中重视下面几个要点,这些要点也常常与传统开发方式的要点相反:

          (1)    敏捷开发需要重视概念和架构的设计,适当看淡产品的详细设计,强调的是产品的路线规划、市场趋势、客户价值、技术趋势、实现方式、层次分布、层次关系设计等,而不是具体的设计和做法以及API接口;

          (2)    建立系统化的分析方法,对产品进行SWOT分析,注重客户的需求,适应市场的变化;

          (3)    开发由业务和客户进行驱动,而不应由开发技术进行驱动,或者说不要从技术方面盲目的扩大需求,这种需求往往并不是客户真正想要的需求。另外就是敏捷开发虽然拥抱变化,但不欢迎盲目的变化,它崇尚简单的设计而不是颠覆性的设计,意味着如果我们需要做出一些改动,务必保证版本迁移的平滑性;  

          (4)    测试优先,在编码之前应当先编写测试。与传统的开发模型恰恰相反,先编写测试用例,既是一种验证,更是一种设计,在这个过程中,我们可以发现某些需求设计上的缺陷从而便于更改需求和设计,避免付出无谓的劳动,造成无意义的浪费;

          (5)    时刻考虑版本的兼容性,当设计变动的时候,要时刻考虑产品的架构,产品的规划以及上一个版本的兼容性,以方便后期的维护工作;

          (6)    不看重的文档,但并不意味着不撰写文档,敏捷开发重视沟通,文档也是沟通的一种方式。敏捷开发所排斥的是开发过程中的冗余文档,有利于节省大量的时间和成本;

          (7)    敏捷开发提倡沟通,沟通才能使效益最大化,这里的沟通既包含了团队内开发人员的相互沟通,更包括了开发人员与客户之间的沟通,沟通的效果会直接影响软件的质量、成本以及软件能否顺利交付;

          (8)    时刻保证开发团队的活力与激情,只有这样才能随时适应需求等设计的变化,从而做出更高质量的软件。

    5.总结

          在当下日益发展的计算机软件环境下,敏捷开发已然成为软件开发者不可或缺的一种开发模式,当然其自身仍然存在着一些尚未解决的缺陷,而我们要做的就是合理运用敏捷开发的模式,并与传统开发模式相结合,因地制宜,对症下药,这样才能有效地降低开发的成本,提高开发的效率,适应软件社会的发展趋势。

    6.参考文献

    [1] 张林、张德勇,敏捷开发在软件产品项目中的应用实践[A].2011.

    [2] Roger S Pressman,软件工程-实践者的研究方法[M].北京:机械工业出版社,1999.

    [3] Jakobsen C R,Sutherland J.Scrum and CMMI going from good to great[C].Chicago,IL:IEEE,2009:333-337.

    [4] 邓辉,敏捷软件开发:原则、模式与实践.清华大学出版社,2003:9,132.

    [5] 苏敬凯,敏捷软件开发[M].机械工业出版社,2008,1.

    [6] CMMI Product Team. CMMI for development,version 1.2[M].Pittsburgh:Carnegie Mellon University Software Engineering Institute,2006.

  • 相关阅读:
    模块
    Queue(队列)
    Stack(栈)
    Vector(容器)
    位图像素的颜色
    大数处理之三(除法)
    大数处理之二(幂运算)
    浮点数(double)的优势
    大数处理之一(加法和乘法)
    Depth-First Search
  • 原文地址:https://www.cnblogs.com/ztc14061055/p/5985127.html
Copyright © 2011-2022 走看看