I recently changed jobs. My new employer appears to be keenly interested in my bringing in XP, more, probably, due to my enthusiasm for it than for my relatively limited experience with it. During the initial interviews, we agreed that it was important for me to provide an overview of the process for the different people who will be involved, and I suggested ExtremeHour as an efficient, instructive and fun format for such an overview.
With some anxiety, I therefore found myself faced with the task of organizing an ExtremeHour. Though realizing how foolish I was to be doing this without previous experience - but comforted by the thought that others had been there before and reported on the Wiki - I embarked on the adventure, and lived to tell the tale, some of which is told below.
For various reasons, the ParisExtremeHour was to some extent modeled on the TorontoExtremeHour. I am especially grateful for the kind advice MikeBowler offered; several others lent support and encouragement, to whom I am also indebted.
Rehearsal
I held a "rehearsal" session (May 23, 2001) with people not involved with my future employer. I don't have that many friends who might be interested in such a crazy thing, so I got in touch with the FrenchXpCommunity - by itself a nice thing to be doing, possibly something I should have done sooner.
I learned quite a lot of things which I wouldn't have wanted to learn in the professional context. We finished one iteration without any acceptance tests passed, didn't get around to a second iteration, and cheerfully declared the project a failure - albeit a very instructive one. We did have a good time !
"Official" session #1
This was held on May 28, to the reasonable satisfaction of all involved. Acting on suggestions from the rehearsal group, I pitched the presentation from a more concrete, pragmatic angle; essentially restating Kent's argument in the XpSeries frontispiece that in actual application most non-agile methods are essentially half-measures piled upon half-measures, to the point that we lose sight of the truly important aspects of our work - to which XP responds with the notion that we will do less, but that what we will do is directly related to software development and consists of a minimal set of practices that can actually be followed wholeheartedly.
We went through two iterations, building the traditional coffee machine. The project wasn't a full success, but the dynamics of XP came into play more fully than in the rehearsal session. Somehow I suspect that this session was paradoxically less convincing to the developers and project managers on the audience - although this may be just my own anxieties speaking, I intend to follow up with interviews over the next few days to get more feedback.
Further sessions are planned to bring other coworkers up to speed on XP.
"Official" session #2
Held on June 13. This one didn't even get off the ground. We went through the theoretical part, which generated a lot of interesting discussion and thoughtful objections. I felt unable, however, to run the ExtremeHour proper, as the non-Development participants had taken a rain check at the last moment and one of the Development players didn't come back from the half-time break.
"Official" session #3
Held on October 16. Completed successfully.
Materials
MikeBowler suggests having these, to "nail down" the ideas people were exposed to during the session. For the rehearsal, I mimicked the TorontoExtremeHour format in a 2-page handout, which was appreciated by the participants and served to focus attention on some of the imperfections of the session. I revised these significantly for he "official" session. At MikeBowler's invitation I used the slides at http://www.redhookgroup.com/presentations.html as a handy "cheat sheet".
People
Rehearsal :
The official session ran 2PM-6PM. I kept the "intro to XP" down to half an hour; what with explaining all the rules and the first round of generating UserStories, the pre-production phase took one hour, the first iteration one hour, and the second iteration half an hour.
When we do it, the first iteration takes an hour and the second takes half an hour. Then we fill the rest of the time with more detailed explanations of XP. In our experience, two iterations is good enough to ensure that everyone understands the process. One iteration is not. For schedule, we have a very brief intro, followed by the ExtremeHour, followed by a more detailed discussion of XP -- MikeBowler
Theme
The rehearsal made it clear that it was better to impose a specific product theme. I initially left this up to the team to decide; what they came up with (a new & improved swing) was interesting but didn't leave enough conceptual room to generate interesting stories, made it difficult to discuss the effect of the metaphor, and so on. Some of the rejected alternates would have displayed the same difficulties, only exacerbated (the "Better Government Toolkit" to name but one example). It might be interesting to see what an experienced XP team could dream up and how it might follow through, though.
Metaphor
In both the rehearsal and the official session, I felt that the absence of a metaphor (or whatever could have been usefully designated by that term) was one of the critical factors in not being able to deliver a fully satisfactory product. This might be just my bias showing - it is the one practice that has captured most of my attention lately - but some of the other participants concurred.
Limitations
The ExtremeHour - together with the initial XP introduction, and taking advantages of the many opportunities it affords to discuss various aspects of software projects - was in my experience an interesting and effective way to introduce XP to groups of people. It does have several limitations and blind spots, which must be addressed otherwise. [More later...]
Follow-ups
One of my coworkers, BenjaminSiband?, and myself produced the following code in an afternoon-long PairProgramming session, TestFirst-style. We implemented one "story", the intent of which was to output calendars in the following form :
L M M J V S D 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31The following code is what resulted from this session.
import junit.framework.*; public class CalendarAccept? extends TestCase { public CalendarAccept?(String name) { super(name); } public void testCalendarForMay() throws Throwable { String calendarForMay = " L M M J V S D\n 1 2 3 4 5 6\n 7 8 9 10 11 12 13\n14 15 16 17 18 19 20\n21 22 23 24 25 26 27\n28 29 30 31\n"; Calendar forMay = new Calendar(2001,Calendar.MAY); assert("Unexpected output "+forMay.display(),forMay.display().equals(calendarForMay)); } public void testCalendarForApril() throws Throwable { String calendarForApril = " L M M J V S D\n 1\n 2 3 4 5 6 7 8\n 9 10 11 12 13 14 15\n16 17 18 19 20 21 22\n23 24 25 26 27 28 29\n30\n"; Calendar forApril = new Calendar(2001,Calendar.APRIL); assert("Unexpected output "+forApril.display(),forApril.display().equals(calendarForApril)); }}
import junit.framework.*; import java.util.GregorianCalendar?; public class CalendarUnit? extends TestCase { public CalendarUnit?(String name) { super(name); } public void testGregorianCalendarValues() { assert(GregorianCalendar?.SUNDAY == 1); assert(GregorianCalendar?.MONDAY == 2); assert(GregorianCalendar?.TUESDAY == 3); assert(GregorianCalendar?.WEDNESDAY == 4); assert(GregorianCalendar?.THURSDAY == 5); assert(GregorianCalendar?.FRIDAY == 6); assert(GregorianCalendar?.SATURDAY == 7); } public void testFirstDay() { Calendar may = new Calendar(2001, Calendar.MAY); assert("First day of May reported at index "+may.firstWeekDay(), may.firstWeekDay() == 2); } public void testFirstDaySunday() { Calendar april = new Calendar(2001, Calendar.APRIL); assert("First day of April reported at index "+april.firstWeekDay(), april.firstWeekDay() == 7); } public void testFirstMonday() { Calendar may = new Calendar(2001, Calendar.MAY); assert("First monday of May reported at index "+may.firstMonday(), may.firstMonday() == 0); Calendar april = new Calendar(2001, Calendar.APRIL); assert("First monday of April reported at index "+april.firstMonday(), april.firstMonday() == -5); } public void testLastDay() { Calendar may = new Calendar(2001, Calendar.MAY); assert(may.lastDay() == 31); } public void testLastDayApril() { Calendar april = new Calendar(2001, Calendar.APRIL); assert(april.lastDay() == 30); } public void testBuildFirstWeekOfMay() { Calendar may = new Calendar(2001, Calendar.MAY); Week firstOfMay = new Week(may.firstMonday()); assert(firstOfMay.display().equals(" 1 2 3 4 5 6")); } public void testBuildWeeks() { Calendar april = new Calendar(2001, Calendar.APRIL); assert(april.buildWeeks().size() == 6); Calendar may = new Calendar(2001, Calendar.MAY); assert(may.buildWeeks().size() == 5); Calendar feb99 = new Calendar(1999, Calendar.FEBRUARY); assert(feb99.buildWeeks().size() == 4); } } import junit.framework.*; public class WeekUnit? extends TestCase { public WeekUnit?(String name) { super(name); } public void testFirstIsMonday() { Week fromMonday = new Week(1); assert(fromMonday.display().equals(" 1 2 3 4 5 6 7")); } public void testSeventhIsMonday() { Week fromMonday = new Week(7); assert(fromMonday.display().equals(" 7 8 9 10 11 12 13")); } public void testWeekStartingOnWednesday() { Week fromWed = new Week(-1); // L M M J V S D assert("Unexpected result "+fromWed.display(), fromWed.display().equals(" 1 2 3 4 5")); } public void testIncompleteWeek() { Week lastWeek = new Week(28,31); // L M M J V S D assert("Unexpected result "+lastWeek.display(), lastWeek.display().equals("28 29 30 31")); } public void testPad() { Week paddingWeek = new Week(1); assert(paddingWeek.pad(7).equals(" 7")); assert(paddingWeek.pad(11).equals("11")); } } import java.util.GregorianCalendar?; import java.util.Vector; import java.util.Iterator; public class Calendar { public static final int JANUARY = 1; public static final int FEBRUARY = 2; public static final int MARCH = 3; public static final int APRIL = 4; public static final int MAY = 5; public static final int JUNE = 6; public static final int JULY = 7; public static final int AUGUST = 8; public static final int SEPTEMBER = 9; public static final int OCTOBER = 10; public static final int NOVEMBER = 11; public static final int DECEMBER = 12; private intyear; private intmonth; private Vectorweeks; public Calendar(int year, int month) { this.year = year; this.month = month; this.weeks = buildWeeks(); } public int firstWeekDay() { GregorianCalendar? ourMonth = getMonth(); int dayOfWeek = ourMonth.get(GregorianCalendar?.DAY_OF_WEEK)-1; // Switch MONDAY (== 2) and SUNDAY (== 1) return dayOfWeek != 0 ? dayOfWeek : 7; } public int firstMonday() { return 2-firstWeekDay(); } public int lastDay() { GregorianCalendar? ourMonth = getMonth(); return ourMonth.getActualMaximum(GregorianCalendar?.DAY_OF_MONTH); } public int size() { return weeks.size(); } public String displayHeader() { return " L M M J V S D\n"; } public String displayWeeks() { String result = ""; for (Iterator it = weeks.iterator(); it.hasNext(); ) { Week current = (Week)it.next(); result += current.display() + "\n"; } return result; } public String display() { return displayHeader() + displayWeeks(); } public Vector buildWeeks() { Vector weeks = new Vector(); for (int firstDayOfWeek = firstMonday(); firstDayOfWeek <= lastDay(); firstDayOfWeek += 7) { Week current = new Week(firstDayOfWeek); if (firstDayOfWeek + 7 > lastDay()) { current = new Week(firstDayOfWeek, lastDay() ); } weeks.add(current); } return weeks; } private GregorianCalendar? getMonth() { GregorianCalendar? result = new GregorianCalendar?(); result.set(GregorianCalendar?.YEAR,year); result.set(GregorianCalendar?.MONTH,month-1); result.set(GregorianCalendar?.DAY_OF_MONTH,1); return result; } } public class Week { private intstartingDate; private intendingDate; public Week(int startingDate) { this(startingDate,startingDate+6); } public Week(int startingDate, int endingDate) { this.startingDate = startingDate; this.endingDate = endingDate; } public String pad(int number) { if (number <= 9) { return " " + number; } else { return "" + number; } } public String display() { String result = ""; for (int day = startingDate; day <= endingDate; day++) { String formattedDay = (day > 0) ? pad(day) : " "; result += " " + formattedDay; } return result.substring(1); } }