Software
Engineering Lecture: eXtreme programming
Extreme Programming (XP) is a software methodology for producing well
managed high quality software that is responsive to the customers needs.
The software development is managed by planning small releases and constantly
monitors the project's completion date. Managers communicate
with the programmers via daily short stand up meetings, and behave more
as coaches then as bosses. High quality software is achieved by emphasizing
simple design based on objected oriented (OO) principles, and using paired
programming to refractor, test and write code.
The final software product is responsive to the evolving customer needs
by involving the customer in the development via user stories and
acceptance tests.
XP is typically defined by a set of rules and practices. I will
list only the rules and practices that are important to our class (see
http://www.extremeprogramming.org/rules.html
for the complete list of rules):
Planning:
-
user stories are written by the customer
The customer can communicate and adopt his goal by writing stories.
Stories are short (two or three sentence) task descriptions written in
the users language. User stories are similar to use-case but differ
in extend that they describe the project. A few use-case can describe
a project while many many 100 stories are required to describe a
large project. The small stories permit the customer to modified
the project without rewriting all the specifications. XP rejects
project specifications. Because our project is small and I, the customer,
do not plan on changing the direction I have favored use-cases over
user-stories. You may chose to start your programming day with a
feature list to implement; this is like using stories.
-
the project is divided into iterations
Code of any substantial length is impossible to write straight though,
especially if the customer changes features or specifications. A
lot is learned by attempting to solve the problem. I have built in
several iterations into the project.
Designing:
The design rules are the most important for this class.
-
Keep it simple (stupid!)
Well designed code is simple and easy to understand. When ever
I have methods that exceed a 100 lines I ask myself; "have I made this
to complicated?" If there are more than a dozen class used at any
time in the code I wonder; "is the system design too complicated?"
-
Chose a system metaphor
Well chosen class and method names are easy to remember and recognize.
Lots of time can be wasted looking for the names of methods or classes.
Be consistent and use precise names.
-
Use CRC cards for designing classes
CRC is an object orientated design technique that involves both the
customer and programmer in designing classes. Programmers can use
technique among themselves. CRC is best learned by practice; if you
wish we can simulate CRC design in class.
-
In doubt make a spike
Are you concerned about a design or technique? Then write a short
program, the spike, to test your ideas. The spike should
be written outside the program and incorporated only after proven reliable.
-
Do not add extra features early
Get the basis of the program done first. Do not clutter up the
program with lines that might be used in the future.
-
Make it better; refractor
Your goal as a programmer should always be to write the best code that
you can. So if in process of developing you spot code that you could
have written better then rewrite it. You can also refractor to make
the code simpler and less clutter of complications.
Coding:
The second most important aspect.
-
Code written to agreed standards
Standards are specified to make code easier to read and understand.
-
Production code is pair programmed
One of the difficult aspects of XP to understand; two heads are better
then one. Paired programming is not two programmers programming in
isolations, rather two programming side by side. They share the keyboard
and monitor. While one programmer is typing the other programming
is thinking of the big picture. I have suggested that the observing
programmer could be thinking about how to test the methods. Try this technique
and report back to me your feelings.
-
Leave optimization for last
It is tempting to write the best code first time and optimize the code
ahead of time. If you do so, you will fine that you have spent a
lot time coding a part of the code that seldom runs.
This is wasted effort.
-
No overtime
You can not write good code if you tired. It is tempting to keep
working late in the night (I am doing it right now), and I have found that
if the task at hand is not too hard then it possible to put in long hours.
If the work requires creativity and thought then sleep on the problem.
I have been successful so many times in coming up with solutions in the
morning that I plan my work day around that schedule. I am digressing.
Get plenty of sleep.
Testing:
Almost as important as designing. A good design is worth nothing
if it is implemented wrong.
-
Test first code second
OK I left out some words out for the shock value, but it is not as
far off as you might think. (see "programming - test first" links at http://www.xp123.com/xplor/)
Write and trying running the test before you have written the method.
Naturally this implies that you will be black box testing. More on
this important topic later.
-
Unit test any suspect code
Unit test means that you test at the level of the method call.
Testing at this level insures that your basis is good.
There are tools for specific platforms to ease the burden of unit testing;
see http://junit.sourceforge.net/
for a java unit tester API. These the unit test code remain with
the program so that any time a change is made the test programs can be
ran.
-
Make more tests to trap bugs
Do not despair if you find bugs just write more tests. Try to
make a game out of it. During debugging both programmers of the pair
are active in finding and fixing bugs.
-
Acceptance test by the customer
Another means for the customer to become involved.
An example testing:
We want to design a unit test procedure that:
-
Stays with the class.
-
Can be ran at any time to assure us that the class is correct.
-
Incrementally built, so that the tests grow as the class grows.
My design for a test procedure is:
-
For each proposed class method (I'll call functional method) write a test
method.
-
Test the syntax of the test method by compiling the test method.
-
Call the test method from the class's main method.
-
Code the the functional method.
-
Check the syntax of functional method by compiling.
-
Run main method of class.
-
Repeat process with next functional method.
I code this example as spike in order to verify the above test procedure.
A spike is a quick program to test an idea:
public class ASpike{
private String name;
ASpike(String name){this.name = name;}
public String toString() {return name;}
// test method for constructor and string
public static void testASpike(String testName){
ASpike testASpike =
new ASpike(testName);
System.out.println("ASpike:
testASpike: "+ testASpike);
}
public static void main(String[] args) {
ASpike.testASpike("test");
}
} // end class ASpike
The procedure works! The program prints out what we
expect:
ASpike: testASpike: test
There is one problem; we would prefer a more silent tester. We want
a tester that only outputs if there is a failure, then we will not resist
running the tester frequently. Also we want to perform more then
one test. I wrote AnotherSpike to illustrate this technique.
public class AnotherSpike{
private String name;
AnotherSpike(String name){this.name = name;}
public String toString() {return name;}
// test method for constructor
public static boolean AnotherSpikeTest(String
testName){
AnotherSpike testAnotherSpike
= new AnotherSpike(testName);
// Test for reference
assignment
if( testAnotherSpike
== null ) {
System.out.println("Failed: AnotherSpike: Constructor");
return false;
}
return true;
} // end test constructor
// test method for toString
public static boolean toStingTest(String testName){
AnotherSpike testAnotherSpike
= new AnotherSpike(testName);
// Note we check for
failure otherwise return true
if( !( testName.equals(testAnotherSpike.toString())
) ){
System.out.println("Failed: AnotherSpike: toString");
return false;
}
return true;
} // end test toString
public static void main(String[] args) {
// status of unit test
keep in boolean pass
boolean pass = true;
// if test should fail
pass becomes false and stays false
pass = pass &&
AnotherSpike.AnotherSpikeTest("test");
pass = pass &&
AnotherSpike.toStingTest("test");
if( !pass ) System.out.println("FAILED:
AnotherSpike FAILED unit tests");
else System.out.println("PASSED:
AnotherSpike PASSED unit tests");
}
} // end class AnotherSpike
Using this technique copious output will not be produced during
tests. All the unit tests will run and a failure will produce output.
Note that I have change the constructor test name; I am trying to develop
a system metaphor. Naming the test method is just the method name
followed by Test.
Another example testing: Maybe more to real life.
This example is only meant to illustrate how to write unit tests.
Please do not assume that your project should be designed with the following
classes or methods.
For a project to construct a maze game assume that you have already
written a Room class representing rooms in the maze and are currently coding
the Player class which represents a player in the maze. Naturally
you well want a move() method for the Player class which attempts to moves
a player through a door in room. If the door is open the method returns
the new room, and if the door is closed returns the old room.
So you write the test method we will call it moveTest(). The method
belongs in the Player class and called from the main method.
public class Player{
// class fields ....
private String name;
private Room location;
// constructor ...
Player(String name, Room location){
this.name = name;
this.location = location;
} // end Player, too easy to test?
// I recommend overload the toString method,
// makes testing easier
String toString(){
// assumed that I have overload Room's
toString
return "Player "+name+" in Room "+location;
}
public boolean moveTest(){
// Need to make some rooms with
all the doors closed
Room testRoom = new Room("testRoom");
Room adjacentRoom = new Room("adjacentRoom");
// Need to open the door between the two
rooms,
// assume that directions are public static
final of Room
testRoom.setDoorOpen(Room.EAST, adjacentRoom);
adjacentRoom.setDoorOpen(Room.WEST, testRoom);
// also need a player located in testRoom
Player testPlayer = new Player("testPlayer",
testRoom);
// Test by moving the player
if ( !(testPlayer.move(Room.EAST) == adjacentRoom)
){
System.out.println("FAILED:
moveTest open door from "
+testRoom+" going EAST to "+adjacentRoom);
return false;
}
if( !(testPlayer.move(Room.EAST) == adjacentRoom)
){
System.out.println("FAILED:
moveTest closed door from "
+adjacentRoom+" going EAST ");
return false;
}
// and test more moves ....
// passed all test
return true;
} // end moveTest
// more methods ...
// we call moveTest from the main
public static void main(String [] arg){
boolean pass = true;
pass = pass && Player.moveTest()
// more tests go here
if( !pass ) System.out.println("FAILED:
Player FAILED unit tests");
else System.out.println("PASSED: Player
PASSED unit tests");
} // end Player main
In your projects you should include test methods.