20145213《Java程序设计》实验三敏捷开发与XP实践
实验要求
1.XP基础
2.XP核心实践
3.相关工具
实验内容
1.敏捷开发与XP
软件工程是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程。软件工程包括下列领域:软件需求分析、软件设计、软件构建、软件测试和软件维护。
人们在开发、运营、维护软件的过程中有很多技术、做法、习惯和思想体系。软件工程把这些相关的技术和过程统一到一个体系中,叫“软件开发流程”。软件开发流程的目的是为了提高软件开发、运营、维护的效率,并提高软件的质量、用户满意度、可靠性和软件的可维护性。
光有各种流程的思想是不够的,我们还要有一系列的工具来保证这些思想能够在实践中有效率地运作。软件开发很重要的一点不是看你能对多少理论讲的头头是道,还要看你对相关工具应用的如何,比如Java中单元测试要和JUnit的应用结合起来,建模要和Umbrello或StarUML的应用结合起来。编程学习是一个习而学的过程。
一个常见的公式是:软件工程=开发流程+工具
邹欣老师给出的两个公式:软件=程序+软件工程和软件企业=软件+商业模式
常见的开发流程有:
RUP(Rational Unified Process)
PSP(Personal Software Process )
TSP(Team Software Process )
Agile Process
……
XP软件开发是什么样的
通过XP准则来表达:
沟通 :XP认为项目成员之间的沟通是项目成功的关键,并把沟通看作项目中间协调与合作的主要推动因素。
简单 :XP假定未来不能可靠地预测,在现在考虑它从经济上是不明智的,所以不应该过多考虑未来的问题而是应该集中力量解决燃眉之急。
反馈 :XP认为系统本身及其代码是报告系统开发进度和状态的可靠依据。系统开发状态的反馈可以作为一种确定系统开发进度和决定系统下一步开发方向的手段。
勇气:代表了XP认为人是软件开发中最重要的一个方面的观点。在一个软件产品的开发中人的参与贯穿其整个生命周期,是人的勇气来排除困境,让团队把局部的最优抛之脑后,达到更重大的目标。表明了XP对“人让项目取得成功”的基本信任态度。
一项实践在XP环境中成功使用的依据通过XP的法则呈现,包括:快速反馈、假设简单性、递增更改、提倡更改、优质工作。
XP软件开发的基石是XP的活动,包括:编码、测试、倾听、设计。
项目成员用户成功执行XP活动的技术通过XP实践来呈现,包括编程、团队、过程相关的12条实践.
2.编码标准
编写代码一个重要的认识是“程序大多时候是给人看的”,编程标准使代码更容易阅读和理解,甚至可以保证其中的错误更少。编程标准包含:具有说明性的名字、清晰的表达式、直截了当的控制流、可读的代码和注释,以及在追求这些内容时一致地使用某些规则和惯用法的重要性。
编码标准中的版式就是一个很好的例子,版式虽然不会影响程序的功能,但会影响可读性。程序的版式追求清晰、美观,是程序风格的重要因素。
这里老师给出了一个国外的比赛,里面有C语言的格式错误示范,这里面的代码都是可以运行的,但是格式实在不符合一个程序员该有的格式。(这里有一个链接大家可以看看。)
-C语言混乱代码大赛之错误的示范
虽然他们的格式有问题,但是都是可以运行的代码,这又从另一个方面说明了,他们的C语言水平是真的高,这又是我们应该去努力靠近的。
3.结对编程
结对编程是XP中的重要实践。在结对编程模式下,一对程序员肩并肩、平等地、互补地进行开发工作。他们并排坐在一台电脑前,面对同一个显示器,使用同一个键盘、同一个鼠标一起工作。他们一起分析,一起设计,一起写测试用例,一起编码,一起做单元测试,一起做集成测试,一起写文档等。
结对编程中有两个角色:
驾驶员(Driver)是控制键盘输入的人。
领航员(Navigator)起到领航、提醒的作用。
如何结对编程,为何要结对编程,大家参考一下结对编程和两人合作 ,重点是:
驾驶员:写设计文档,进行编码和单元测试等XP开发流程。
领航员:审阅驾驶员的文档、驾驶员对编码等开发流程的执行;考虑单元测试的覆盖率;思考是否需要和如何重构;帮助驾驶员解决具体的技术问题。
驾驶员和领航员不断轮换角色,不要连续工作超过一小时,每工作一小时休息15分钟。领航员要控制时间。
主动参与。任何一个任务都首先是两个人的责任,也是所有人的责任。没有“我的代码”、“你的代码”或“他/她的代码”,只有“我们的代码”。
只有水平上的差距,没有级别上的差异。两人结对,尽管可能大家的级别资历不同,但不管在分析、设计或编码上,双方都拥有平等的决策权利。
团队精神是好多地方都强调的一个精神,最小的团队就是一对一的二人团队了,培养团队精神从结对编程开始吧。社会生活中人与人相处最重要的是诚信,有同理心,互利。结对编程中大家会出现分歧,如何更有效地合作要做到对事不对人,掌握这些是可以终生受益的。
4.版本控制
XP的集体所有制意味着每个人都对所有的代码负责;这一点,反过来又意味着每个人都可以更改代码的任意部分。结对编程对这一实践贡献良多:借由在不同的结对中工作,所有的程序员都能看到完全的代码。集体所有制的一个主要优势是提升了开发程序的速度,因为一旦代码中出现错误,任何程序员都能修正它。
这意味着代码要放到一个大家都能方便获取的地方,我们叫代码仓库。这引出另外一个话题叫版本控制(Version Control)。
不论是对于团队还是个体,版本控制都提供了很多好处。
版本控制提供项目级的 undo(撤销) 功能: 没有什么事情是终结版本, 任何错误必须很容易回滚。 假设你在使用世界上最复杂的文字处理系统。 它具备了所有的能想到的功能,就是没有支持 DELETE(删除) 键。想象你打字的时候得多么的谨慎和缓慢吧, 特别是一篇超大的文档的快临近末尾的时候, 一个不小心就要重新再来(试想你选中所有的文字, 不小心按了 DELETE 键, 因为没有撤销功能,只好重新录入)。编辑文字和版本控制相同,任何时候都需要回滚,无论是一个小时, 一天, 还是一周, 这让你的团队工作自由快速的工作, 而且对于修正错误也非常自信。
版本控制允许多人在同一代码上工作, 只要遵守一定的控制原则就行。 再也不会发生诸如一个人覆盖了另一个人编辑的代码,导致那个人的修改无效这样的情况。
版本控制系统保存了过去所作的修改的历史记录。如果你遭遇到一些惊讶的代码,通过版本控制系统可以很容易找出是谁干的, 修改了什么, 修改的时间, 如果幸运的话,还能找出原因。
版本控制系统还支持在主线上开发的同时发布多个软件版本。在软件发布的时候也不需要整个团队的停止工作,不需要冻结代码。
版本控制也是项目级的时间机器,你可以选择任何一个时间, 精确地查看项目在当时的情况。 这对研究非常有用, 也是重现以前某个有问题的发布版本的基础。
流行的版本控制工具有CVS,SVN,Git等,更多的可以参考这里。Git是Linus除了Linux操作系统外的另外一个重要发明。
实验步骤
前期准备
1.预先安装好JDK,并配置好环境变量,相关操作请看之前的实验报告,这里不赘述了。
2.本次实验两人一组,找一个JAVA水平相近,乐于互帮互助的搭档也是很重要的。
3.舍弃之前一直用的Notepad++,改用老师群里分享的IDEA资源
4.掌握git.add、 git.commit、 git.push代码托管方法。
需求分析
小组成员合作编写学生成绩管理系统。通过GIT相互推送,下载代码,每个小组成员都参与代码编写修改,让自己的想法、思路在代码上得到体现。在不断合作完善后得到完整正确的产品代码。
截图如下
设计
设计一个学生成绩管理系统对于小组来说并不陌生,比较在Java实验一及更早的C语言实验中都有过接触。选择这个题目也是为了将实验重心放在于体验与搭档合作编写代码的过程和熟悉掌握Git代码托管下载推送。关于学生成绩管理系统的设计思路具体可以参见我的[第一次Java实验报告](http://www.cnblogs.com/qiwei/p/5375557.html)
容易出现的问题
我们小组成员相互合作,其实还是一个成员先编写出主体代码,实现基本功能后,搭档负责添加功能和相关代码优化。而每个人编写的代码或多或少都会带有一点自己的味道,文学上称之为笔风,很熟悉的一个例子就是《红楼梦》的前八十回与后四十回在用词上的讲究,对剧情的把握,对人物形象的刻画都有明显的差距。为什么有那么多红学大师批判高鹗续作烂尾,说到底是认为他没有继承曹雪芹的意志。编写代码也是一样,负责编写主体初稿代码的同学可以向准备添加功能,优化代码的搭档讲述清楚自己的编写思路,方便搭档更好理解自己的代码并统一编写用的方法,避免一些人喜欢用数据结构,一些人用数组。
IDEA执行结果
第一次代码
package study;
/**
* Created by Administrator on 2016/4/24 0024.
*/
import java.util.Scanner;
//import java.util.Arrays;
public class S1
{
//输入方法
public static int scanner()
{
//初始化输入
Scanner scanner=new Scanner(System.in);
int us_str_int=0;
try
{
us_str_int =scanner.nextInt();
}
catch(Exception e)
{
System.out.println("Error:请确保您输入的是1~6之间的数字," +
"并且没有特殊字符!");
return 0;
}
if(us_str_int>0&&us_str_int<=6)
{
return us_str_int;
}
else
{
System.out.print("Error:超出命令范围!1~6");
return 0;
}
}
//输出方法
public static void println(String str)
{
System.out.println(str);
}
public static void main(String[]Args)
{
//System.out.println( scanner());//测试
//println("i");//测试
Scanner scanner=new Scanner(System.in);
boolean bool=true;
boolean leb1=false;
boolean leb2=false;
int max=0;
String[] students_name={};
int[] students_cj={};
do{
println("********欢迎进入成绩管理系统********");
println("请选择功能:1-姓名录入 2-成绩录入" +
" 3-成绩查询 ");
int us_ml = scanner();//调用事先写好错误处理的输入函数
switch(us_ml)
{
//功能1:
case 1:
//调用为了偷懒事先写好的输出函数哈哈^_^
println("**********开始录入姓名**********");
println("请输入班级的人数");
try
{
max = scanner.nextInt();
students_name=new String[max];
for(int i=0;i<=students_name.length-1;i++)
{
println("请输入第"+(i+1)+"个人的姓名:");
students_name[i]=scanner.next();
}
println("**********录入已完成**********");
bool=true;
leb1=true;
}
catch(Exception e)
{
println("Error:输入有误!");
bool=true;
leb1=false;
}
break;
//功能2
case 2:
//println(Arrays.toString(students_name));//用于测试
if(leb1==true)
{
students_cj= new int[students_name.length];
println("********开始录入成绩********");
for(int i=0;i<=students_name.length-1;i++)
{
println("学生姓名:"+students_name[i]+",成绩:?");
students_cj[i]=scanner.nextInt();
leb2=true;
}
println("********成绩录入完成********");
bool=true;
leb2=true;
}
else
{
println("Erorr:您还没有输入班级人数或学生姓名!");
bool=true;
leb2=false;
}
break;
case 3:
if(leb1==true&&leb2==true)
{
println("********开始查询********");
println("请输入要查找的学生姓名");
String st_name=scanner.next();
int i=0;
for(i=0;i<=students_name.length-1;i++)
{
if(st_name.equalsIgnoreCase(students_name[i]))
{
break;
}
}
if(i<=students_name.length-1)
{
System.out.println("学生"+students_name[i]+"的分数为:"
+students_cj[i]+"分");
}
else
{
println("Error:请检查你是否录入该姓名和成绩?");
}
println("********结束查询********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
}
}while(bool);
}}
执行结果如下:
第二次代码如下:
package study;
/**
* Created by Administrator on 2016/4/24 0024.
*/
import java.util.Scanner;
//import java.util.Arrays;
public class S2
{
//输入方法
public static int scanner()
{
//初始化输入
Scanner scanner=new Scanner(System.in);
int us_str_int=0;
try
{
us_str_int =scanner.nextInt();
}
catch(Exception e)
{
System.out.println("Error:请确保您输入的是1~6之间的数字," +
"并且没有特殊字符!");
return 0;
}
if(us_str_int>0&&us_str_int<=6)
{
return us_str_int;
}
else
{
System.out.print("Error:超出命令范围!1~6");
return 0;
}
}
//输出方法
public static void println(String str)
{
System.out.println(str);
}
public static void main(String[]Args)
{
//System.out.println( scanner());//测试
//println("i");//测试
Scanner scanner=new Scanner(System.in);
boolean bool=true;
boolean leb1=false;
boolean leb2=false;
int max=0;
String[] students_name={};
int[] students_cj={};
do{
println("********欢迎进入成绩管理系统********");
println("请选择功能:1-姓名录入 2-成绩录入" +
" 3-成绩列表 4-成绩查询");
int us_ml = scanner();//调用事先写好错误处理的输入函数
switch(us_ml)
{
//功能1:
case 1:
//调用为了偷懒事先写好的输出函数哈哈^_^
println("**********开始录入姓名**********");
println("请输入班级的人数");
try
{
max = scanner.nextInt();
students_name=new String[max];
for(int i=0;i<=students_name.length-1;i++)
{
println("请输入第"+(i+1)+"个人的姓名:");
students_name[i]=scanner.next();
}
println("**********录入已完成**********");
bool=true;
leb1=true;
}
catch(Exception e)
{
println("Error:输入有误!");
bool=true;
leb1=false;
}
break;
//功能2
case 2:
//println(Arrays.toString(students_name));//用于测试
if(leb1==true)
{
students_cj= new int[students_name.length];
println("********开始录入成绩********");
for(int i=0;i<=students_name.length-1;i++)
{
println("学生姓名:"+students_name[i]+",成绩:?");
students_cj[i]=scanner.nextInt();
leb2=true;
}
println("********成绩录入完成********");
bool=true;
leb2=true;
}
else
{
println("Erorr:您还没有输入班级人数或学生姓名!");
bool=true;
leb2=false;
}
break;
case 3:
if(leb1==true&&leb2==true)
{
println("********成绩列表********");
for(int i=0;i<=students_name.length-1;i++)
{
System.out.println((i+1)+". "+students_name[i]+"学生的成绩:"
+students_cj[i]+"分");
}
println("********成绩列表********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
case 4:
if(leb1==true&&leb2==true)
{
println("********开始查询********");
println("请输入要查找的学生姓名");
String st_name=scanner.next();
int i=0;
for(i=0;i<=students_name.length-1;i++)
{
if(st_name.equalsIgnoreCase(students_name[i]))
{
break;
}
}
if(i<=students_name.length-1)
{
System.out.println("学生"+students_name[i]+"的分数为:"
+students_cj[i]+"分");
}
else
{
println("Error:请检查你是否录入该姓名和成绩?");
}
println("********结束查询********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
}
}while(bool);
}}
执行结果如下:
第三次代码
package study;
/**
* Created by Administrator on 2016/4/24 0024.
*/
import java.util.Scanner;
//import java.util.Arrays;
public class S3
{
//输入方法
public static int scanner()
{
//初始化输入
Scanner scanner=new Scanner(System.in);
int us_str_int=0;
try
{
us_str_int =scanner.nextInt();
}
catch(Exception e)
{
System.out.println("Error:请确保您输入的是1~6之间的数字," +
"并且没有特殊字符!");
return 0;
}
if(us_str_int>0&&us_str_int<=6)
{
return us_str_int;
}
else
{
System.out.print("Error:超出命令范围!1~6");
return 0;
}
}
//输出方法
public static void println(String str)
{
System.out.println(str);
}
public static void main(String[]Args)
{
//System.out.println( scanner());//测试
//println("i");//测试
Scanner scanner=new Scanner(System.in);
boolean bool=true;
boolean leb1=false;
boolean leb2=false;
int max=0;
String[] students_name={};
int[] students_cj={};
do{
println("********欢迎进入成绩管理系统********");
println("请选择功能:1-姓名录入 2-成绩录入" +
" 3-成绩列表 4-成绩查询 5-成绩统计" );
int us_ml = scanner();//调用事先写好错误处理的输入函数
switch(us_ml)
{
//功能1:
case 1:
//调用为了偷懒事先写好的输出函数哈哈^_^
println("**********开始录入姓名**********");
println("请输入班级的人数");
try
{
max = scanner.nextInt();
students_name=new String[max];
for(int i=0;i<=students_name.length-1;i++)
{
println("请输入第"+(i+1)+"个人的姓名:");
students_name[i]=scanner.next();
}
println("**********录入已完成**********");
bool=true;
leb1=true;
}
catch(Exception e)
{
println("Error:输入有误!");
bool=true;
leb1=false;
}
break;
//功能2
case 2:
//println(Arrays.toString(students_name));//用于测试
if(leb1==true)
{
students_cj= new int[students_name.length];
println("********开始录入成绩********");
for(int i=0;i<=students_name.length-1;i++)
{
println("学生姓名:"+students_name[i]+",成绩:?");
students_cj[i]=scanner.nextInt();
leb2=true;
}
println("********成绩录入完成********");
bool=true;
leb2=true;
}
else
{
println("Erorr:您还没有输入班级人数或学生姓名!");
bool=true;
leb2=false;
}
break;
case 3:
if(leb1==true&&leb2==true)
{
println("********成绩列表********");
for(int i=0;i<=students_name.length-1;i++)
{
System.out.println((i+1)+". "+students_name[i]+"学生的成绩:"
+students_cj[i]+"分");
}
println("********成绩列表********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
case 4:
if(leb1==true&&leb2==true)
{
println("********开始查询********");
println("请输入要查找的学生姓名");
String st_name=scanner.next();
int i=0;
for(i=0;i<=students_name.length-1;i++)
{
if(st_name.equalsIgnoreCase(students_name[i]))
{
break;
}
}
if(i<=students_name.length-1)
{
System.out.println("学生"+students_name[i]+"的分数为:"
+students_cj[i]+"分");
}
else
{
println("Error:请检查你是否录入该姓名和成绩?");
}
println("********结束查询********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
case 5:
if(leb1==true&&leb2==true)
{
println("********开始统计********");
int i=0;
int num1=0;
int num2=0;
int num3=0;
int num4=0;
int num5=0;
int temp=0;
for(i=0;i<=students_name.length-1;i++)
{
if(students_cj[i]==100)
{
num1+=1;
continue;
}
if(students_cj[i]>=90&&students_cj[i]<=99)
{
num2+=1;
continue;
}
if(students_cj[i]>=80&&students_cj[i]<=89)
{
num3+=1;
continue;
}
if(students_cj[i]>=60&&students_cj[i]<=79)
{
num4+=1;
continue;
}
if(students_cj[i]>=0&&students_cj[i]<=59)
{
num5+=1;
continue;
}
}
System.out.println("满分(100 )"+num1+"人
"+"优秀(99-90)"+num2+"人
"+
"良好(89-80)"+num3+"人
"+"及格(79-60)"+num4+"人
"+"不及格(50-0)"+num5+"人
");
for(int j=0;j<=students_cj.length-1;j++)
{
temp+=students_cj[j];
}
System.out.println("全班平均成绩为:"+(temp/max)+"分");
println("********统计结束********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
}
}while(bool);
}}
执行结果如下:
第四次代码
package study;
/**
* Created by Administrator on 2016/4/24 0024.
*/
import java.util.Scanner;
//import java.util.Arrays;
public class S
{
//输入方法
public static int scanner()
{
//初始化输入
Scanner scanner=new Scanner(System.in);
int us_str_int=0;
try
{
us_str_int =scanner.nextInt();
}
catch(Exception e)
{
System.out.println("Error:请确保您输入的是1~6之间的数字," +
"并且没有特殊字符!");
return 0;
}
if(us_str_int>0&&us_str_int<=6)
{
return us_str_int;
}
else
{
System.out.print("Error:超出命令范围!1~6");
return 0;
}
}
//输出方法
public static void println(String str)
{
System.out.println(str);
}
public static void main(String[]Args)
{
//System.out.println( scanner());//测试
//println("i");//测试
Scanner scanner=new Scanner(System.in);
boolean bool=true;
boolean leb1=false;
boolean leb2=false;
int max=0;
String[] students_name={};
int[] students_cj={};
do{
println("********欢迎进入成绩管理系统********");
println("请选择功能:1-姓名录入 2-成绩录入" +
" 3-成绩列表 4-成绩查询 5-成绩统计" +
" 6-退出系统");
int us_ml = scanner();//调用事先写好错误处理的输入函数
switch(us_ml)
{
//功能1:
case 1:
//调用为了偷懒事先写好的输出函数哈哈^_^
println("**********开始录入姓名**********");
println("请输入班级的人数");
try
{
max = scanner.nextInt();
students_name=new String[max];
for(int i=0;i<=students_name.length-1;i++)
{
println("请输入第"+(i+1)+"个人的姓名:");
students_name[i]=scanner.next();
}
println("**********录入已完成**********");
bool=true;
leb1=true;
}
catch(Exception e)
{
println("Error:输入有误!");
bool=true;
leb1=false;
}
break;
//功能2
case 2:
//println(Arrays.toString(students_name));//用于测试
if(leb1==true)
{
students_cj= new int[students_name.length];
println("********开始录入成绩********");
for(int i=0;i<=students_name.length-1;i++)
{
println("学生姓名:"+students_name[i]+",成绩:?");
students_cj[i]=scanner.nextInt();
leb2=true;
}
println("********成绩录入完成********");
bool=true;
leb2=true;
}
else
{
println("Erorr:您还没有输入班级人数或学生姓名!");
bool=true;
leb2=false;
}
break;
case 3:
if(leb1==true&&leb2==true)
{
println("********成绩列表********");
for(int i=0;i<=students_name.length-1;i++)
{
System.out.println((i+1)+". "+students_name[i]+"学生的成绩:"
+students_cj[i]+"分");
}
println("********成绩列表********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
case 4:
if(leb1==true&&leb2==true)
{
println("********开始查询********");
println("请输入要查找的学生姓名");
String st_name=scanner.next();
int i=0;
for(i=0;i<=students_name.length-1;i++)
{
if(st_name.equalsIgnoreCase(students_name[i]))
{
break;
}
}
if(i<=students_name.length-1)
{
System.out.println("学生"+students_name[i]+"的分数为:"
+students_cj[i]+"分");
}
else
{
println("Error:请检查你是否录入该姓名和成绩?");
}
println("********结束查询********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
case 5:
if(leb1==true&&leb2==true)
{
println("********开始统计********");
int i=0;
int num1=0;
int num2=0;
int num3=0;
int num4=0;
int num5=0;
int temp=0;
for(i=0;i<=students_name.length-1;i++)
{
if(students_cj[i]==100)
{
num1+=1;
continue;
}
if(students_cj[i]>=90&&students_cj[i]<=99)
{
num2+=1;
continue;
}
if(students_cj[i]>=80&&students_cj[i]<=89)
{
num3+=1;
continue;
}
if(students_cj[i]>=60&&students_cj[i]<=79)
{
num4+=1;
continue;
}
if(students_cj[i]>=0&&students_cj[i]<=59)
{
num5+=1;
continue;
}
}
System.out.println("满分(100 )"+num1+"人
"+"优秀(99-90)"+num2+"人
"+
"良好(89-80)"+num3+"人
"+"及格(79-60)"+num4+"人
"+"不及格(50-0)"+num5+"人
");
for(int j=0;j<=students_cj.length-1;j++)
{
temp+=students_cj[j];
}
System.out.println("全班平均成绩为:"+(temp/max)+"分");
println("********统计结束********");
}
else
{
println("Error:请检查你是否录入姓名和成绩?");
}
bool=true;
break;
case 6:
bool=false;
break;
}
}while(bool);
}}
执行结果如下:
PSP(Personal Software Process)时间
步骤 | 耗时 | 百分比 |
---|---|---|
需求分析 | 10 | 10% |
设计 | 50 | 50% |
代码实现 | 30 | 30% |
测试 | 无 | |
分析总结 | 10 | 10% |