极限编程概述

极限编程(Extreme Programming,简称XP)是目前讨论最多、实践最多、争议也是最多的一种敏捷开发方法。XP是一套能够快速开发高质量软件所需的价值观、原则和活动的集合,使软件能以尽可能快的速度开发出来并向客户提供最高效益。

XP是很多Java开源软件使用的软件开发方法,也是许多实干派大师倍加推崇的一种方法,同时也有很多使用传统软件开发方法的大师和大公司倍加批评的一种软件开发方法。可以说,认同者,对它是相见恨晚!批评者,认为它是离经叛道,胡说八道!接触XP之前,我也曾认为它的思想和实践都是胡扯。但是,经过深入学习之后,才发现XP是句句箴言,十分有道理。而之前的我,却是白走了很多冤枉路!我相信,所有的软件开发都应该使用极限编程来实施!

极限编程的力量源泉
XP的极限就在于它将12个众所周知的软件开发“最佳实践”都发挥到了极限“10分”。

下面是Kent Beck提出的12个最佳实践:

1,          计划游戏—通过结合使用业务优先级和技术评估来快速确定下一个版本的范围。当计划赶不上实际变化时就应更新计划。

2,          小版本—将一个简单系统迅速投入生产,然后以很短的周期发布新版本。

3,          隐喻—用有关整个系统如何运行的简单、众所周知的故事来指导所有的开发。

4,          简单设计—任何时候都应当将系统设计的尽可能简单。不必要的复杂性一旦被发现就马上去掉。

5,          测试—程序员不断地编写单元测试,在这些测试能够准确无误地运行的情况下,开发才可以继续。客户编写测试来证明各功能已经完成。

6,          重构—程序员重新构造系统(而不更改其行为)以去除重复、改善沟通、简化或提高柔性。

7,          结对编程—所有的生产代码都是由两个程序员在通一台机器上编写的。

8,          集体所有权—任何人在任何时候都可以在系统中的任何位置更改任何代码。

9,          持续集成—每天多次集成和生成系统,每次都完成一项任务。

10,     每周工作40小时—一般情况下,一周工作不超过40小时。不要连续两个星期都加班。

11,     现场客户—在团队中加入一位真正的、起作用的用户,他将全职负责回答问题。

12,     编码标准—程序员依照强调通过代码沟通的规则来编写所有代码。[1]

XP依靠这些简单得可笑的最佳实践(就是那些几十年前常常被视为不切实际或天真而遭摒弃的实践)之间的协作,高效地完成软件开发的重任。“实践互相支持。一种实践的弱点可以由其他实践的优点来弥补。”[2]

Java开源社区的软件成指数形式增长,已经超过了C和C++的开源软件项目的数量,位居开源领域第一位。可以说,这其中很大的功劳是因为诞生于Java社区的极限编程,在Java社区获得了广泛的认同,众多的开源项目应用极限编程的实践,取得了巨大的成功。Rod Johnson主持开发的著名开源项目Spring框架,就是使用极限编程开发的。其他许多著名的Java开源软件项目也都是使用极限编程开发的。

极限编程的真谛
但是,尽管许多采用极限编程的软件项目取得了成功,但是仍有为数众多的项目最后失败了。失败者往往认为,是XP本身不切实际造成了他们的失败,或者是认为XP不能够适用于他们项目的特殊情况。

我认为,事实并非如此,XP是迄今为止最完美的一种软件开发方法,也是一种普遍适用的软件开发方法。失败的原因,是因为项目实施者没有能够真正的领悟XP的真谛。

极限编程的假设
极限编程的假设:平滑成本曲线—变化的成本不会随时间的推移而急剧上升。极限编程的12个最佳实践中,很多实践都是在几十年前结构化编程和瀑布模型时代被枪毙掉的“最差实践”。为什么到了极限编程这里,它们就能够起死回生呢?这就是因为今天使用了适当的软件开发技术后,成本曲线已经改变,已经变得平滑,而非“变化的成本随时间的推移而以指数方式上升”[3]。

极限编程的技术基石是OO面向对象技术,而且是高超的面向对象技术。不是说,你使用面向对象的编程语言开发软件,开发出的软件就是面向对象的。Kent Beck,Ward Cunningham,Martin Fowler,Robert C. Martin,Rod Johnson等XP的领军人物无一不是面向对象软件开发的顶尖高手。面向对象技术为软件模块高度解耦提供了可能,只有这样的软件,“平滑成本曲线”这一假设才能够成立,应用XP才能够取得辉煌的胜利。

记得有一位大师曾经说过“教会一个人使用C++的唯一途径,就是使其成为高手”。对于极限编程来说也是如此,教会一个人使用极限编程的唯一途径,就是使其成为软件开发的高手!

Kent Beck等极限编程的开拓者提出了12个软件开发的最佳实践。这些实践涵盖了软件开发的各个阶段,可以说为极限编程提出了简单有效的实施方式。但是,不能不说,这12个最佳实践过于宽泛。XP实践者在实际的项目开发过程中,常常不知道该怎样实施这些实践。为此,我在本论文中将深入阐述一些上述12个经典最佳实践中的几个,也会另外提出一些更有实际意义的最佳实践和原则。

Kent Beck说:“XP也反映了我不担心的事情:

1,          编码。

2,          改变想法。

3,          对未来一无所知而能不断前进。

4,          依赖别人。

5,          更改运行中的系统地分析和设计。

6,          编写测试。

”[8]

Kent Beck并不害怕这些,但是很多程序员却害怕这些。正是这些能力上的不足,让很多XP项目夭折了。本文就是希望对帮助你克服这些恐惧有所帮助。

极限编程的真谛
    一、极限编程的哲学思想是实证主义的哲学思想:“任何不能度量的事物都是不存在的”[4]。极限编程反对传统开发方法重视过程和文档的做法,主张快速的开发、构建、测试和部署软件。认为,一切决策都应该基于软件的实际运行结果,而不是无端的猜测。经常“问问电脑”[5expert one-on-one J2EE Development without EJB 中文版第9页],而不是基于一种毫无证据的信念,这是XP人的基本编程原则。

XP人首先就需要是一个皈依实证主义哲学的信徒。

二、极限编程的假设:平滑成本曲线—变化的成本不会随时间的推移而急剧上升。这就是说,采用极限编程方法开发的软件,必须容易修改。随着时间的推移,修改软件的成本不会急剧上升。不论这个修改是对于单个类的小修小改,还是对于业务功能的修改,甚至是修改软件的架构,其成本都不应该急剧上升。

这就需要,首先,软件在概念上是简单清晰、易于理解的。“简单设计”是XP方法的核心思想和第一要求!12个经典的最佳实践中,可以说,每一个都是在力求开发出的软件简单清晰、易于理解。XP不欢迎所谓的高手,不欢迎高效而复杂的设计。XP只需要简单清晰、而又满足最基本的性能要求的设计。最佳设计,就是能运行所有测试用例的最简单的设计。

XP的软件,其简单性,不仅仅反映在软件概念上。而且,其整个的开发流程也是简单、灵活、可迭代的。倾听、设计、测试、编码就是一个完整的软件开发的过程。这个开发过程的所有工作,都由一个或者一对结对的程序员完成。管理人员只是分发任务和跟踪项目进展情况,并不干涉程序员们怎样完成他们的工作。而在传统的重视过程的开发方法中,软件开发中的需求调研、分析、设计、实现、测试都是处于不同的阶段,由不同的人负责的。程序员是一台大型机器的“螺丝钉”,程序员想要做出任何改变都是困难重重的。而在XP项目中,程序员是软件开发的主人,可以自由的在倾听、设计、测试、编码这些过程中切换。

XP的开发流程是简单、灵活的。打个比方,传统的重视过程、面向文档的开发方法,比如经典的瀑布模型,最新的RUP(Rational Unified Process统一软件开发过程)都是计划经济体制,有着严格的计划和分工。而XP等敏捷开发方法,程序员是市场经济的主体,有着很强的自主权力。管理人员仅仅是为程序员开发软件提供帮助和度量程序员的成果。

XP的软件,其简单性,还反映在软件开发的具体成果--软件中。

XP开发的软件,逻辑清晰,结构简单,代码很少重复(一旦发现重复的代码,就重构)。从结构上来说,是高耦合、低内聚的。要做到这一点,XP的软件就一定需要是优良的面向对象软件。

OO(面向对象)技术已经诞生几十年了,早已飞入了平常百姓家,今天主流的编程语言都是面向对象的编程语言。可以说,基本上所有的程序员都认为自己已经掌握了面向对象技术。但是,事实上,我看,最多只有10%的程序员掌握了OO技术,要说到精通,更是凤毛麟角。而且,OO技术还有很多尚未探索明白的地方。OO技术可以说是IT界中最受到忽视的一门最关键的技术!今天,有不少程序员正在反思OO技术,这是好现象。Robert C. Martin在其经典名著《敏捷软件开发:原则、模式与实践》中煞费苦心的强调了OO技术的重要性,Rod Johnson也强调了OO的无比重要性。1994年GoF的划时代名著《设计模式可复用面向对象软件的基础》一书中提出了“模式”这个高级的OO技术,也提出了一些高级OO的原则和理念。此后,一大批“模式”诞生了。

使用面向对象技术开发软件,看似容易,实际上要想真正创建出高内聚、低耦合的最佳软件却绝不容易!真正优秀的面向对象软件,其为更改软件提供了最大的灵活性。我们可以简单的替换掉软件的某些模块来实现随需应变的更改软件!

上述3点做得越好,成本曲线就越平滑。更改软件的成本就越低。而越是这样,我们就越可以放心地不提前实现未来的需求。我们的软件也就越是简单!

三、“源代码就是设计”[6Jack Reeves,论文“什么是设计”],这是极限编程和其他敏捷开发方法的基本观点!而传统的重视过程、面向文档的开发方法,认为“设计文档才是设计,源代码就是实现”。它们把“编码”称作实现,是实现“设计文档”的活动。所以,传统开发方法中,更加重视的是“设计文档”。它们认为,“设计文档”才是软件思想的体现,才是真正的软件。而敏捷开发方法,都可以说是“轻视过程、重视人的主动性、面向源代码”的开发方法。

“源代码就是设计”,因为:

1,实际的软件运行于计算机之中。它是存储在某种磁介质中的0和1的序列。它不是使用C++语言(或者其他任何编程语言)编写的程序。
2,程序清单是代表软件设计的文档。实际上把软件设计构建出来的是编译器和连接器。
3,构建实际软件设计的廉价程度是令人难以置信的,并且它始终随着计算机速度的加快而变得更加廉价。
4,设计实际软件的昂贵程度是令人难以置信的,之所以如此,是因为软件的复杂性是令人难以置信的,并且软件项目的几乎所有步骤都是设计过程的一部分。
5,编程是一种设计活动——好的软件设计过程认可这一点,并且在编码显得有意义时,就会毫不犹豫的去编码。
6,编码要比我们所认为的更频繁地显现出它的意义。通常,在代码中表现设计的过程会揭示出一些疏漏以及额外的设计需要。这发生的越早,设计就会越好。
7,因为软件构建起来非常廉价,所以正规的工程验证方法在实际的软件开发中没有多大用处。仅仅建造设计并测试它要比试图去证明它更简单、更廉价。
8,测试和调试是设计活动——对于软件来说,它们就相当于其他工程学科中的设计验证和改进过程。好的软件设计过程认可这一点,并且不会试图去减少这些步骤。
9,还有一些其他的设计活动——称它们为高层设计、模块设计、结构设计、构架设计或者诸如此类的东西。好的软件设计过程认可这一点,并且慎重地包含这些步骤。
10,所有的设计活动都是相互影响的。好的软件设计过程认可这一点,并且当不同的设计步骤显示出有必要时,它会允许设计改变,有时甚至是根本上的改变,
11,许多不同的软件设计符号可能是有用的——它们可以作为辅助文档以及工具来帮助简化设计过程。它们不是软件设计。
12,软件开发仍然还是一门工艺,而不是一个工程学科。主要是因为缺乏验证和改善设计的关键过程中所需的严格性。
13, 最后,软件开发的真正进步依赖于编程技术的进步,而这又意味着编程语言的进步。C++就是这样的一个进步。它已经取得了爆炸式的流行,因为它是一门直接支持更好的软件设计的主流编程语言。
14,C++在正确的方向上迈出了一步,但是还需要更大的进步。[7]

Java是比C++更好的面向对象的主流编程语言。它也比C++更好的支持软件设计。Java的接口可以更加直接的支持软件设计。实际上,我们可以直接使用Java的接口和类来表示设计和编码这两层概念。

因为“源代码就是设计”,所以在极限编程中,源代码就是一切活动的中心。XP的一切活动都是为了快速的生成源代码。XP项目中,程序员在拥有了编码所需的全部知识后就开始编码,无需像在传统开发方法中那样,等待需求、分析、设计文档统统完成后才能够编程。也无须花大力气维护需求、分析、设计文档。只需要维护好一份可以运行的源代码即可。所有的辅助文档,都依靠工具从源代码中获取,或者是手工从源代码中整理出来。

极限编程的最佳实践
理解了极限编程的真谛之后,接下来,我们以软件开发的流程为主线,逐个讨论实用而具体的最佳实践。

首先,是计划阶段
这个阶段,主要就是XP的经典实践“计划游戏”。在这个阶段,用户在程序员的帮助下,编写“故事卡”。

计划游戏中的两个玩家是开发方和业务方。开发方由所有负责实现系统的人共同组成。业务方由所有负责决定系统功能的人组成。

一、     编写一个故事—业务方编写故事来描述系统要做的事。故事写在索引卡上,有一个名称和一小段说明故事的目的的文字。这类似于UML的用例。我们也可以使用UML的用例图来帮助描述“故事”。

二、     估算时间—开发方要估算实现故事需要多长时间。如果开发方无法对故事进行估算,可以要求业务方将其解释清楚或分解。评估故事最简单的方法就是问自己:“如果这个故事由我来实现,在没有任何干扰或会议的情况下,我会需要多长时间呢?”这叫做“理想工程时间”。一般,在实际开发中,每个人的正常速度是每个工作日实际完成1/3-1/2个理想工程时间。请注意,XP中,软件质量是最基本的要求。如果你每天完成了超过1/2个理想工作日的工作量,那么请注意你的代码是不是足够健壮!

Bruce Eckel说,他总是把自己的估算时间*210%作为自己最重的工作计划。因为,其中100%的时间用来完成工作。另外100%的时间用来使工作完成得更加优美。10%的时间用来润饰代码,添加注释,使之更加清晰,易于阅读。

三、     分割时间—如果开发方不能评估整个故事,或者如果开发方认识到故事中的某个部分比其他部分更重要,业务方可以让开发方把故事分割为两个或多个故事。

“当你能够度量你所说的,并且能够用数字去表达它时,就表示你了解了它;若你不能度量它,不能用数字去表达它,那么说明你的知识是匮乏的、不能令人满意的。”—凯尔文勋爵(英国物理学家)语。开发人员对用户故事的估算,本身就需要开发人员对怎样用软件来解决用户故事有一定的理解。如果,开发人员不能够有效的评估用户故事,那么他们就可以在这个阶段进行一些技术上的探索。比如,设计和编写一些解决方案,对这些代码进行测试,从而帮助评估技术风险和工作量。

通常,分解“用户故事”,得到更深入的用户故事的细节,将有助于更加精确的估算时间。但是,在计划阶段,并不需要估算时间十分精确,只需要大致准确就可以了。如果在这个阶段,开发人员深入技术细节,那么就违背了XP“尽可能推迟决策”的精神,而更像传统软件开发方法“预先制定详细计划、分析、设计”的作风了!

然后,是开发阶段
开发阶段是整个软件开发的重头戏,包括大量的步骤。

其中,首先是“发布计划”阶段。在计划阶段,用户得到了其提出的所有的用户故事及其对应的估算时间。这样,用户就可以根据用户故事的轻重缓急,自由的决定一次小版本内需要实现的用户故事。

一、     小版本。XP主张,将一个简单系统迅速投入生产,然后以很短的周期发布新版本。这样,1,系统可以尽早的投入生产,为客户提供效益。也可以使客户在决定中止项目时,不使项目的投资浪费掉。2,投入生产的系统,比传统的“快速软件原型”更能够揭示系统的工作,可以尽早的发现很多问题,让业务人员和开发人员及早的解决。如果,直到整个系统已经完成了90%,才发现采用的软件架构根本无法实现系统的性能要求,那么一切都已经太晚了。3,更重要的是,一般只有当业务人员真正看到运行中的系统时,业务人员才会知道他们真正要的是什么!尽快投产的系统,使用户有更多的时间更改用户故事。

20-80原则是一个非常普遍的原则,各个学科都有这样的原则。软件程序员习惯于使用的20-80原则是:80%的效益来自20%的工作。而XP程序员用自己的方式利用这条原则:将最有价值的20%的功能率先投入生产,依靠20-80原则来延期优化。将80%锦上添花的功能留在后面的版本中实现。“小版本”就是这一XP哲学的体现。

二、迭代计划。在小版本内,还划分成若干个更小的“迭代计划”。开发人员完成的版本,是一个个发布之后能够使用的完整但可能并不完善的软件。而“迭代计划”的成果,则并不是可以发布的完整的应用软件,只是在原有的软件基础之上又添加了几个完整的用户故事。

三、任务卡。业务人员和管理人员制定发布计划和迭代计划后,业务人员编写每个用户故事的功能测试。功能测试,就是客户问自己,如果软件能够通过这些功能测试,就可以认为软件能够完成用户故事。功能测试也是文字描述。实际上,按照我的理解,功能测试,很像UML的用例的事件流,或者是UML的序列图。实际上就是用户故事的分解。这当然有助于开发人员更清晰的知道自己要做的是什么。

随着理解的加深,开发人员对迭代计划中人物的时间估算会更加准确。

开发人员编写任务卡。开发人员将开发任务和对应的估算时间写在一张卡上。任务卡中的任务不仅仅包括实现用户故事的任务,还包括其他所有的工作任务。任务估算完毕之后,开发人员根据自己在本次迭代中应承担的“标准编程时间”认领任务卡。这就是详细的开发计划了!XP管理者不制定具体的实施计划,只是分配任务给开发人员。具体怎样分配时间来完成用户故事和任务,由程序员自己决定。

管理者跟踪程序员的任务完成情况,并在软件实际开发与估算发生偏差时提供帮助,或者调整任务分配。当所有的功能测试都通过时,我们的一次迭代任务就完成了。

三、软件架构

在软件的第一个版本的第一次迭代计划中,要实现的用户故事是有开发人员决定。因为,开发人员首先要在实现这些最基本的用户故事的同时,构建软件的架构。为后续的用户故事的实现提供一个基础的软件平台。

在开发软件之时,我们总会首先构建一个软件框架。软件框架的构造有几种方式:

1,从已有的软件中,去除所有的特定与业务的部分,得到一个“参考架构”。比如说,微软、SUN、Oracle、iBATIS开源项目、Spring开源项目都采用各自不同的技术实现过PetStore宠物店这个软件。我们可以去除掉所有与特定业务相关的代码,就得到了一个个使用不同技术的“参考架构”。以此为基础,在其中添加上实现用户故事的代码,就能够方便的完成软件。

还可以对其他来源的软件,提取出“参考架构”。比如,以前开发过的软件等。

2,软件技术非常之多,将各种技术以不同的方式组合起来,可以有无数种软件架构。我们可以在软件的第一个版本的第一次迭代时,选取一套简单的、基本的,能够搭建起整个体系结构的故事。根据业务需要,将集中最适合的软件技术整合起来,构建出一个软件架构。

只有需要构建软件架构的迭代计划中,才由开发人员指定要完成的用户故事,其他情况下,都由客户指定要完成的故事。

四、实际的开发阶段

制定了迭代计划,分配了任务之后,程序员就可以进入全速的开发状态了!

UML中最常用到的图是:用例图、序列图(或者状态图,它们可以互相转化)、类图。其他的UML图都存在很大的争议,在我看来,未必有用。UML中,Ivar Jacobson提出的用例驱动开发,最值得称道。

XP可以说是“测试驱动开发”。测试驱动开发,有两个方面:

功能测试驱动开发(又叫做“验收测试驱动开发”)和单元测试驱动开发。其中的“功能测试驱动开发”和UML的“用例驱动开发”类似。

在计划阶段,XP首先由客户主导,编写了“故事卡”,这类似于“用例”。

软件开发可以分为3大模块:文档层面的业务模块,代码层面的业务模块,代码层面的业务模块。

1,文档层面的业务模块

其中,文档层面的业务模块,是由客户负责编写的。包括故事和功能测试。对应于UML来说,就是用例图和序列图。故事是整体上的、概略的软件需求。用于概略的描述整个系统的需求。在计划阶段,选择相应的故事之后,在迭代计划阶段,客户对选中的故事进行深入地挖掘。为故事添加更多的细节,这样就得到了UML中用例的“事件流”。然后,可以据此编写功能测试(又叫验收测试)和功能测试所需的测试数据。系统的这部分功能完成后,如果对于输入的数据,其结果都和预期的一致,那么软件的故事就实现了。

2,代码层面的业务模块

客户提供了用户故事和功能测试之后,开发人员就可以使用序列图对用户故事及其事件流进行分析,以序列图为主线,找到提供服务的对象及其功能和配合方式。

代码层面的业务模块,是开发人员开发的代码。这些代码是直接对应于故事和功能测试的,是为了满足功能测试而存在的。其主要分为两个部分:业务代表模块和业务服务模块。业务代表模块,我使用Action表示;业务服务模块,我使用Service来表示。至于表现层(就是用户界面)这一部分,我没有把它们归在软件开发的三大模块中,因为表现层仅仅是一个用户界面,和软件核心无关。表现层是一个非常薄的软件模块,而且还可以任意替换。

表现层模块有很多种,如B/S架构的Browser(网络浏览器)作为表现层,或者C/S架构的富客户端技术作为表现层。在Java中,富客户端表现层技术主要有AWT,Swing,SWT这些界面技术;在Web瘦客户端方面,主要有JSP,Spring MVC,WebWork,Struts,Tapstry,JSF等技术。这里,我主要以目前最流行的Java网络浏览器表现层技术Struts为例来加以说明。Struts中Action是MVC模式(MVC模型-视图-控制器模式)中的控制器。同时,它也是业务代表。所谓业务代表,是指在客户端和业务服务层之间,增设一个“代表层”,所有客户端到服务器的调用,都“委托”该层完成。[10J2EE核心模式 第二版218页]业务代表虽然身处表现层(客户端)内,但实际上执行的是调用业务服务模块功能的任务,完成的是业务功能。因此,不少人都将业务代表划分在业务模块内。我也认同这种划分方法。

业务代表Action提供的是服务,为表现层提供业务处理的服务。所以,它和具体的客户是无关的,它为所有的客户都提供同一种服务。所以,它一般是多线程的单例对象。Struts的Action就是这样的。当然也有不同的,WebWork的Action是多例的。为每一个客户的请求都新建一个Action的实例。

业务服务模块Service提供的是业务服务。它是完全独立于表现层,可以为不同的表现层提供服务的模块。业务代表通过调用业务服务模块的服务方法为客户提供服务。Service也是多线程单例的对象。因为它为不同的客户提供的是同一种服务方法。

业务代表类Action和业务服务类Service互相配合,完成客户指定的用户故事和功能测试。同时,它们也是很容易混淆的。

表现层要尽可能的薄。这句话的意思也就是说业务代表Action也要尽可能的薄(因为表现层主要的代码就是在业务代表Action类中了)。Action只是接受客户请求,然后调用业务服务Service类的方法完成业务。它不应该包括任何业务代码。而我们常犯的错误,就是Action类太大,含有太多的业务代码。实际上,Action中不应该含有任何业务代码。因为,表现层是软件中最多变的需求。客户常常会仅仅因为界面不好看而要求更改。如果,Action中代码尽可能的少,那么更改用户界面就是一件很省力的事情。

让Action变薄的最简单方法,就是考虑如果对于这个业务需求,还需要增加一个表现层,那么在Action中哪一些代码会重复出现。如果有重复代码,就“重构”,把它们提炼成方法,然后移到对应的业务服务Service类中。

业务代表Action模块尽可能的薄(这样整个表现层也就薄了),业务服务Service模块尽可能的厚(这样,可重用代码就多了),这是代码层面的业务模块的第一大原则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值