实验三 敏捷开发与XP实践
实验内容
1. XP基础
2. XP核心实践
3. 相关工具
实验步骤
(一)敏捷开发与XP
软件工程包括下列领域:软件需求分析、软件设计、软件构建、软件测试和软件维护。
标识符名字应当直观且可以拼读,可望文知意,不必进行“解码”,一般采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名,用词要准确例如“当前值”应该起名currentValue
,写成nowValue
就不准确了,但还凑合,写成dqz
(dang qian zhi 首字母)就是笑话了。
标识符的长度“min-length && max-information”
的原则,比如:maxVal
比 maxValueUntilOverflow
要好些,可以通过去元音法把变量名变短,如returnValue
->rtnVal
,message
->msg
;一般全局变量用具有说明性的名字,局部变量用短名字:单字符的名字,常见的如i,j,k等用作局部变量。
(二)编码标准
编写代码一个重要的认识是“程序大多时候是给人看的”,编程标准使代码更容易阅读和理解,甚至可以保证其中的错误更少。编程标准包含:具有说明性的名字、清晰的表达式、直截了当的控制流、可读的代码和注释,以及在追求这些内容时一致地使用某些规则和惯用法的重要性。
标识符的长度“min-length && max-information”
的原则,比如:maxVal
比 maxValueUntilOverflow
要好些,可以通过去元音法把变量名变短,如returnValue
->rtnVal
,message
->msg
;一般全局变量用具有说明性的名字,局部变量用短名字:单字符的名字,常见的如i,j,k等用作局部变量。
Java中的一般的命名规则有:
- 要体现各自的含义
- 包、类、变量用名词
- 方法名用动宾
- 包名全部小写,如:io,awt
- 类名第一个字母要大写,如:HelloWorldApp
- 变量名第一个字母要小写,如:userName
- 方法名第一个字母要小写:setName
(三)结对编程
武西垚工作:加注释,测试代码,加图片
郑伟:写主函数,完善各模块
结对同学:20135332
http://www.cnblogs.com/wuxiyao/
拼图游戏
* JAVA小游戏-拼图 我做的第一个小游戏
* Cell类是继承的按钮类,并加上相应图形,形成方格
*MyCanvas是一个面板,加载Cell类的对象(方格),是这三个类中的核心
*
*/
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MyMainFrame extends JFrame implements ActionListener {
MyCanvas myCanvas;
JPanel panelNorth,panelPreview;//定义上方的面板,及预览所需的面板
Button start,preview,set;//定义开始,预览,设定按钮
Container container;//容器,得到内容面板
public MyMainFrame() {//初使化
container=this.getContentPane();
start=new Button("开始");
start.addActionListener(this);
preview=new Button("预览");
preview.addActionListener(this);
set = new Button("设置");
set.addActionListener(this);
panelPreview=new JPanel();
panelPreview.setLayout(null);
Icon icon=new ImageIcon("pictrue/pic_"+MyCanvas.pictureID+".jpg");
JLabel label=new JLabel(icon);
label.setBounds(0,0,300,300);
panelPreview.add(label);
panelNorth=new JPanel();
panelNorth.setBackground(Color.red);
panelNorth.add(start);
panelNorth.add(preview);
panelNorth.add(set);
myCanvas=new MyCanvas();
container.add(myCanvas,BorderLayout.CENTER);
container.add(panelNorth,BorderLayout.NORTH);
this.setTitle("拼图小游戏-明");
this.setLocation(300,200);
this.setSize(308,365);
this.setResizable(false);
this.setVisible(true);
this.setDefaultCloseOperation(3);
}
public static void main(String[] args) {
// TODO 自动生成方法存根
new MyMainFrame();
}
public void actionPerformed(ActionEvent arg0) {//对三个按钮事件的处理
// TODO 自动生成方法存根
Button button=(Button)arg0.getSource();
if(button==start){
myCanvas.Start();
}else if(button==preview){
if(button.getLabel()=="预览"){
container.remove(myCanvas);
container.add(panelPreview);
panelPreview.updateUI();
container.repaint();
button.setLabel("返回");
}else{
container.remove(panelPreview);
container.add(myCanvas);
container.repaint();
button.setLabel("预览");
}
}else if(button==set){//修改所选图片
Choice pic = new Choice();
pic.add("小猫");
pic.add("小猪");
pic.add("云");
pic.add("QQ");
pic.add("卡通");
pic.add("花");
int i=JOptionPane.showConfirmDialog(this, pic, "选择图片", JOptionPane.OK_CANCEL_OPTION);
if(i==JOptionPane.YES_OPTION){
MyCanvas.pictureID=pic.getSelectedIndex()+1;
myCanvas.reLoadPictrue();
Icon icon=new ImageIcon("pictrue/pic_"+MyCanvas.pictureID+".jpg");
JLabel label=new JLabel(icon);
label.setBounds(0,0,300,300);
panelPreview.removeAll();
panelPreview.add(label);
panelPreview.repaint();
}
}
}
}
--------------------------------------------------
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
public class MyCanvas extends JPanel implements MouseListener {
boolean hasAddActionListener=false;//设置方格的动作监听器的标志位,TRUE为已经添加上动作事件,FALSE是尚未添加动作事件
Cell cell[];//定义方格
Rectangle cellNull;//定义空方格区域
public static int pictureID=1;//当前选择的图片代号
public MyCanvas() {
this.setLayout(null);
this.setSize(400,400);
cellNull=new Rectangle(200,200,100,100);//空方格区域在第三行每三列
cell=new Cell[9];
Icon icon;
for (int i = 0; i < 3; i++) {//为9个方格加载图片,并初使化坐标,形成三行三列
for(int j=0;j<3;j++){
icon=new ImageIcon("pictrue/pic_"+pictureID+"_"+(i*3+j+1)+".jpg");
cell[i*3+j]=new Cell(icon);
cell[i*3+j].setLocation(j*100,i*100);
this.add(cell[i*3+j]);
}
}
this.remove(cell[8]);//移除最后一个多余的方格
}
public void reLoadPictrue(){//当选择其它图形进行拼图时,需重新加载新图片
Icon icon;
for (int i = 0; i < 3; i++) {
for(int j=0;j<3;j++){
icon=new ImageIcon("pictrue/pic_"+pictureID+"_"+(i*3+j+1)+".jpg");
cell[i*3+j].setIcon(icon);
}
}
}
public boolean isFinish(){//判断是否拼合成功
for(int i=0;i<8;i++){
int x=cell[i].getBounds().x;
int y=cell[i].getBounds().y;
if(y/100*3+x/100!=i)
return false;
}
return true;
}
public void Start(){//对方格进行重新排列,打乱顺序
while(cell[0].getBounds().x<=100&&cell[0].getBounds().y<=100){//当第一个方格距左上角较近时
int x=cellNull.getBounds().x;
int y=cellNull.getBounds().y;
int direction=(int)(Math.random()*4);//产生0-4,对应空方格的上下左右移动
if(direction==0){//空方格左移动,与左侧方格互换位置,左侧方格右移动
x-=100;
if(test(x,y)){
for(int j=0;j<8;j++){
if((cell[j].getBounds().x==x)&&(cell[j].getBounds().y==y)){//依次寻找左侧的按钮
cell[j].move("RIGHT",100);
cellNull.setLocation(x,y);
break;//找到后跳出for循环
}
}
}
}else if(direction==1){//RIGHT
x+=100;
if(test(x,y)){
for(int j=0;j<8;j++){
if((cell[j].getBounds().x==x)&&(cell[j].getBounds().y==y)){
cell[j].move("LEFT",100);
cellNull.setLocation(x,y);
break;
}
}
}
}else if(direction==2){//UP
y-=100;
if(test(x,y)){
for(int j=0;j<8;j++){
if((cell[j].getBounds().x==x)&&(cell[j].getBounds().y==y)){
cell[j].move("DOWN",100);
cellNull.setLocation(x,y);
break;
}
}
}
}else{//DOWN
y+=100;
if(test(x,y)){
for(int j=0;j<8;j++){
if((cell[j].getBounds().x==x)&&(cell[j].getBounds().y==y)){
cell[j].move("UP",100);
cellNull.setLocation(x,y);
break;
}
}
}
}
}
if(!hasAddActionListener)//如果尚未添加动作事件,则添加
for(int i=0;i<8;i++)//为第个方格添加动作事件,这样单击按钮就能移动了
cell[i].addMouseListener(this);
hasAddActionListener=true;
}
private boolean test(int x,int y){
if((x>=0&&x<=200)||(y>=0&&y<=200))
return true;
else
return false;
}
// public void paint(Graphics g){
//
// for(int i=0;i<=300;i+=100)
// g.drawLine(0, i, 300, i);
// for(int i=0;i<=300;i+=100)
// g.drawLine(i, 0, i, 300);
// for(int i=0;i<8;i++)
// cell[i].repaint();
// }
public void mouseClicked(MouseEvent arg0) { }
public void mouseEntered(MouseEvent arg0) { }
public void mouseExited(MouseEvent arg0) { }
public void mouseReleased(MouseEvent arg0) { }
public void mousePressed(MouseEvent arg0) {//方格的鼠标事件,因为用到了MyCanvas中的一些方法,因此没有在Cell类中处理鼠标事件
Cell button=(Cell)arg0.getSource();
int x1=button.getBounds().x;//得到所单击方格的坐标
int y1=button.getBounds().y;
int x2=cellNull.getBounds().x;//得到空方格的坐标
int y2=cellNull.getBounds().y;
if(x1==x2&&y1-y2==100)//进行比较,如果满足条件则进行交换
button.move("UP",100);
else if(x1==x2&&y1-y2==-100)
button.move("DOWN",100);
else if(x1-x2==100&y1==y2)
button.move("LEFT",100);
else if(x1-x2==-100&&y1==y2)
button.move("RIGHT",100);
else
return;//不满足就不进行任何处理
cellNull.setLocation(x1,y1);
this.repaint();
if(this.isFinish()){//进行是否完成的判断
JOptionPane.showMessageDialog(this,"恭喜你完成拼图,加油!");
for(int i=0;i<8;i++)
cell[i].removeMouseListener(this);//如果已完成,撤消鼠标事件,鼠标单击方格不在起作用
hasAddActionListener=false;
}
}
}
------------------------------------------------------------------------------------------
import javax.swing.Icon;
import javax.swing.JButton;
public class Cell extends JButton {
Cell(Icon icon){//实际为ICON
super(icon);
this.setSize(100,100);
}
public void move(String direction,int sleep){//方格的移动
if(direction=="UP"){
this.setLocation(this.getBounds().x,this.getBounds().y-100);
}else if(direction=="DOWN"){
this.setLocation(this.getBounds().x,this.getBounds().y+100);
}else if(direction=="LEFT"){
this.setLocation(this.getBounds().x-100,this.getBounds().y);
}else{
this.setLocation(this.getBounds().x+100,this.getBounds().y);
}
}
}
(四)版本控制
注意一点,往代码库提交的代码一定编译、运行、测试都没有问题的代码,我们上面测试代码没有问题了,就可以提交了: 如图:我们可以先用git status
查看一下代码状态,显示有未跟踪的代码,并建议用git add <file>...
添加,我们使用git add HelloWorld.*
把要提交的文件的信息添加到索引库中。当我们使用git commit
时,git将依据索引库中的内容来进行文件的提交。这只是在本地操作,关闭实验环境,会删除代码的,如果想把代码保存到远程托管服务器中,需要使用git push
,实验完成前,一定不要忘了使用git push
,否则就是相当于你在Word中编辑了半天文件最后却没有保存。 我们可以修改HelloWorld.java
,如下图所示:
编译、运行、测试没有问题后进行提交,这儿使用的是git commit -a
: ,
我们可以通过git log
查看代码提交记录:
(五)重构
我们先看看重构的概念:
重构(Refactor),就是在不改变软件外部行为的基础上,改变软件内部的结构,使其更加易于阅读、易于维护和易于变更 。
重构中一个非常关键的前提就是“不改变软件外部行为”,它保证了我们在重构原有系统的同时,不会为原系统带来新的BUG,以确保重构的安全。如何保证不改变软件外部行为?重构后的代码要能通过单元测试。如何使其更加易于阅读、易于维护和易于变更 ?设计模式给出了重构的目标。
重构重要吗?你看看Eclipse菜单中有个refactor
菜单就知道了,重构几乎是现代IDE的标配了:
我们在编码标准
中说“给标识符命名”是程序员一项重要技能,以前没有这个意识,现在知道了怎么办?没问题,上图中重构的第一项功能就是Rename
,可以给类、包、方法、变量改名字。 例如这有个ABC
类:
这个类,类名,方法名和方法的参数名都有问题,没有注释的话是无法理解代码的。我们可以使用Eclipse中的重构功能来改名。修改方法是,用鼠标单击要改的名字,选择Eclipse中菜单中的Refactor
->Rename...
:
重构完的效果如下:
功能不变,代码水平立马上了一个档次,体会到命名的威力了吧?
Eclipse中菜单中的Refactor
->Encapsulate Field...
,如下图:
注意分析一下重构前后的代码变化: 同样可以封装id
和age
两个成员变量,结果如下:
上面第34,35行还是有问题的,每次打印学生信息都这么写代码违反了DRY原则,造成代码重复,正常的重构可以使用Eclipse中的Extract Method...
,如下图:
由于Java中所有的类都有个专门的toString方法,我们使用Eclipse中Source
->Generate toString()...
给Student
类产生一个toString
方法,如下图:
修改main的代码,结果如下:
大家想一想,这样重构后有什么好处?重构有什么问题吗?
我们要修改软件,万变不离其宗,无非就是四种动机:
- 增加新功能;
- 原有功能有BUG;
- 改善原有程序的结构;
- 优化原有系统的性能 。
- 最单纯的
Duplicated Code
就是[同一个class内的两个方法含有相同表达式(expression)]。这时候你需要做的就是采用Extract Method
提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。 - 另一种常见情况就是[两个互为兄弟(sibling)的subclasses内含有相同表达式]。要避免这种情况,只需要对两个classes都使用
Extract Method
,然后再对被提炼出的代码使用Pull Up Method
,将它推入superclass内。 - 如果代码之间只是类似,并非完全相同,那么就得运用
Extract Method
将相似部分和差异部分割开,构成单独一个方法。然后你可能发现或许可以运用Form Template Method
获得一个Template Method
设计模式。 - 如果有些方法以不同的算法做相同的事,你可以择定其中较清晰的一个,并使用
Substitute Algorithm
将其它方法的算法替换掉。 - 如果两个毫不相关的classes内出现
Duplicaded Code
,你应该考虑对其中一个使用Extract Class
,将重复代码提炼到一个独立class中,然后在另一个class内使用这个新class。但是,重复代码所在的方法也可能的确只应该属于某个class,另一个class只能调用它,抑或这个方法可能属于第三个class,而另两个classes应该引用这第三个class。你必须决定这个方法放在哪儿最合适,并确保它被安置后就不会再在其它任何地方出现。
其他Bad Smell
与相应的重构手法如下表所示:
Eclipse中Refactor
菜单中的重构手法的应用时机如下图所示:
更完整的手法可以参考《重构》作者Martin Fowler的博客。Eclipse中基本手法的使用大家可以参考任何人都可以重构来进行学习实践。
一个完整的重构流程包括:
- 从版本控制系统代码库中Check out code
- 读懂代码(包括测试代码)
- 发现bad smell
- Refactoring
- 运行所有的Unit Tests
- 往代码库中Check in code
我们结合Git给出一个比较完整的例子。
(六)实践项目
1. 以结对编程的方式编写一个软件,Blog中要给出结对同学的Blog网址,可以拍照展现结对编程情况,可以参考一下其他学校的作业
结对同学:20135332
http://www.cnblogs.com/wuxiyao/
2.记录TDD和重构的过程,测试代码不要少于业务代码,Eclipse中refactor
菜单下的重构技能不要少于5个
3.团队代码要使用git在实验楼中托管,要使用结对同学中的一个同学的账号托管。
4. 程序要有GUI界面,参考用户界面和用户体验
5.程序功能从豌豆荚游戏中选择一款用Java实现,注意:团队之间项目不能有重复,课代表协调一下。
6.实验报告中统计自己的PSP(Personal Software Process)时间
步骤 | 耗时 | 百分比 |
---|---|---|
需求分析 | 40min | |
设计 | 50min | |
代码实现 | 324min | |
测试 | 300min | |
分析总结 | 400min |
参考资料
1.《解析极限编程》 2.《构建之法 (电子版)》,著者邹欣Blog 3.《结对编程技术》 4.《版本控制之道》 5.《重构》 6.《重构与模式》 7.《程序设计实践》