把用例应用到实时系统中的实践

发表于:2007-04-28来源:作者:点击数: 标签:实践系统用例用到实时
概述 本文基于我去年为客户 开发 一个实时控制系统的工作。 本文的目标是:第一,重点描述实时系统规格的确定和用例之间的关系;第二,描述我们如何开发用例模型以及应用用例给我们带来了哪些益处。 要说明的第一件事情是为什么我们需要类似这样的一篇文章,

概述
本文基于我去年为客户开发一个实时控制系统的工作。

本文的目标是:第一,重点描述实时系统规格的确定和用例之间的关系;第二,描述我们如何开发用例模型以及应用用例给我们带来了哪些益处。

要说明的第一件事情是为什么我们需要类似这样的一篇文章,然后我们再说明用用例来描述实时系统有哪些特殊之处。

在叙述为什么需要本文之后,我将描述一个具体项目和它的用例模型。我将重点描述用例模型的一些有趣的和有意义的特性,然后看看它们是如何有益于项目的。最后,我将讨论一些经验学习,并给出一些建议。

CCE2>为什么我们需要这篇文章?
有一种看法认为用例只对你描述高度交互的系统有用,这些系统以IT系统为代表,如银行系统。例如,在银行中你可能需要处理一个抵押应用,它会有大量的用户交互。对于实时系统来说,并不存在那么多的用户交互。但是,如果实时系统具有有意义的外部可视的行为,用用例描述仍然对定义它们的规格是有用的,即使在一些用例中用户只是选择一些设置以及告诉系统完成一些功能。

在为客户工作了这么多年来,我发现用例被广泛地误解。尽管有很多与之相关的书籍、文章和培训课程。可能这些书籍、文章和培训课程都只关注于问题,因此它们提供大量的不同的方法。在这些著作中经常提到的一个例子是自动柜员机(ATM)。类似这样的例子对于说明一些问题是有用的,但在处理很大而且很复杂的实际系统时却通常没有明显的帮助。在实时系统中你将怎样处理出现的问题?有一些实际的例子可以对你有所帮助,本文恰恰可以给你提供这样一个实际的例子。

什么是实时系统的特点?
实时系统就是时间是最关键因素的系统。如果一个系统不理会它的时间需求,将可能导致生命危险,例如造成飞机失事。简单来说,对于这样的系统,对于时间需求处理的失败将可能导致产品的损害和上百万美元的损失。

实时系统也有最小限度的用户交互,对于这类系统怎么样定义它的用例是一件值得考虑的事情。实时系统通常都是高度算法化的,用例可能并不是最好的描述算法的文档方法。典型地,一个用例将引用在其它地方描述的算法。

如果你的系统具有有效的外部可视行为,那么用例能够极大地帮助你文档化你的系统需求。下面我描述的系统就有大量外部可视行为。在IT系统中应用用例的规则在这里仍然适用。

产品传送系统(PT)项目
用户是一个全球最大的铁矿生产商,它在澳大利亚西北的Headland港拥有一套铁矿传送工厂。铁矿从传送带网络传送到火车车厢,然后分类,称重,保存在仓库。最后铁矿从仓库中运出,再通过传送带运送到船上。

这个系统中每个东西都很大。有的传送带有7公里长,需要15分钟才能启动。运行传送带网络需要一个复杂的控制系统。对控制系统的一个需求是减少运行系统需要的人数。另一个需求是降低对运行系统的人的培训需求。图1是工厂的航拍图。


图1: Hedland港工厂的航拍图

在左上角有一个船泊位。你可以看到有铁路线从右边连过去。卸货场在接近于中间的垂直线附近。在这个线的顶端是粉碎机。你可以在北面和南面的院子里看到仓库。传送带运行在卸货场和粉碎机,以及粉碎机和仓库之间。 你可以看到运输机从仓库装上铁矿,把它运到船上。运输机的容量非常大。在这个系统中,传送带可以在一小时内传送10000吨的铁矿!

有一些能够增加系统复杂度的需求非常值得关注。控制系统在避免溢出的情况下要达到最大的生产能力。出口容量在2004年从每年67M(百万)吨增加到81M吨,到2011年将扩大到90M吨。

基本概念:作业(route)
一个作业(route)由一系列传送带、定位和反馈设备组成,这些设备被选择和调配以便把铁矿从源位置运送到目标位置。作业可以是单独的,也可以共享设备以便允许把铁矿分开运输,从一个源位置运送到多个目标位置,或者组合运输,从多个源位置运送到一个目标位置。这些作业由操作员创建、维护和删除。一旦操作员启动作业,系统就开始工作,按照正确的顺序启动每个工作,以及处理一些异常状态例如有不合适的产品已经在传送带上。

为了达到生产力的需求,控制系统采用激进的启动和错误处理策略。尽管已经存在的系统在一个时间点一个作业只能启动一条传送带,当它达到最大速度后,再启动下一条,依此类推。 新的控制系统在一个作业中将同时启动所有的传送带--注意我们的传送带的长度不同,启动时间和容量也不同。在出现问题时,新的系统试图尽可能关闭最少数量的设备以避免溢出,因为启动一个长的传送带需要15分钟。无论什么可能情况,设备都将保持运行,直到铁矿有可能溢出为止。系统需要同时运行作业,以便矿石能够传送到多个目标位置或者把矿石从多个源位置组合到一个位置。

设备失败的处理是全自动的。当一个传送带一小时可以传送超过10000吨矿石时,你可以想象出现问题时不能很快停下传送带的后果。这个结果并不是你可以用独轮车能够清理干净的!你将不得不使用一些重型设备来清理那些溢出的矿石,而且需要花费很长的时间。甚至有更坏的结果,比如一个错误导致矿石已经倒进了船的甲板上。

PT系统的用例模型
本文的工作基于IBM(r) Rational Unified Process(r) (RUP(r))和一本名为“用例建模”的书,这本书由Kurt Bittner和Ian Spence合著。用例是一个可供选择的写需求的方法。在过去,我们基于一个词"shall"来书写这些声明。在几年前我曾经在一个国防项目工作,它包括22卷需求文档,没有人能够真正全部理解它们。确认所有的需求描述是正确的和一致的是一件非常困难的事情。用例可以帮助我们处理这类问题。用例把需求放在系统提供给用户和有关人员的上下文中。 按照顺序描述每个用例以消除上下文的冗余,这些冗余在用传统的基于"shall"的需求描述方法中是必然包括的。

这里仅仅描述用例模型的一些特点,因为全部模型太大,也太复杂。用例的规范非常长,也非常细致。

识别角色(actor)
在整个操作开始前主要工作集中在识别角色是非常重要的,因为它可以确定系统的边界,清楚地指示哪些是在系统范围内,哪些在系统范围外。我们不想开发那些我们没有必要付钱的东西。在PT系统中,运输机是一个我们需要交互的系统。这里我们只需要知道运输机干什么工作,并不需要控制它如何工作,我们也不需要修改它。既然我们不控制运输机,它就在我们的系统范围之外,作为一个角色模型存在。我们有Ship-Hatch Loaders, Stackers, Sampling Stations 和Dust Control Systems - 所有这些外部系统都作为角色模型。图2显示部分这个系统的角色。


图 2: PT系统角色

在图2中,角色使用UML 包分组。在左边是角色包。最重要的一个在左上角。CRO 就是Control Room Operator,中央控制室操作员。其它重要的还有在左下角的RSMT(Root System Maintenance Technician,根系统维护技术员)。

其它的包包括所有不同的设备角色。一些用例提到我们管理的通用设备,另一些将提到特定的不同类型的设备。

在图的下方有一些与我们交互的外部设备。左边的一个是PMAC,一个已经存在的系统用来处理我们的用户界面。如果它仅仅是与我们的系统相关的用户界面,那么我们不需要把它看作角色,因为实际上角色是CRO。但是它实际上是我们相关的角色,因为我们给它传递信息,并且从它那里收集信息。

电力负荷管理系统Power Load Management System (PLMS)是值得关注的。在Headland港,港口工厂有自己的电站。当你启动7KM长的传送带,并在上面运行数千吨的矿石是,它需要大量的电力供应。因此,传送带的启动实际上是错开的,以便降低电站的负荷。只要你想启动传送带,PT系统都将询问电站并得到电站的许可。

确定用例
用例是一种简单的可选择的定义需求的方法。首先,让我们再次回顾一下用例的定义:

用例定义一个由系统完成的操作序列,它给操作者和相关人员提供可观察到的有价值的结果

在识别IT系统的用例时,牢记“可观察到的有价值的结果”是非常重要的,这一点在这里仍然适用。我们将看到系统提供给操作者和相关人员的值。在我们的例子中,中央控制室操作员是一个角色,但是整个公司实际上都可以看到系统把矿石从一个地方移动到另外的地方。“由系统完成的操作序列”声明了在用例中系统怎么做的描述。这些每个都是我们系统的需求。最后,用例是一个完整的有意义的事件流。把“完整的有意义的事件流”和“可观察到的有价值的结果”这两个概念组合起来可以帮助避免识别出不完整的功能片断并把它称为用例。

用例和角色在用例图中以图形化的方式描述。图3显示了 PT系统的顶层用例图。你很快就能看到 PT系统的用例分为4组:Operation, Administration, Configuration 和System Startup。


图 3: 顶层用例图

PT系统中用例相关的操作在图4中显示:


图 4: PT系统的用例

PT的主要用例是“传送产品”。正如这个用例的名字那样,你可以看到在这里系统提供给用户和有关人员的价值是把产品从一个地方运送到另一个地方的能力。这个软件也能够用来运送其它物品,不仅仅是铁矿。例如它能够用来在制药厂传送药片。传送产品是一个很大的用例,但它抓住了最根本的因素,即我们的系统存在的原因,即给我们的操作者和相关人员提供的价值。

在图4中我们看到与电源管理系统(PLMS)的交互,请求启动设备,我们也看到了用例与我们控制的设备的交互。我们可以显示每个设备角色,但那样的话,图就显得太混乱了。

在图4中,从CRO到传送产品的箭头,表示这个操作员发起或者启动这个用例。用例到设备和PLMS操作者的箭头表示这个用例或者系统与设备和PLMS交谈。从船舱装载系统(SHLS)的箭头表示SHLS发起与系统的交互--特别地,SHLS可以请求铁矿流暂时中断以便装载机移动到另一个舱室。

有关管理、配置和系统启动的用例在图5中描述。


图 5: 有关管理、配置和启动的用例

在这里我们可以做的一件事情是在对系统的操作影响最小的情况下更新系统软件。因此我们有"Perform Software Upgrade" 用例。我们也有"Start System" 用例 -这个用例经常被忘掉。系统有人身安全优先的规则,Start System 用例包括在系统启动时进行的所有安全检查的规格说明。你一定不想在服务人员在传送带上工作时偶然地启动它!

Route System Maintenance Technologist (RSMT)是一个负责创建作业或者预定义作业的人,预定义的作业稍后由CRO使用。我们可以在系统中添加新的设备,定义新设备的种类以及定义系统传送产品的特点。港口工厂处理来自不同矿山的不同类型的铁矿,它们有不同的颗粒大小、不同的矿石成分等级。我们要非常小心的一件事情是避免把错误的矿石装到船上去。

关于用例图这里要警告一下:用例图只是冰山的一角。直到你开始写用例规格之前不要去重新分解、重组和调整用例模型。用例的规格大约95%来自用例模型。用例图仅仅给你一个模型的总体、全面的概要描述。

正如我前面提到的,用例描述“事件流”。图6显示不同种类的事件流和它们是如何在用例规格中描述的。


图 6:主事件流和分支流

从顶部到底端底蓝色粗线条表示主事件流(Basic Flow)。它表示每件事情都按照正确的方式发生。有很多种分支流(Alternate Flow)。描述处理设备故障的分支是一个很好的分支流的例子。另一个例子是描述操作员取消了前面请求的动作。

这里一个非常重要的结构是子事件流(Subflow)。子事件流就象子程序调用。我们试图保持主事件流简单易读,没有子事件流是很难做到这一点的。例如,我们使用子事件流来描述我们启动或者安放设备位置时发生的细节。这些过程每个都相当复杂,如果我们都在主事件流上描述,那么它将变得很长并且很难理解。

图7显示一个主事件流的片断:


图 7: 描述启动一个作业的主事件流示例

在第二步,系统检查作业是否有效。如果系统确认作业无效时会发生什么?这在分支流中描述。在用例规格中我们使用脚注来指示分支流,脚注文本包括指向相关文档章节的交叉引用。这将减少描述事件流的混乱,使得它更容易读。在这里我使用 "§" 符号指示存在一个分支流。我也用高亮的红色表示这是项目术语表--一份很重要的文档--中的定义。

在第3步,系统检查选择的作业的启动策略。"Starting/Positioning Stragegy 2.1"表示交错启动,它需要在启动作业前所有的设备都到位。这个启动策略在分支流中描述。最后系统向PLMS询问是否允许启动作业。如果不允许时发生的事情也在分支流中描述。

在图8中,我们将看到分支流和子事件流的例子。


图 8: 分支流和子事件流示例

分支流定义当基于某种原因设备不到位,因而这个作业不能启动时应该发生什么。系统给操作者发一个消息,建议在我们实际启动作业前把设备移动到相应的位置。在这个点上,用例就结束了。

子事件流的例子提供我们实际上如何启动作业的更详细的信息。系统检查全部作业设备是否畅通(传送带上没有其它物品)。如果正常,系统同时移动所有需要定位的设备到相应的位置,包括作业自己需要的设备和要与其它作业共享的设备,然后我们交错启动所有的设备以避免电力过载。当全部设备都运行起来后,我们告诉操作员作业已经启动,然后用例结束。第16步的 "a", "b" 和 "c" 也涉及了子事件流。

特殊需求
有很多需求,特别在实时系统中,不能归结到用例的事件流中。通常它们是非功能需求如性能、可用性等等。那些与给定用例相关的特殊需求可以归结到与用例规格定义相关联的"特殊需求"一节中。有时为了方便,甚至功能需求也可以放到这一节中。图9即为一个示例。


图 9: 特殊需求

第一个例子可以作为一个分支流,但是为了使主事件流基于阅读,我们可以简单地说:“系统检查是否有特殊需求的xyz节中规定的不能启动的状况发生”。在这个特定用例中,如果我们在码头传送带上还有东西,而传送带由于完成了往甲板上卸矿石已经停止工作,那么我们将给操作员发出一个警告。

第二个例子是一个算法的详细描述,这个算法用来调整传送带的传输量和传送速度。这里,主事件流可以简单地描述:“传输量和传送速度按照特殊需求中的uvw节进行调整。”

结构化用例
用例的结构化是一个高级课题。你也可以不使用这项技术来完成一个系统的需求文档。用例的结构化可以让你避免冗余信息,保证用例文本只存在一个单独的可维护的位置。关于结构化的重要的一点是,你必须避免让用例模型和用例变得难以理解。

结构化技术在图10中显示。


图 10: 结构化用例

在图的左边,我们有一个用例,它有一个主事件流,一个分支流和一个子事件流。子事件流和分支流都可以作为单独的用例分出来。分支流变成一个新的用例,扩展了原始的用例。子事件流也变成一个新的用例,它包含在原始的用例中。一般来说,包含关系仅仅用于一个分支流共用于多个用例的情况。这就是我们如何确保仅仅写一个分支流,仅仅有一个维护地点的方法。通常,当需要在已经存在的用例上增加新功能而且你又不想修改原始用例时,可以创建一个扩展流

把这些结构化技术用到我们的产品传送系统后,改变后的用例如图11所示:


图 11: 用例模型的修改

这些新的用例称为“抽象用例”,因为它们没有操作者,你不能直接调用它们。如果我把这些抽象用例隐藏起来,你仍然可以相当清楚地看到系统的主要功能。

一系列的错误处理用例都以这样的声明开始:“当系统检测到设备故障”或者“当系统检测到超载”等。然后用例继续描述系统在这些情况的响应。

在图11的顶部,我提炼出了两个抽象用例:一个是启动作业,另一个是停止作业。从技术角度来说,我在这里破坏了规则:包含用例应当被多于一个的用例共享。我之所以这么做是因为,如果我在“传送产品”用例中写出所有的内容,文档将超过300页。把它分开可以让很多人同时在系统的不同部分描述需求。

补充需求
我们有很多补充的需求,它们不适合放在任何用例中。因为特定的需求与具体的用例相关,这些都是典型的非功能性需求。在你在用例中的需求和补充的需求之间存在一个平衡。当你进行实时系统工作时,你可能发现会比你进行IT系统开发时有更多的补充需求。这是由于在实时系统中有更多的有时间要求的需求,它们都在补充需求中描述。

下面是放在补充需求中的非功能需求的例子:

  • The PT System shall, upon notification of fault requiring upstream interlocks to be set to "False", set Upstream interlocks to False within 0.60 second.
  • The PT System annual Availability (refer Glossary) shall exceed 99.954%.
  • The PT System Maintenance Procedures shall comply with the Client's maintenance strategy for real-time PLC control systems.
  • The PT System shall use the existing PMAC System for the CRO, PCO, and viewer interface.
  • The portion of the PT System controlling route interlocking and directly connected to route equipment and interconnected systems shall be PLC based and, in the event of failure of higher level control within the PT System, shall be capable of safely operating any route currently starting, running, or stopping.

功能需求也可以包括在补充需求中。当有些功能用用例写还不如用补充需求描述时,可以把这些需求放在补充需求中,而不用写一个完整地用例规格定义。下面是一些这样的例子:

  • The System shall advise the Operator if the sum of the source feed setpoints for a route is more than 10% less than the minimum capacity for the route.
  • If a feed source has been enabled for a route with a cleanup destination, the System shall:
    • Raise an alarm
    • Re-raise the alarm every 10 minutes while the condition persists
  • If during a ship hatch change, burden is on the wharf conveyor at or within the gross stopping distance of the shiploader, the System shall:
    • Raise an alarm
    • Issue a message to the operator
    • Stop the wharf conveyor
    • Issue a request to the SHLS to stop the shiploader boom conveyor

用例的益处
用例有以下益处:

  • 给出需求的上下文,清楚显示为什么需要系统。
  • 把需求按照时间顺序排列,使需求比传统方式更加容易读,更容易理解。
  • 更容易得到客户的认同,因为他们能更容易地阅读和理解。
  • 可以用作项目计划、分析、设计、测试和文档的基础。

另一方面,关于用例有很多不正确的观点。第一个是,象当前多种书籍和文章中指出的,传统的书写需求的方法能够更严谨和精确。实际上并不是这样的,我们在这个项目中也能写出非常严谨而精确的用例。

另一个看法是任何没有经过专业培训的人都可以写用例。写传统需求文档的原则在这里仍然适用。你需要从外部视角来看系统做什么。你不需要写出系统是怎样做的。象传统的需求一样,你写的用例需求也应该是可测试的。新手经常继续使用老的诸如功能分解的习惯。有开发背景的人经常倾向于从系统内部而不是用外部视角描述。

用例贯穿整个项目
我前面提到,用例在整个项目中都有用。

项目计划
在这里我们没有足够的空间讨论分配用例迭代的准则。只能简单说,这些准则基于RUP的风险评估。下面是分配的迭代:

迭代1 启动、停止和处理一个独立作业上的一个设备故障。
维护设备和作业。
迭代2 启动、停止和处理覆盖作业和作业组上的设备故障。
维护计划和约束。
维护产品。
迭代3 创建产品产品差距。
改变作业所有权。
直接命令设备
删除作业。
修改负载数据。

用例分析
用例分析是一个用来识别对象、类、类属性和类方法的过程。 由于这个项目的实现方法是非面向对象的,我们使用一个映射把面向对象的分析模型转换到过程设计模型。图12显示对启动作业用例进行分析的部分结果:


图 12: 从启动作业用例导出的序列图

测试计划
我极力推荐下面的一篇文章:June 2001 Rational Edge,Jim Heumann,"Generating Test Cases from Use Cases"。在我们的PT系统中使用这种方法识别测试用例。图13显示启动作业用例的迭代2的测试用例。注意在测试用例和用例需求之间的连接。它可以帮助保证需求都被测试,以及测试用例随着需求变更而更新。


图 13: 从启动作业用例导出的测试用例

经验学习
1. 集中在外部可视的行为上。这一点怎么强调都不过分,这就是我为什么这里再次重复的原因。在这个项目中,我经常告诉那些需求分析人员,让他们想象它们是飞翔在港口工厂的用户,让他们问自己他们想要看见发生的事情。如果你写的描述一些事情的需求不可见,那么你将不能对它进行测试。

2. 不要引入设计。这与经验1相关。下面是一个例子

每个作业都拥有一个使能信号,这个信号来自于作业的运送源。使能信号在操作间内部驱动作业运送来源的物理信号。(用例在这里描述了系统内部信号。)

有很多原因使你不应该这么做。首先,它使得测试很困难,因为描述的行为在外部不可见。第二,如果你把设计信息放到用例中,你就需要照着这些信息实现,因为那是需求,因此你不仅必须照着它来进行演示说明,而且它也限制你的设计人员的实现方式。

3. 不要为内部监控过程写用例。在这个系统中,由于是实时系统,有很多在系统内部运行的过程用来检查、监控和轮询其它设备。你不应该在你的用例中描述这些,因为它是设计,它仅仅是一种可能的实现方式。

除非过程与系统的目标相关,例如在建筑监控中的“监控建筑”用例中,你应该把它们写在补充需求规格中。例如

  • 系统应该每200毫秒或者更短的时间内检查所有设备的状态。
  • 系统应该在设备失败的200毫秒内开始响应。
  • 系统应该保证产品混合率在设定值的+/-5%范围内。

在用例的分支流或者扩展用例中,都可以描述如果系统检测到问题时发生什么事情。这些需求可以使你设计诸如轮询设备或者设备产生中断等的过程,然是它们属于设计应该考虑的事情,不属于需求的范围。

4. 不要太早"冻结"用例。在项目中太早冻结用例将会导致很多问题,因为在你和你的客户更好地理解需求时,你可以找到机会重新组织用例,或者你将引出新的用例,或者你要修改现有的用例。需求变更会发生,但这需要在一个迭代过程的管理和限制下进行,以便进行适当的影响评估,以及合适的预算和计划日程的变更。如果你使用迭代过程,你至少有在后面的迭代中改正的机会。

5. 不要过于追求完美的用例模型而超出项目计划日程。这是经验4的必然结论。你从来都不可能得到一个完美的用例模型。在某个点上,你将不得不停下修补完善用例模型的工作,开始实际的系统开发工作。即使用例模型并不完美,你也可能开发出符合用户需求的系统。最后,图14显示PT用例模型的实际样子。


图 14: 不完善的用例模型

你将看到选择作业、启动作业和停止作业是主要的用例。这里并没有围绕着传送产品用例把所有其它用例联系在一起,我们不得不在附加需求中描述其它的需求。来自操作员和相关者的选择、启动和停止作业的需求并没有进行评估。用例模型不完善的主要原因在于,需求必须在某个点冻结,以便开发者不会频繁地面对不确定的开发目标,系统应当被实际开发测试和部署。

6. 不要害怕收集细节信息。用例假定容易被每个人阅读,但是这并不意味着你在大街上随便找一个人,给他一个描述复杂系统的用例,就能够期望他能够理解。

需要问你自己的问题是:你的客户需要多大程度的细节?你想给你的开发者多大的范围?如果你在用例中没有给出很多细节,你就让你的开发者自行作出决定。你想这样做吗?你的开发者可能有丰富的经验而你也很高兴这么做。另一方面,如果你正在进行实际的开发,没有充分的细节不一定是可以接受的。你需要给开发者和测试者描述以足够的细节信息,说明什么对于客户是重要的。

如果有大量的细节信息,可以考虑把它们放到特殊需求或者附加需求中,或者用活动框图(Activity Diagram)来补充用例。(参见经验9)

7. 不要引入用例间的直接交互。用例不能相互交互。它们可以相互影响,但是只能通过操作系统状态来影响。

8. 不要把有关解决资源争夺问题的架构决策放到用例中。这一点与第2课有关,例如,当操作员使能一个作业的来源时,同时第二个作业共享同一个来源因此得到一个来源不可用的错误时,会发生什么情况?

这些状况应该在不同的用例中描述。在每个用例,你只需要简单描述在给定环境下你想要系统做什么。在这个例子中,系统内部如何工作,什么信号发到设备,这些都属于架构或者设计问题。系统架构需要解决这些问题,诸如保证信号在非常接近情况下接收、不同顺序接受或者高频率地接收。

9. 使用活动框图以便使得复杂的用例能够易于理解。活动框图可以作为用例的规格创建。

总结
在PT系统中使用用例给项目带来很大的价值。只要有外部可见的系统行为,就应该使用用例来定义需求规格。

对任何系统来说,在确定用例时,要集中在系统的目标上以及系统给相关人员提供的价值。在写用例时要维护一个系统的外部视图。

对于实时系统,附加需求在整个系统需求中占有更大的百分比。

原文转自:http://www.ltesting.net